aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoraozeritsky <aozeritsky@ydb.tech>2023-08-21 17:15:26 +0300
committeraozeritsky <aozeritsky@ydb.tech>2023-08-21 17:30:24 +0300
commit6128c83f475b712a95f262e363dd2d3681500a0e (patch)
treefb52e4fa7d0dccb6bc4e6ced2707e11bfaf2e4b3
parenteac8ca1f552726198b4d7a21fcdecf8954339262 (diff)
downloadydb-6128c83f475b712a95f262e363dd2d3681500a0e.tar.gz
Add yt into autobuild
-rw-r--r--contrib/libs/CMakeLists.linux-aarch64.txt1
-rw-r--r--contrib/libs/CMakeLists.linux-x86_64.txt1
-rw-r--r--contrib/libs/isa-l/erasure_code/ec_base.c371
-rw-r--r--contrib/libs/isa-l/erasure_code/ec_base.h6680
-rw-r--r--contrib/libs/isa-l/erasure_code/ec_highlevel_func.c336
-rw-r--r--contrib/libs/isa-l/erasure_code/ec_multibinary.asm95
-rw-r--r--contrib/libs/isa-l/erasure_code/ec_multibinary_darwin.asm96
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx.asm341
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx2.asm360
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx512.asm250
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_sse.asm343
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx.asm240
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx2.asm251
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx512.asm235
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_2vect_mad_sse.asm244
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx.asm382
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx2.asm402
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx512.asm275
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_sse.asm383
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx.asm292
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx2.asm322
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx512.asm252
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_3vect_mad_sse.asm303
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx.asm446
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx2.asm466
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx512.asm306
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_sse.asm448
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx.asm341
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx2.asm347
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx512.asm272
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_4vect_mad_sse.asm347
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx.asm308
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx2.asm320
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_sse.asm309
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx.asm370
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx2.asm368
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_5vect_mad_sse.asm378
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx.asm320
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx2.asm331
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_sse.asm320
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx.asm399
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx2.asm405
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_6vect_mad_sse.asm411
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx.asm276
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx2.asm285
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx512.asm245
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_sse.asm276
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_mad_avx.asm201
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_mad_avx2.asm208
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_mad_avx512.asm198
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_mad_sse.asm202
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_mul_avx.asm168
-rw-r--r--contrib/libs/isa-l/erasure_code/gf_vect_mul_sse.asm175
-rw-r--r--contrib/libs/isa-l/erasure_code/ya.make81
-rw-r--r--contrib/libs/isa-l/include/erasure_code.h944
-rw-r--r--contrib/libs/isa-l/include/gf_vect_mul.h152
-rw-r--r--contrib/libs/isa-l/include/multibinary.asm399
-rw-r--r--contrib/libs/isa-l/include/reg_sizes.asm248
-rw-r--r--contrib/libs/pfr/CMakeLists.linux-aarch64.txt18
-rw-r--r--contrib/libs/pfr/CMakeLists.linux-x86_64.txt18
-rw-r--r--contrib/libs/pfr/CMakeLists.txt13
-rw-r--r--contrib/libs/pfr/LICENSE_1_0.txt23
-rw-r--r--contrib/libs/pfr/README.md104
-rw-r--r--contrib/libs/pfr/include/pfr/detail/config.hpp91
-rw-r--r--contrib/libs/pfr/include/pfr/detail/fields_count.hpp330
-rw-r--r--contrib/libs/pfr/include/pfr/detail/make_integer_sequence.hpp90
-rw-r--r--contrib/libs/pfr/include/pfr/detail/sequence_tuple.hpp127
-rw-r--r--contrib/libs/pfr/include/pfr/detail/size_t_.hpp18
-rw-r--r--contrib/libs/pfr/include/pfr/detail/unsafe_declval.hpp36
-rw-r--r--contrib/libs/pfr/include/pfr/tuple_size.hpp48
-rw-r--r--contrib/libs/pfr/ya.make20
-rw-r--r--library/cpp/erasure/README.md9
-rw-r--r--library/cpp/erasure/codec.cpp1
-rw-r--r--library/cpp/erasure/codec.h80
-rw-r--r--library/cpp/erasure/helpers.cpp80
-rw-r--r--library/cpp/erasure/helpers.h30
-rw-r--r--library/cpp/erasure/isa_erasure.cpp1
-rw-r--r--library/cpp/erasure/isa_erasure.h170
-rw-r--r--library/cpp/erasure/lrc.cpp1
-rw-r--r--library/cpp/erasure/lrc.h326
-rw-r--r--library/cpp/erasure/lrc_isa.cpp1
-rw-r--r--library/cpp/erasure/lrc_isa.h77
-rw-r--r--library/cpp/erasure/public.cpp1
-rw-r--r--library/cpp/erasure/public.h57
-rw-r--r--library/cpp/erasure/reed_solomon.cpp1
-rw-r--r--library/cpp/erasure/reed_solomon.h60
-rw-r--r--library/cpp/erasure/reed_solomon_isa.cpp1
-rw-r--r--library/cpp/erasure/reed_solomon_isa.h69
-rw-r--r--library/cpp/erasure/ya.make35
-rw-r--r--library/cpp/testing/CMakeLists.txt1
-rw-r--r--library/cpp/testing/gtest/friend.h5
-rw-r--r--library/cpp/testing/mock_server/CMakeLists.darwin-x86_64.txt19
-rw-r--r--library/cpp/testing/mock_server/CMakeLists.linux-aarch64.txt20
-rw-r--r--library/cpp/testing/mock_server/CMakeLists.linux-x86_64.txt20
-rw-r--r--library/cpp/testing/mock_server/CMakeLists.txt17
-rw-r--r--library/cpp/testing/mock_server/CMakeLists.windows-x86_64.txt19
-rw-r--r--library/cpp/testing/mock_server/ut/server_ut.cpp40
-rw-r--r--library/cpp/testing/mock_server/ut/ya.make11
-rw-r--r--ydb/library/yql/providers/ya.make1
-rw-r--r--ydb/library/yql/providers/yt/CMakeLists.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/CMakeLists.darwin-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/CMakeLists.linux-aarch64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/CMakeLists.linux-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/CMakeLists.windows-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/CMakeLists.darwin-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-aarch64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/CMakeLists.windows-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.darwin-x86_64.txt75
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-aarch64.txt78
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-x86_64.txt80
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.windows-x86_64.txt68
-rw-r--r--ydb/library/yql/providers/yt/codec/ut/CMakeLists.darwin-x86_64.txt78
-rw-r--r--ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-aarch64.txt81
-rw-r--r--ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-x86_64.txt83
-rw-r--r--ydb/library/yql/providers/yt/codec/ut/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/codec/ut/CMakeLists.windows-x86_64.txt71
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/CMakeLists.darwin-x86_64.txt2
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-aarch64.txt2
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-x86_64.txt2
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/CMakeLists.windows-x86_64.txt2
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.darwin-x86_64.txt30
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-aarch64.txt34
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-x86_64.txt34
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.windows-x86_64.txt30
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp24
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.h10
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp120
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.h11
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader_impl.h205
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.cpp347
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.h92
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp282
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.h10
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/dq/ya.make35
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.darwin-x86_64.txt82
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-aarch64.txt85
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-x86_64.txt87
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.windows-x86_64.txt75
-rw-r--r--ydb/library/yql/providers/yt/gateway/CMakeLists.txt1
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/CMakeLists.darwin-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-aarch64.txt1
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/CMakeLists.windows-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.darwin-x86_64.txt61
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-aarch64.txt65
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-x86_64.txt67
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.windows-x86_64.txt54
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/CMakeLists.darwin-x86_64.txt22
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-aarch64.txt23
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-x86_64.txt23
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/CMakeLists.windows-x86_64.txt22
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/ya.make14
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp184
-rw-r--r--ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.h9
-rw-r--r--ydb/library/yql/providers/yt/gateway/ya.make6
-rw-r--r--ydb/library/yql/providers/yt/lib/ya.make19
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/CMakeLists.darwin-x86_64.txt26
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-aarch64.txt27
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-x86_64.txt27
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/CMakeLists.windows-x86_64.txt26
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/ya.make18
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp131
-rw-r--r--ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.h9
-rw-r--r--ydb/library/yql/providers/yt/provider/CMakeLists.darwin-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/provider/CMakeLists.linux-aarch64.txt1
-rw-r--r--ydb/library/yql/providers/yt/provider/CMakeLists.linux-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/provider/CMakeLists.windows-x86_64.txt1
-rw-r--r--ydb/library/yql/providers/yt/provider/ut/CMakeLists.darwin-x86_64.txt89
-rw-r--r--ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-aarch64.txt93
-rw-r--r--ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-x86_64.txt95
-rw-r--r--ydb/library/yql/providers/yt/provider/ut/CMakeLists.txt17
-rw-r--r--ydb/library/yql/providers/yt/provider/ut/CMakeLists.windows-x86_64.txt82
-rw-r--r--ydb/library/yql/providers/yt/ya.make17
-rw-r--r--yt/yt/CMakeLists.darwin-x86_64.txt11
-rw-r--r--yt/yt/CMakeLists.linux-aarch64.txt12
-rw-r--r--yt/yt/CMakeLists.linux-x86_64.txt12
-rw-r--r--yt/yt/CMakeLists.txt12
-rw-r--r--yt/yt/CMakeLists.windows-x86_64.txt11
-rw-r--r--yt/yt/client/CMakeLists.linux-aarch64.txt206
-rw-r--r--yt/yt/client/CMakeLists.linux-x86_64.txt206
-rw-r--r--yt/yt/client/CMakeLists.txt13
-rw-r--r--yt/yt/client/api/accounting_client.h40
-rw-r--r--yt/yt/client/api/admin_client.h282
-rw-r--r--yt/yt/client/api/client.cpp65
-rw-r--r--yt/yt/client/api/client.h107
-rw-r--r--yt/yt/client/api/client_cache.cpp45
-rw-r--r--yt/yt/client/api/client_cache.h56
-rw-r--r--yt/yt/client/api/client_common.cpp133
-rw-r--r--yt/yt/client/api/client_common.h204
-rw-r--r--yt/yt/client/api/config.cpp190
-rw-r--r--yt/yt/client/api/config.h256
-rw-r--r--yt/yt/client/api/connection.h75
-rw-r--r--yt/yt/client/api/cypress_client.h253
-rw-r--r--yt/yt/client/api/delegating_client.cpp1000
-rw-r--r--yt/yt/client/api/delegating_client.h617
-rw-r--r--yt/yt/client/api/etc_client.cpp26
-rw-r--r--yt/yt/client/api/etc_client.h77
-rw-r--r--yt/yt/client/api/file_client.h87
-rw-r--r--yt/yt/client/api/file_reader.h28
-rw-r--r--yt/yt/client/api/file_writer.h38
-rw-r--r--yt/yt/client/api/helpers.cpp24
-rw-r--r--yt/yt/client/api/helpers.h13
-rw-r--r--yt/yt/client/api/internal_client.cpp27
-rw-r--r--yt/yt/client/api/internal_client.h136
-rw-r--r--yt/yt/client/api/journal_client.cpp33
-rw-r--r--yt/yt/client/api/journal_client.h86
-rw-r--r--yt/yt/client/api/journal_reader.h32
-rw-r--r--yt/yt/client/api/journal_writer.h36
-rw-r--r--yt/yt/client/api/operation_archive_schema.cpp127
-rw-r--r--yt/yt/client/api/operation_archive_schema.h133
-rw-r--r--yt/yt/client/api/operation_client.cpp226
-rw-r--r--yt/yt/client/api/operation_client.h447
-rw-r--r--yt/yt/client/api/persistent_queue.cpp1054
-rw-r--r--yt/yt/client/api/persistent_queue.h137
-rw-r--r--yt/yt/client/api/private.h16
-rw-r--r--yt/yt/client/api/public.cpp10
-rw-r--r--yt/yt/client/api/public.h219
-rw-r--r--yt/yt/client/api/query_tracker_client.cpp61
-rw-r--r--yt/yt/client/api/query_tracker_client.h155
-rw-r--r--yt/yt/client/api/queue_client.h127
-rw-r--r--yt/yt/client/api/rowset.cpp197
-rw-r--r--yt/yt/client/api/rowset.h49
-rw-r--r--yt/yt/client/api/rpc_proxy/address_helpers.cpp72
-rw-r--r--yt/yt/client/api/rpc_proxy/address_helpers.h52
-rw-r--r--yt/yt/client/api/rpc_proxy/api_service_proxy.h175
-rw-r--r--yt/yt/client/api/rpc_proxy/client_base.cpp1021
-rw-r--r--yt/yt/client/api/rpc_proxy/client_base.h192
-rw-r--r--yt/yt/client/api/rpc_proxy/client_impl.cpp2034
-rw-r--r--yt/yt/client/api/rpc_proxy/client_impl.h503
-rw-r--r--yt/yt/client/api/rpc_proxy/config.cpp125
-rw-r--r--yt/yt/client/api/rpc_proxy/config.h79
-rw-r--r--yt/yt/client/api/rpc_proxy/connection.cpp18
-rw-r--r--yt/yt/client/api/rpc_proxy/connection.h23
-rw-r--r--yt/yt/client/api/rpc_proxy/connection_impl.cpp512
-rw-r--r--yt/yt/client/api/rpc_proxy/connection_impl.h90
-rw-r--r--yt/yt/client/api/rpc_proxy/discovery_service_proxy.h28
-rw-r--r--yt/yt/client/api/rpc_proxy/file_reader.cpp71
-rw-r--r--yt/yt/client/api/rpc_proxy/file_reader.h15
-rw-r--r--yt/yt/client/api/rpc_proxy/file_writer.cpp94
-rw-r--r--yt/yt/client/api/rpc_proxy/file_writer.h15
-rw-r--r--yt/yt/client/api/rpc_proxy/helpers.cpp1871
-rw-r--r--yt/yt/client/api/rpc_proxy/helpers.h280
-rw-r--r--yt/yt/client/api/rpc_proxy/journal_reader.cpp73
-rw-r--r--yt/yt/client/api/rpc_proxy/journal_reader.h15
-rw-r--r--yt/yt/client/api/rpc_proxy/journal_writer.cpp91
-rw-r--r--yt/yt/client/api/rpc_proxy/journal_writer.h15
-rw-r--r--yt/yt/client/api/rpc_proxy/private.h39
-rw-r--r--yt/yt/client/api/rpc_proxy/protocol_version.h3
-rw-r--r--yt/yt/client/api/rpc_proxy/protocol_version_variables.h.in10
-rw-r--r--yt/yt/client/api/rpc_proxy/public.cpp12
-rw-r--r--yt/yt/client/api/rpc_proxy/public.h42
-rw-r--r--yt/yt/client/api/rpc_proxy/row_stream.cpp138
-rw-r--r--yt/yt/client/api/rpc_proxy/row_stream.h66
-rw-r--r--yt/yt/client/api/rpc_proxy/table_mount_cache.cpp144
-rw-r--r--yt/yt/client/api/rpc_proxy/table_mount_cache.h21
-rw-r--r--yt/yt/client/api/rpc_proxy/table_reader.cpp257
-rw-r--r--yt/yt/client/api/rpc_proxy/table_reader.h14
-rw-r--r--yt/yt/client/api/rpc_proxy/table_writer.cpp106
-rw-r--r--yt/yt/client/api/rpc_proxy/table_writer.h15
-rw-r--r--yt/yt/client/api/rpc_proxy/timestamp_provider.cpp69
-rw-r--r--yt/yt/client/api/rpc_proxy/timestamp_provider.h21
-rw-r--r--yt/yt/client/api/rpc_proxy/transaction.cpp45
-rw-r--r--yt/yt/client/api/rpc_proxy/transaction.h31
-rw-r--r--yt/yt/client/api/rpc_proxy/transaction_impl-inl.h30
-rw-r--r--yt/yt/client/api/rpc_proxy/transaction_impl.cpp1075
-rw-r--r--yt/yt/client/api/rpc_proxy/transaction_impl.h292
-rw-r--r--yt/yt/client/api/rpc_proxy/wire_row_stream.cpp141
-rw-r--r--yt/yt/client/api/rpc_proxy/wire_row_stream.h16
-rw-r--r--yt/yt/client/api/security_client.cpp92
-rw-r--r--yt/yt/client/api/security_client.h152
-rw-r--r--yt/yt/client/api/skynet.cpp49
-rw-r--r--yt/yt/client/api/skynet.h32
-rw-r--r--yt/yt/client/api/sticky_transaction_pool.cpp191
-rw-r--r--yt/yt/client/api/sticky_transaction_pool.h33
-rw-r--r--yt/yt/client/api/table_client.cpp35
-rw-r--r--yt/yt/client/api/table_client.h561
-rw-r--r--yt/yt/client/api/table_reader.h51
-rw-r--r--yt/yt/client/api/table_writer.h37
-rw-r--r--yt/yt/client/api/transaction-inl.h42
-rw-r--r--yt/yt/client/api/transaction.cpp197
-rw-r--r--yt/yt/client/api/transaction.h288
-rw-r--r--yt/yt/client/api/transaction_client.h93
-rw-r--r--yt/yt/client/arrow/CMakeLists.linux-aarch64.txt26
-rw-r--r--yt/yt/client/arrow/CMakeLists.linux-x86_64.txt26
-rw-r--r--yt/yt/client/arrow/CMakeLists.txt13
-rw-r--r--yt/yt/client/arrow/arrow_row_stream_decoder.cpp20
-rw-r--r--yt/yt/client/arrow/arrow_row_stream_decoder.h19
-rw-r--r--yt/yt/client/arrow/arrow_row_stream_encoder.cpp1143
-rw-r--r--yt/yt/client/arrow/arrow_row_stream_encoder.h23
-rw-r--r--yt/yt/client/arrow/fbs/CMakeLists.linux-aarch64.txt78
-rw-r--r--yt/yt/client/arrow/fbs/CMakeLists.linux-x86_64.txt78
-rw-r--r--yt/yt/client/arrow/fbs/CMakeLists.txt13
-rw-r--r--yt/yt/client/arrow/fbs/Message.fbs140
-rw-r--r--yt/yt/client/arrow/fbs/Schema.fbs353
-rw-r--r--yt/yt/client/arrow/fbs/SparseTensor.fbs218
-rw-r--r--yt/yt/client/arrow/fbs/Tensor.fbs54
-rw-r--r--yt/yt/client/arrow/fbs/ya.make14
-rw-r--r--yt/yt/client/arrow/public.cpp10
-rw-r--r--yt/yt/client/arrow/public.h13
-rw-r--r--yt/yt/client/arrow/ya.make16
-rw-r--r--yt/yt/client/chaos_client/config.h29
-rw-r--r--yt/yt/client/chaos_client/helpers.cpp95
-rw-r--r--yt/yt/client/chaos_client/public.h42
-rw-r--r--yt/yt/client/chaos_client/replication_card.cpp590
-rw-r--r--yt/yt/client/chaos_client/replication_card.h160
-rw-r--r--yt/yt/client/chaos_client/replication_card_cache.cpp33
-rw-r--r--yt/yt/client/chaos_client/replication_card_cache.h38
-rw-r--r--yt/yt/client/chaos_client/replication_card_serialization.cpp465
-rw-r--r--yt/yt/client/chaos_client/replication_card_serialization.h76
-rw-r--r--yt/yt/client/chunk_client/chunk_replica-inl.h301
-rw-r--r--yt/yt/client/chunk_client/chunk_replica.cpp272
-rw-r--r--yt/yt/client/chunk_client/chunk_replica.h250
-rw-r--r--yt/yt/client/chunk_client/config.cpp430
-rw-r--r--yt/yt/client/chunk_client/config.h499
-rw-r--r--yt/yt/client/chunk_client/data_statistics.cpp217
-rw-r--r--yt/yt/client/chunk_client/data_statistics.h75
-rw-r--r--yt/yt/client/chunk_client/helpers.cpp63
-rw-r--r--yt/yt/client/chunk_client/helpers.h23
-rw-r--r--yt/yt/client/chunk_client/public.cpp23
-rw-r--r--yt/yt/client/chunk_client/public.h203
-rw-r--r--yt/yt/client/chunk_client/read_limit.cpp1037
-rw-r--r--yt/yt/client/chunk_client/read_limit.h251
-rw-r--r--yt/yt/client/chunk_client/reader_base.h24
-rw-r--r--yt/yt/client/chunk_client/ready_event_reader_base.cpp39
-rw-r--r--yt/yt/client/chunk_client/ready_event_reader_base.h51
-rw-r--r--yt/yt/client/chunk_client/writer_base.h25
-rw-r--r--yt/yt/client/complex_types/check_type_compatibility.cpp444
-rw-r--r--yt/yt/client/complex_types/check_type_compatibility.h20
-rw-r--r--yt/yt/client/complex_types/check_yson_token-inl.h25
-rw-r--r--yt/yt/client/complex_types/check_yson_token.cpp22
-rw-r--r--yt/yt/client/complex_types/check_yson_token.h28
-rw-r--r--yt/yt/client/complex_types/infinite_entity.cpp39
-rw-r--r--yt/yt/client/complex_types/infinite_entity.h41
-rw-r--r--yt/yt/client/complex_types/public.h16
-rw-r--r--yt/yt/client/complex_types/scanner_factory.h231
-rw-r--r--yt/yt/client/complex_types/time_text.cpp62
-rw-r--r--yt/yt/client/complex_types/time_text.h15
-rw-r--r--yt/yt/client/complex_types/uuid_text.cpp127
-rw-r--r--yt/yt/client/complex_types/uuid_text.h19
-rw-r--r--yt/yt/client/complex_types/yson_format_conversion.cpp1267
-rw-r--r--yt/yt/client/complex_types/yson_format_conversion.h61
-rw-r--r--yt/yt/client/converters/boolean_converter.cpp101
-rw-r--r--yt/yt/client/converters/boolean_converter.h15
-rw-r--r--yt/yt/client/converters/converter.cpp77
-rw-r--r--yt/yt/client/converters/converter.h52
-rw-r--r--yt/yt/client/converters/floating_point_converter.cpp134
-rw-r--r--yt/yt/client/converters/floating_point_converter.h15
-rw-r--r--yt/yt/client/converters/helper.cpp60
-rw-r--r--yt/yt/client/converters/helper.h49
-rw-r--r--yt/yt/client/converters/integer_converter.cpp176
-rw-r--r--yt/yt/client/converters/integer_converter.h17
-rw-r--r--yt/yt/client/converters/null_converter.cpp48
-rw-r--r--yt/yt/client/converters/null_converter.h13
-rw-r--r--yt/yt/client/converters/string_converter.cpp379
-rw-r--r--yt/yt/client/converters/string_converter.h25
-rw-r--r--yt/yt/client/cypress_client/public.cpp12
-rw-r--r--yt/yt/client/cypress_client/public.h49
-rw-r--r--yt/yt/client/driver/admin_commands.cpp453
-rw-r--r--yt/yt/client/driver/admin_commands.h273
-rw-r--r--yt/yt/client/driver/authentication_commands.cpp97
-rw-r--r--yt/yt/client/driver/authentication_commands.h73
-rw-r--r--yt/yt/client/driver/chaos_commands.cpp58
-rw-r--r--yt/yt/client/driver/chaos_commands.h41
-rw-r--r--yt/yt/client/driver/command-inl.h274
-rw-r--r--yt/yt/client/driver/command.cpp119
-rw-r--r--yt/yt/client/driver/command.h321
-rw-r--r--yt/yt/client/driver/config.cpp79
-rw-r--r--yt/yt/client/driver/config.h58
-rw-r--r--yt/yt/client/driver/cypress_commands.cpp487
-rw-r--r--yt/yt/client/driver/cypress_commands.h266
-rw-r--r--yt/yt/client/driver/driver.cpp683
-rw-r--r--yt/yt/client/driver/driver.h167
-rw-r--r--yt/yt/client/driver/etc_commands.cpp437
-rw-r--r--yt/yt/client/driver/etc_commands.h223
-rw-r--r--yt/yt/client/driver/file_commands.cpp171
-rw-r--r--yt/yt/client/driver/file_commands.h74
-rw-r--r--yt/yt/client/driver/helpers.cpp66
-rw-r--r--yt/yt/client/driver/helpers.h31
-rw-r--r--yt/yt/client/driver/internal_commands.cpp144
-rw-r--r--yt/yt/client/driver/internal_commands.h95
-rw-r--r--yt/yt/client/driver/journal_commands.cpp343
-rw-r--r--yt/yt/client/driver/journal_commands.h60
-rw-r--r--yt/yt/client/driver/private.h20
-rw-r--r--yt/yt/client/driver/proxy_discovery_cache.cpp204
-rw-r--r--yt/yt/client/driver/proxy_discovery_cache.h58
-rw-r--r--yt/yt/client/driver/public.h22
-rw-r--r--yt/yt/client/driver/query_commands.cpp198
-rw-r--r--yt/yt/client/driver/query_commands.h112
-rw-r--r--yt/yt/client/driver/queue_commands.cpp206
-rw-r--r--yt/yt/client/driver/queue_commands.h115
-rw-r--r--yt/yt/client/driver/scheduler_commands.cpp611
-rw-r--r--yt/yt/client/driver/scheduler_commands.h382
-rw-r--r--yt/yt/client/driver/table_commands.cpp1396
-rw-r--r--yt/yt/client/driver/table_commands.h518
-rw-r--r--yt/yt/client/driver/transaction_commands.cpp141
-rw-r--r--yt/yt/client/driver/transaction_commands.h67
-rw-r--r--yt/yt/client/driver/ya.make31
-rw-r--r--yt/yt/client/election/public.cpp12
-rw-r--r--yt/yt/client/election/public.h38
-rw-r--r--yt/yt/client/federated/client.cpp732
-rw-r--r--yt/yt/client/federated/client.h28
-rw-r--r--yt/yt/client/federated/config.cpp35
-rw-r--r--yt/yt/client/federated/config.h51
-rw-r--r--yt/yt/client/federated/connection.cpp137
-rw-r--r--yt/yt/client/federated/connection.h25
-rw-r--r--yt/yt/client/federated/private.h15
-rw-r--r--yt/yt/client/federated/public.h14
-rw-r--r--yt/yt/client/federated/unittests/client_ut.cpp480
-rw-r--r--yt/yt/client/federated/unittests/connection_ut.cpp75
-rw-r--r--yt/yt/client/federated/unittests/ya.make21
-rw-r--r--yt/yt/client/federated/ya.make17
-rw-r--r--yt/yt/client/file_client/config.h33
-rw-r--r--yt/yt/client/file_client/public.h13
-rw-r--r--yt/yt/client/formats/config.cpp355
-rw-r--r--yt/yt/client/formats/config.h419
-rw-r--r--yt/yt/client/formats/dsv_parser.cpp236
-rw-r--r--yt/yt/client/formats/dsv_parser.h35
-rw-r--r--yt/yt/client/formats/dsv_writer.cpp278
-rw-r--r--yt/yt/client/formats/dsv_writer.h89
-rw-r--r--yt/yt/client/formats/escape.cpp249
-rw-r--r--yt/yt/client/formats/escape.h94
-rw-r--r--yt/yt/client/formats/format.cpp659
-rw-r--r--yt/yt/client/formats/format.h132
-rw-r--r--yt/yt/client/formats/helpers.cpp104
-rw-r--r--yt/yt/client/formats/helpers.h37
-rw-r--r--yt/yt/client/formats/lenval_control_constants.h18
-rw-r--r--yt/yt/client/formats/parser.cpp28
-rw-r--r--yt/yt/client/formats/parser.h29
-rw-r--r--yt/yt/client/formats/private.h20
-rw-r--r--yt/yt/client/formats/protobuf.cpp1633
-rw-r--r--yt/yt/client/formats/protobuf.h384
-rw-r--r--yt/yt/client/formats/protobuf_options.cpp503
-rw-r--r--yt/yt/client/formats/protobuf_options.h99
-rw-r--r--yt/yt/client/formats/protobuf_parser.cpp734
-rw-r--r--yt/yt/client/formats/protobuf_parser.h17
-rw-r--r--yt/yt/client/formats/protobuf_writer.cpp1078
-rw-r--r--yt/yt/client/formats/protobuf_writer.h35
-rw-r--r--yt/yt/client/formats/public.h91
-rw-r--r--yt/yt/client/formats/schemaful_dsv_parser.cpp198
-rw-r--r--yt/yt/client/formats/schemaful_dsv_parser.h31
-rw-r--r--yt/yt/client/formats/schemaful_dsv_writer.cpp397
-rw-r--r--yt/yt/client/formats/schemaful_dsv_writer.h50
-rw-r--r--yt/yt/client/formats/schemaful_writer.cpp119
-rw-r--r--yt/yt/client/formats/schemaful_writer.h52
-rw-r--r--yt/yt/client/formats/schemaless_writer_adapter.cpp462
-rw-r--r--yt/yt/client/formats/schemaless_writer_adapter.h159
-rw-r--r--yt/yt/client/formats/skiff_parser.cpp631
-rw-r--r--yt/yt/client/formats/skiff_parser.h29
-rw-r--r--yt/yt/client/formats/skiff_writer.cpp1112
-rw-r--r--yt/yt/client/formats/skiff_writer.h37
-rw-r--r--yt/yt/client/formats/skiff_yson_converter-inl.h157
-rw-r--r--yt/yt/client/formats/skiff_yson_converter.cpp1899
-rw-r--r--yt/yt/client/formats/skiff_yson_converter.h113
-rw-r--r--yt/yt/client/formats/unversioned_value_yson_writer.cpp84
-rw-r--r--yt/yt/client/formats/unversioned_value_yson_writer.h28
-rw-r--r--yt/yt/client/formats/versioned_writer.cpp138
-rw-r--r--yt/yt/client/formats/versioned_writer.h48
-rw-r--r--yt/yt/client/formats/web_json_writer.cpp780
-rw-r--r--yt/yt/client/formats/web_json_writer.h33
-rw-r--r--yt/yt/client/formats/ya.make45
-rw-r--r--yt/yt/client/formats/yamr_parser.cpp98
-rw-r--r--yt/yt/client/formats/yamr_parser.h28
-rw-r--r--yt/yt/client/formats/yamr_parser_base.cpp453
-rw-r--r--yt/yt/client/formats/yamr_parser_base.h160
-rw-r--r--yt/yt/client/formats/yamr_writer.cpp211
-rw-r--r--yt/yt/client/formats/yamr_writer.h34
-rw-r--r--yt/yt/client/formats/yamr_writer_base.cpp90
-rw-r--r--yt/yt/client/formats/yamr_writer_base.h45
-rw-r--r--yt/yt/client/formats/yamred_dsv_parser.cpp130
-rw-r--r--yt/yt/client/formats/yamred_dsv_parser.h29
-rw-r--r--yt/yt/client/formats/yamred_dsv_writer.cpp251
-rw-r--r--yt/yt/client/formats/yamred_dsv_writer.h35
-rw-r--r--yt/yt/client/formats/yql_yson_converter.cpp855
-rw-r--r--yt/yt/client/formats/yql_yson_converter.h41
-rw-r--r--yt/yt/client/formats/yson_map_to_unversioned_value.cpp181
-rw-r--r--yt/yt/client/formats/yson_map_to_unversioned_value.h59
-rw-r--r--yt/yt/client/formats/yson_parser.cpp56
-rw-r--r--yt/yt/client/formats/yson_parser.h18
-rw-r--r--yt/yt/client/hedging/cache.cpp93
-rw-r--r--yt/yt/client/hedging/cache.h50
-rw-r--r--yt/yt/client/hedging/config.cpp67
-rw-r--r--yt/yt/client/hedging/config.h69
-rw-r--r--yt/yt/client/hedging/counter.cpp51
-rw-r--r--yt/yt/client/hedging/counter.h50
-rw-r--r--yt/yt/client/hedging/hedging.cpp284
-rw-r--r--yt/yt/client/hedging/hedging.h65
-rw-r--r--yt/yt/client/hedging/hedging_executor.cpp80
-rw-r--r--yt/yt/client/hedging/hedging_executor.h110
-rw-r--r--yt/yt/client/hedging/logger.cpp11
-rw-r--r--yt/yt/client/hedging/logger.h13
-rw-r--r--yt/yt/client/hedging/options.cpp48
-rw-r--r--yt/yt/client/hedging/options.h19
-rw-r--r--yt/yt/client/hedging/penalty_provider.cpp230
-rw-r--r--yt/yt/client/hedging/penalty_provider.h41
-rw-r--r--yt/yt/client/hedging/public.h17
-rw-r--r--yt/yt/client/hedging/rpc.cpp156
-rw-r--r--yt/yt/client/hedging/rpc.h50
-rw-r--r--yt/yt/client/hedging/unittests/cache_ut.cpp110
-rw-r--r--yt/yt/client/hedging/unittests/counters_ut.cpp241
-rw-r--r--yt/yt/client/hedging/unittests/hedging_ut.cpp533
-rw-r--r--yt/yt/client/hedging/unittests/hook.cpp20
-rw-r--r--yt/yt/client/hedging/unittests/options_ut.cpp83
-rw-r--r--yt/yt/client/hedging/unittests/penalty_provider_ut.cpp224
-rw-r--r--yt/yt/client/hedging/unittests/rpc_ut.cpp35
-rw-r--r--yt/yt/client/hedging/unittests/ya.make30
-rw-r--r--yt/yt/client/hedging/ya.make28
-rw-r--r--yt/yt/client/hive/public.h41
-rw-r--r--yt/yt/client/hive/timestamp_map.cpp77
-rw-r--r--yt/yt/client/hive/timestamp_map.h32
-rw-r--r--yt/yt/client/hive/transaction_participant.h60
-rw-r--r--yt/yt/client/hydra/public.h62
-rw-r--r--yt/yt/client/hydra/version.cpp112
-rw-r--r--yt/yt/client/hydra/version.h80
-rw-r--r--yt/yt/client/job_tracker_client/helpers.cpp29
-rw-r--r--yt/yt/client/job_tracker_client/helpers.h14
-rw-r--r--yt/yt/client/job_tracker_client/public.cpp13
-rw-r--r--yt/yt/client/job_tracker_client/public.h76
-rw-r--r--yt/yt/client/journal_client/config.cpp17
-rw-r--r--yt/yt/client/journal_client/config.h34
-rw-r--r--yt/yt/client/journal_client/public.cpp10
-rw-r--r--yt/yt/client/journal_client/public.h17
-rw-r--r--yt/yt/client/misc/config.cpp15
-rw-r--r--yt/yt/client/misc/config.h28
-rw-r--r--yt/yt/client/misc/io_tags.cpp32
-rw-r--r--yt/yt/client/misc/io_tags.h54
-rw-r--r--yt/yt/client/misc/method_helpers.cpp12
-rw-r--r--yt/yt/client/misc/method_helpers.h59
-rw-r--r--yt/yt/client/misc/public.h53
-rw-r--r--yt/yt/client/misc/workload.cpp188
-rw-r--r--yt/yt/client/misc/workload.h73
-rw-r--r--yt/yt/client/node_tracker_client/helpers.cpp58
-rw-r--r--yt/yt/client/node_tracker_client/helpers.h29
-rw-r--r--yt/yt/client/node_tracker_client/node_directory.cpp753
-rw-r--r--yt/yt/client/node_tracker_client/node_directory.h195
-rw-r--r--yt/yt/client/node_tracker_client/private.h15
-rw-r--r--yt/yt/client/node_tracker_client/public.cpp13
-rw-r--r--yt/yt/client/node_tracker_client/public.h70
-rw-r--r--yt/yt/client/object_client/helpers-inl.h186
-rw-r--r--yt/yt/client/object_client/helpers.cpp263
-rw-r--r--yt/yt/client/object_client/helpers.h191
-rw-r--r--yt/yt/client/object_client/public.cpp68
-rw-r--r--yt/yt/client/object_client/public.h412
-rw-r--r--yt/yt/client/query_client/public.h20
-rw-r--r--yt/yt/client/query_client/query_builder.cpp159
-rw-r--r--yt/yt/client/query_client/query_builder.h70
-rw-r--r--yt/yt/client/query_client/query_statistics.cpp117
-rw-r--r--yt/yt/client/query_client/query_statistics.h41
-rw-r--r--yt/yt/client/query_tracker_client/CMakeLists.linux-aarch64.txt22
-rw-r--r--yt/yt/client/query_tracker_client/CMakeLists.linux-x86_64.txt22
-rw-r--r--yt/yt/client/query_tracker_client/CMakeLists.txt13
-rw-r--r--yt/yt/client/query_tracker_client/public.cpp1
-rw-r--r--yt/yt/client/query_tracker_client/public.h44
-rw-r--r--yt/yt/client/query_tracker_client/ya.make13
-rw-r--r--yt/yt/client/queue_client/common.cpp85
-rw-r--r--yt/yt/client/queue_client/common.h40
-rw-r--r--yt/yt/client/queue_client/config.cpp62
-rw-r--r--yt/yt/client/queue_client/config.h63
-rw-r--r--yt/yt/client/queue_client/consumer_client.cpp582
-rw-r--r--yt/yt/client/queue_client/consumer_client.h123
-rw-r--r--yt/yt/client/queue_client/partition_reader.cpp481
-rw-r--r--yt/yt/client/queue_client/partition_reader.h47
-rw-r--r--yt/yt/client/queue_client/private.h13
-rw-r--r--yt/yt/client/queue_client/public.h29
-rw-r--r--yt/yt/client/queue_client/queue_rowset.cpp110
-rw-r--r--yt/yt/client/queue_client/queue_rowset.h48
-rw-r--r--yt/yt/client/scheduler/operation_cache.cpp40
-rw-r--r--yt/yt/client/scheduler/operation_cache.h39
-rw-r--r--yt/yt/client/scheduler/operation_id_or_alias-inl.h57
-rw-r--r--yt/yt/client/scheduler/operation_id_or_alias.cpp62
-rw-r--r--yt/yt/client/scheduler/operation_id_or_alias.h46
-rw-r--r--yt/yt/client/scheduler/private.h16
-rw-r--r--yt/yt/client/scheduler/public.h159
-rw-r--r--yt/yt/client/security_client/access_control.cpp64
-rw-r--r--yt/yt/client/security_client/access_control.h61
-rw-r--r--yt/yt/client/security_client/acl.cpp201
-rw-r--r--yt/yt/client/security_client/acl.h60
-rw-r--r--yt/yt/client/security_client/helpers.cpp81
-rw-r--r--yt/yt/client/security_client/helpers.h31
-rw-r--r--yt/yt/client/security_client/public.cpp37
-rw-r--r--yt/yt/client/security_client/public.h99
-rw-r--r--yt/yt/client/table_client/adapters.cpp234
-rw-r--r--yt/yt/client/table_client/adapters.h56
-rw-r--r--yt/yt/client/table_client/blob_reader.cpp219
-rw-r--r--yt/yt/client/table_client/blob_reader.h36
-rw-r--r--yt/yt/client/table_client/check_schema_compatibility.cpp235
-rw-r--r--yt/yt/client/table_client/check_schema_compatibility.h22
-rw-r--r--yt/yt/client/table_client/chunk_stripe_statistics.cpp68
-rw-r--r--yt/yt/client/table_client/chunk_stripe_statistics.h39
-rw-r--r--yt/yt/client/table_client/column_rename_descriptor.cpp53
-rw-r--r--yt/yt/client/table_client/column_rename_descriptor.h27
-rw-r--r--yt/yt/client/table_client/column_sort_schema.cpp153
-rw-r--r--yt/yt/client/table_client/column_sort_schema.h52
-rw-r--r--yt/yt/client/table_client/columnar-inl.h378
-rw-r--r--yt/yt/client/table_client/columnar.cpp838
-rw-r--r--yt/yt/client/table_client/columnar.h247
-rw-r--r--yt/yt/client/table_client/columnar_statistics.cpp326
-rw-r--r--yt/yt/client/table_client/columnar_statistics.h95
-rw-r--r--yt/yt/client/table_client/comparator-inl.h58
-rw-r--r--yt/yt/client/table_client/comparator.cpp509
-rw-r--r--yt/yt/client/table_client/comparator.h180
-rw-r--r--yt/yt/client/table_client/composite_compare.cpp279
-rw-r--r--yt/yt/client/table_client/composite_compare.h18
-rw-r--r--yt/yt/client/table_client/config.cpp414
-rw-r--r--yt/yt/client/table_client/config.h381
-rw-r--r--yt/yt/client/table_client/helpers-inl.h563
-rw-r--r--yt/yt/client/table_client/helpers.cpp1570
-rw-r--r--yt/yt/client/table_client/helpers.h349
-rw-r--r--yt/yt/client/table_client/key.cpp190
-rw-r--r--yt/yt/client/table_client/key.h84
-rw-r--r--yt/yt/client/table_client/key_bound.cpp530
-rw-r--r--yt/yt/client/table_client/key_bound.h190
-rw-r--r--yt/yt/client/table_client/key_bound_compressor.cpp164
-rw-r--r--yt/yt/client/table_client/key_bound_compressor.h73
-rw-r--r--yt/yt/client/table_client/logical_type-inl.h145
-rw-r--r--yt/yt/client/table_client/logical_type.cpp2214
-rw-r--r--yt/yt/client/table_client/logical_type.h447
-rw-r--r--yt/yt/client/table_client/name_table.cpp304
-rw-r--r--yt/yt/client/table_client/name_table.h113
-rw-r--r--yt/yt/client/table_client/pipe.cpp292
-rw-r--r--yt/yt/client/table_client/pipe.h52
-rw-r--r--yt/yt/client/table_client/public.cpp19
-rw-r--r--yt/yt/client/table_client/public.h402
-rw-r--r--yt/yt/client/table_client/record_codegen_cpp.cpp36
-rw-r--r--yt/yt/client/table_client/record_codegen_cpp.h24
-rw-r--r--yt/yt/client/table_client/record_helpers-inl.h166
-rw-r--r--yt/yt/client/table_client/record_helpers.cpp20
-rw-r--r--yt/yt/client/table_client/record_helpers.h91
-rw-r--r--yt/yt/client/table_client/row_base.cpp189
-rw-r--r--yt/yt/client/table_client/row_base.h458
-rw-r--r--yt/yt/client/table_client/row_batch-inl.h70
-rw-r--r--yt/yt/client/table_client/row_batch.cpp144
-rw-r--r--yt/yt/client/table_client/row_batch.h269
-rw-r--r--yt/yt/client/table_client/row_buffer.cpp310
-rw-r--r--yt/yt/client/table_client/row_buffer.h114
-rw-r--r--yt/yt/client/table_client/schema-inl.h227
-rw-r--r--yt/yt/client/table_client/schema.cpp2002
-rw-r--r--yt/yt/client/table_client/schema.h604
-rw-r--r--yt/yt/client/table_client/schema_serialization_helpers.cpp343
-rw-r--r--yt/yt/client/table_client/schema_serialization_helpers.h51
-rw-r--r--yt/yt/client/table_client/schemaless_row_reorderer.cpp86
-rw-r--r--yt/yt/client/table_client/schemaless_row_reorderer.h39
-rw-r--r--yt/yt/client/table_client/serialize.cpp16
-rw-r--r--yt/yt/client/table_client/serialize.h35
-rw-r--r--yt/yt/client/table_client/table_consumer.cpp601
-rw-r--r--yt/yt/client/table_client/table_consumer.h159
-rw-r--r--yt/yt/client/table_client/table_output.cpp36
-rw-r--r--yt/yt/client/table_client/table_output.h29
-rw-r--r--yt/yt/client/table_client/unittests/columnar_statistics_ut.cpp634
-rw-r--r--yt/yt/client/table_client/unittests/columnar_ut.cpp717
-rw-r--r--yt/yt/client/table_client/unittests/helpers/helpers.cpp282
-rw-r--r--yt/yt/client/table_client/unittests/helpers/helpers.h232
-rw-r--r--yt/yt/client/table_client/unittests/helpers/ya.make14
-rw-r--r--yt/yt/client/table_client/unittests/serialization_ut.cpp71
-rw-r--r--yt/yt/client/table_client/unittests/ya.make25
-rw-r--r--yt/yt/client/table_client/unordered_schemaful_reader.cpp312
-rw-r--r--yt/yt/client/table_client/unordered_schemaful_reader.h26
-rw-r--r--yt/yt/client/table_client/unversioned_reader.h41
-rw-r--r--yt/yt/client/table_client/unversioned_row.cpp2136
-rw-r--r--yt/yt/client/table_client/unversioned_row.h973
-rw-r--r--yt/yt/client/table_client/unversioned_value.cpp239
-rw-r--r--yt/yt/client/table_client/unversioned_value.h117
-rw-r--r--yt/yt/client/table_client/unversioned_writer.h53
-rw-r--r--yt/yt/client/table_client/validate_logical_type-inl.h209
-rw-r--r--yt/yt/client/table_client/validate_logical_type.cpp635
-rw-r--r--yt/yt/client/table_client/validate_logical_type.h40
-rw-r--r--yt/yt/client/table_client/value_consumer.cpp388
-rw-r--r--yt/yt/client/table_client/value_consumer.h155
-rw-r--r--yt/yt/client/table_client/versioned_reader.cpp82
-rw-r--r--yt/yt/client/table_client/versioned_reader.h63
-rw-r--r--yt/yt/client/table_client/versioned_row.cpp602
-rw-r--r--yt/yt/client/table_client/versioned_row.h666
-rw-r--r--yt/yt/client/table_client/versioned_writer.h37
-rw-r--r--yt/yt/client/table_client/wire_protocol.cpp1291
-rw-r--r--yt/yt/client/table_client/wire_protocol.h323
-rw-r--r--yt/yt/client/tablet_client/config.cpp122
-rw-r--r--yt/yt/client/tablet_client/config.h118
-rw-r--r--yt/yt/client/tablet_client/helpers.cpp24
-rw-r--r--yt/yt/client/tablet_client/helpers.h14
-rw-r--r--yt/yt/client/tablet_client/public.cpp25
-rw-r--r--yt/yt/client/tablet_client/public.h224
-rw-r--r--yt/yt/client/tablet_client/table_mount_cache.cpp182
-rw-r--r--yt/yt/client/tablet_client/table_mount_cache.h176
-rw-r--r--yt/yt/client/tablet_client/table_mount_cache_detail.cpp266
-rw-r--r--yt/yt/client/tablet_client/table_mount_cache_detail.h80
-rw-r--r--yt/yt/client/transaction_client/batching_timestamp_provider.cpp170
-rw-r--r--yt/yt/client/transaction_client/batching_timestamp_provider.h17
-rw-r--r--yt/yt/client/transaction_client/config.cpp34
-rw-r--r--yt/yt/client/transaction_client/config.h41
-rw-r--r--yt/yt/client/transaction_client/helpers.cpp101
-rw-r--r--yt/yt/client/transaction_client/helpers.h63
-rw-r--r--yt/yt/client/transaction_client/noop_timestamp_provider.cpp34
-rw-r--r--yt/yt/client/transaction_client/noop_timestamp_provider.h13
-rw-r--r--yt/yt/client/transaction_client/private.h13
-rw-r--r--yt/yt/client/transaction_client/public.h102
-rw-r--r--yt/yt/client/transaction_client/remote_timestamp_provider.cpp111
-rw-r--r--yt/yt/client/transaction_client/remote_timestamp_provider.h34
-rw-r--r--yt/yt/client/transaction_client/timestamp_provider.h32
-rw-r--r--yt/yt/client/transaction_client/timestamp_provider_base.cpp101
-rw-r--r--yt/yt/client/transaction_client/timestamp_provider_base.h41
-rw-r--r--yt/yt/client/transaction_client/timestamp_service_proxy.h26
-rw-r--r--yt/yt/client/unittests/check_schema_compatibility_ut.cpp349
-rw-r--r--yt/yt/client/unittests/check_type_compatibility_ut.cpp477
-rw-r--r--yt/yt/client/unittests/chunk_replica_ut.cpp47
-rw-r--r--yt/yt/client/unittests/column_sort_schema_ut.cpp70
-rw-r--r--yt/yt/client/unittests/comparator_ut.cpp365
-rw-r--r--yt/yt/client/unittests/composite_compare_ut.cpp76
-rw-r--r--yt/yt/client/unittests/connection_ut.cpp50
-rw-r--r--yt/yt/client/unittests/dsv_parser_ut.cpp365
-rw-r--r--yt/yt/client/unittests/dsv_writer_ut.cpp316
-rw-r--r--yt/yt/client/unittests/farm_fingerprint_stability_ut.cpp99
-rw-r--r--yt/yt/client/unittests/format_writer_ut.h36
-rw-r--r--yt/yt/client/unittests/key_bound_compressor_ut.cpp88
-rw-r--r--yt/yt/client/unittests/key_bound_ut.cpp258
-rw-r--r--yt/yt/client/unittests/key_helpers.cpp28
-rw-r--r--yt/yt/client/unittests/key_helpers.h17
-rw-r--r--yt/yt/client/unittests/key_ut.cpp63
-rw-r--r--yt/yt/client/unittests/logical_type_shortcuts.h135
-rw-r--r--yt/yt/client/unittests/logical_type_ut.cpp1059
-rw-r--r--yt/yt/client/unittests/mock/client.h617
-rw-r--r--yt/yt/client/unittests/mock/connection.h44
-rw-r--r--yt/yt/client/unittests/mock/table_value_consumer.h49
-rw-r--r--yt/yt/client/unittests/mock/transaction.h209
-rw-r--r--yt/yt/client/unittests/mock/ya.make18
-rw-r--r--yt/yt/client/unittests/named_yson_token_ut.cpp301
-rw-r--r--yt/yt/client/unittests/node_directory_ut.cpp33
-rw-r--r--yt/yt/client/unittests/protobuf_format_ut.cpp4657
-rw-r--r--yt/yt/client/unittests/protobuf_format_ut.proto255
-rw-r--r--yt/yt/client/unittests/query_builder_ut.cpp48
-rw-r--r--yt/yt/client/unittests/read_limit_ut.cpp196
-rw-r--r--yt/yt/client/unittests/replication_progress_ut.cpp410
-rw-r--r--yt/yt/client/unittests/row_helpers.cpp70
-rw-r--r--yt/yt/client/unittests/row_helpers.h111
-rw-r--r--yt/yt/client/unittests/row_ut.cpp165
-rw-r--r--yt/yt/client/unittests/schema_ut.cpp506
-rw-r--r--yt/yt/client/unittests/schemaful_dsv_parser_ut.cpp259
-rw-r--r--yt/yt/client/unittests/schemaful_dsv_writer_ut.cpp344
-rw-r--r--yt/yt/client/unittests/skiff_format_ut.cpp3006
-rw-r--r--yt/yt/client/unittests/skiff_yson_converter_ut.cpp728
-rw-r--r--yt/yt/client/unittests/table_consumer_ut.cpp131
-rw-r--r--yt/yt/client/unittests/time_text_ut.cpp67
-rw-r--r--yt/yt/client/unittests/unordered_reader_ut.cpp104
-rw-r--r--yt/yt/client/unittests/unversioned_row_ut.cpp253
-rw-r--r--yt/yt/client/unittests/uuid_text_ut.cpp66
-rw-r--r--yt/yt/client/unittests/validate_logical_type_ut.cpp467
-rw-r--r--yt/yt/client/unittests/value_examples.cpp147
-rw-r--r--yt/yt/client/unittests/value_examples.h24
-rw-r--r--yt/yt/client/unittests/web_json_writer_ut.cpp1570
-rw-r--r--yt/yt/client/unittests/wire_protocol_ut.cpp285
-rw-r--r--yt/yt/client/unittests/ya.make82
-rw-r--r--yt/yt/client/unittests/yamr_parser_ut.cpp606
-rw-r--r--yt/yt/client/unittests/yamr_writer_ut.cpp644
-rw-r--r--yt/yt/client/unittests/yamred_dsv_parser_ut.cpp187
-rw-r--r--yt/yt/client/unittests/yamred_dsv_writer_ut.cpp425
-rw-r--r--yt/yt/client/unittests/ypath_ut.cpp957
-rw-r--r--yt/yt/client/unittests/yson_helpers.cpp29
-rw-r--r--yt/yt/client/unittests/yson_helpers.h13
-rw-r--r--yt/yt/client/unittests/zookeeper_bus_ut.cpp165
-rw-r--r--yt/yt/client/unittests/zookeeper_protocol_ut.cpp106
-rw-r--r--yt/yt/client/ya.make221
-rw-r--r--yt/yt/client/ypath/parser_detail.cpp460
-rw-r--r--yt/yt/client/ypath/parser_detail.h13
-rw-r--r--yt/yt/client/ypath/public.h21
-rw-r--r--yt/yt/client/ypath/rich.cpp741
-rw-r--r--yt/yt/client/ypath/rich.h194
-rw-r--r--yt/yt/client/zookeeper/packet.cpp299
-rw-r--r--yt/yt/client/zookeeper/packet.h15
-rw-r--r--yt/yt/client/zookeeper/protocol.cpp204
-rw-r--r--yt/yt/client/zookeeper/protocol.h59
-rw-r--r--yt/yt/client/zookeeper/public.h19
-rw-r--r--yt/yt/client/zookeeper/requests.cpp28
-rw-r--r--yt/yt/client/zookeeper/requests.h42
-rw-r--r--yt/yt/core/misc/copyable_atomic.h55
-rw-r--r--yt/yt/library/CMakeLists.darwin-x86_64.txt14
-rw-r--r--yt/yt/library/CMakeLists.linux-aarch64.txt20
-rw-r--r--yt/yt/library/CMakeLists.linux-x86_64.txt20
-rw-r--r--yt/yt/library/CMakeLists.txt15
-rw-r--r--yt/yt/library/CMakeLists.windows-x86_64.txt14
-rw-r--r--yt/yt/library/auth/CMakeLists.linux-aarch64.txt25
-rw-r--r--yt/yt/library/auth/CMakeLists.linux-x86_64.txt25
-rw-r--r--yt/yt/library/auth/CMakeLists.txt13
-rw-r--r--yt/yt/library/auth/auth.cpp64
-rw-r--r--yt/yt/library/auth/auth.h27
-rw-r--r--yt/yt/library/auth/authentication_options.cpp55
-rw-r--r--yt/yt/library/auth/authentication_options.h43
-rw-r--r--yt/yt/library/auth/credentials_injecting_channel.cpp251
-rw-r--r--yt/yt/library/auth/credentials_injecting_channel.h47
-rw-r--r--yt/yt/library/auth/public.h13
-rw-r--r--yt/yt/library/auth/unittests/auth_ut.cpp101
-rw-r--r--yt/yt/library/auth/unittests/ya.make22
-rw-r--r--yt/yt/library/auth/ya.make20
-rw-r--r--yt/yt/library/codegen/caller.h54
-rw-r--r--yt/yt/library/decimal/CMakeLists.linux-aarch64.txt23
-rw-r--r--yt/yt/library/decimal/CMakeLists.linux-x86_64.txt23
-rw-r--r--yt/yt/library/decimal/CMakeLists.txt13
-rw-r--r--yt/yt/library/decimal/decimal.cpp571
-rw-r--r--yt/yt/library/decimal/decimal.h60
-rw-r--r--yt/yt/library/decimal/unittests/decimal_ut.cpp299
-rw-r--r--yt/yt/library/decimal/unittests/ya.make18
-rw-r--r--yt/yt/library/decimal/ya.make18
-rw-r--r--yt/yt/library/erasure/CMakeLists.linux-aarch64.txt22
-rw-r--r--yt/yt/library/erasure/CMakeLists.linux-x86_64.txt22
-rw-r--r--yt/yt/library/erasure/CMakeLists.txt13
-rw-r--r--yt/yt/library/erasure/impl/codec.cpp81
-rw-r--r--yt/yt/library/erasure/impl/codec.h92
-rw-r--r--yt/yt/library/erasure/impl/codec_detail.cpp33
-rw-r--r--yt/yt/library/erasure/impl/codec_detail.h100
-rw-r--r--yt/yt/library/erasure/impl/codec_opensource.cpp39
-rw-r--r--yt/yt/library/erasure/impl/public.h18
-rw-r--r--yt/yt/library/erasure/impl/unittests/erasure_stability_ut.cpp77
-rw-r--r--yt/yt/library/erasure/impl/unittests/ya.make19
-rw-r--r--yt/yt/library/erasure/impl/ya.make26
-rw-r--r--yt/yt/library/erasure/public.cpp10
-rw-r--r--yt/yt/library/erasure/public.h26
-rw-r--r--yt/yt/library/erasure/ya.make17
-rw-r--r--yt/yt/library/named_value/README.md3
-rw-r--r--yt/yt/library/named_value/named_value.cpp126
-rw-r--r--yt/yt/library/named_value/named_value.h103
-rw-r--r--yt/yt/library/named_value/ya.make13
-rw-r--r--yt/yt/library/numeric/CMakeLists.linux-aarch64.txt22
-rw-r--r--yt/yt/library/numeric/CMakeLists.linux-x86_64.txt22
-rw-r--r--yt/yt/library/numeric/CMakeLists.txt13
-rw-r--r--yt/yt/library/numeric/binary_search-inl.h155
-rw-r--r--yt/yt/library/numeric/binary_search.h66
-rw-r--r--yt/yt/library/numeric/double_array.h420
-rw-r--r--yt/yt/library/numeric/double_array_format.h22
-rw-r--r--yt/yt/library/numeric/piecewise_linear_function-inl.h1133
-rw-r--r--yt/yt/library/numeric/piecewise_linear_function-test.h36
-rw-r--r--yt/yt/library/numeric/piecewise_linear_function.cpp117
-rw-r--r--yt/yt/library/numeric/piecewise_linear_function.h447
-rw-r--r--yt/yt/library/numeric/serialize/double_array.h41
-rw-r--r--yt/yt/library/numeric/serialize/unittests/double_array_ut.cpp43
-rw-r--r--yt/yt/library/numeric/serialize/unittests/ya.make20
-rw-r--r--yt/yt/library/numeric/serialize/ya.make15
-rw-r--r--yt/yt/library/numeric/unittests/binary_search_ut.cpp332
-rw-r--r--yt/yt/library/numeric/unittests/double_array_ut.cpp94
-rw-r--r--yt/yt/library/numeric/unittests/piecewise_linear_function_ut.cpp1370
-rw-r--r--yt/yt/library/numeric/unittests/util_ut.cpp105
-rw-r--r--yt/yt/library/numeric/unittests/ya.make23
-rw-r--r--yt/yt/library/numeric/util.h51
-rw-r--r--yt/yt/library/numeric/vector_format.h26
-rw-r--r--yt/yt/library/numeric/ya.make37
-rw-r--r--yt/yt/library/quantile_digest/CMakeLists.linux-aarch64.txt55
-rw-r--r--yt/yt/library/quantile_digest/CMakeLists.linux-x86_64.txt55
-rw-r--r--yt/yt/library/quantile_digest/CMakeLists.txt13
-rw-r--r--yt/yt/library/quantile_digest/config.cpp17
-rw-r--r--yt/yt/library/quantile_digest/config.h27
-rw-r--r--yt/yt/library/quantile_digest/proto/quantile_digest.proto20
-rw-r--r--yt/yt/library/quantile_digest/public.h16
-rw-r--r--yt/yt/library/quantile_digest/quantile_digest.cpp88
-rw-r--r--yt/yt/library/quantile_digest/quantile_digest.h35
-rw-r--r--yt/yt/library/quantile_digest/ya.make18
-rw-r--r--yt/yt/library/re2/CMakeLists.linux-aarch64.txt23
-rw-r--r--yt/yt/library/re2/CMakeLists.linux-x86_64.txt23
-rw-r--r--yt/yt/library/re2/CMakeLists.txt13
-rw-r--r--yt/yt/library/re2/public.h13
-rw-r--r--yt/yt/library/re2/re2.cpp57
-rw-r--r--yt/yt/library/re2/re2.h37
-rw-r--r--yt/yt/library/re2/ya.make14
-rw-r--r--yt/yt/library/skiff_ext/parser-inl.h224
-rw-r--r--yt/yt/library/skiff_ext/parser.h45
-rw-r--r--yt/yt/library/skiff_ext/public.h22
-rw-r--r--yt/yt/library/skiff_ext/schema_match.cpp368
-rw-r--r--yt/yt/library/skiff_ext/schema_match.h88
-rw-r--r--yt/yt/library/skiff_ext/serialize.cpp38
-rw-r--r--yt/yt/library/skiff_ext/serialize.h21
-rw-r--r--yt/yt/library/skiff_ext/ya.make16
-rw-r--r--yt/yt_proto/yt/CMakeLists.darwin-x86_64.txt10
-rw-r--r--yt/yt_proto/yt/CMakeLists.linux-aarch64.txt11
-rw-r--r--yt/yt_proto/yt/CMakeLists.linux-x86_64.txt11
-rw-r--r--yt/yt_proto/yt/CMakeLists.txt11
-rw-r--r--yt/yt_proto/yt/CMakeLists.windows-x86_64.txt10
-rw-r--r--yt/yt_proto/yt/client/CMakeLists.linux-aarch64.txt297
-rw-r--r--yt/yt_proto/yt/client/CMakeLists.linux-x86_64.txt297
-rw-r--r--yt/yt_proto/yt/client/CMakeLists.txt13
-rw-r--r--yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.proto2774
-rw-r--r--yt/yt_proto/yt/client/api/rpc_proxy/proto/discovery_service.proto32
-rw-r--r--yt/yt_proto/yt/client/cell_master/proto/cell_directory.proto19
-rw-r--r--yt/yt_proto/yt/client/chaos_client/proto/replication_card.proto73
-rw-r--r--yt/yt_proto/yt/client/chunk_client/proto/chunk_meta.proto166
-rw-r--r--yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.proto90
-rw-r--r--yt/yt_proto/yt/client/chunk_client/proto/confirm_chunk_replica_info.proto15
-rw-r--r--yt/yt_proto/yt/client/chunk_client/proto/data_statistics.proto23
-rw-r--r--yt/yt_proto/yt/client/chunk_client/proto/read_limit.proto35
-rw-r--r--yt/yt_proto/yt/client/discovery_client/proto/discovery_client_service.proto60
-rw-r--r--yt/yt_proto/yt/client/hedging/proto/config.proto75
-rw-r--r--yt/yt_proto/yt/client/hedging/ya.make11
-rw-r--r--yt/yt_proto/yt/client/hive/proto/cluster_directory.proto18
-rw-r--r--yt/yt_proto/yt/client/hive/proto/timestamp_map.proto13
-rw-r--r--yt/yt_proto/yt/client/misc/proto/workload.proto23
-rw-r--r--yt/yt_proto/yt/client/node_tracker_client/proto/node.proto243
-rw-r--r--yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.proto20
-rw-r--r--yt/yt_proto/yt/client/query_client/proto/query_statistics.proto25
-rw-r--r--yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.proto327
-rw-r--r--yt/yt_proto/yt/client/table_chunk_format/proto/column_meta.proto77
-rw-r--r--yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.proto20
-rw-r--r--yt/yt_proto/yt/client/transaction_client/proto/timestamp_service.proto16
-rw-r--r--yt/yt_proto/yt/client/ya.make48
904 files changed, 166452 insertions, 11 deletions
diff --git a/contrib/libs/CMakeLists.linux-aarch64.txt b/contrib/libs/CMakeLists.linux-aarch64.txt
index 988f4f0d75..358f97add0 100644
--- a/contrib/libs/CMakeLists.linux-aarch64.txt
+++ b/contrib/libs/CMakeLists.linux-aarch64.txt
@@ -50,6 +50,7 @@ add_subdirectory(openldap)
add_subdirectory(opentelemetry-proto)
add_subdirectory(pcre)
add_subdirectory(pdqsort)
+add_subdirectory(pfr)
add_subdirectory(poco)
add_subdirectory(protobuf)
add_subdirectory(protoc)
diff --git a/contrib/libs/CMakeLists.linux-x86_64.txt b/contrib/libs/CMakeLists.linux-x86_64.txt
index f0b532ac16..ac47fb1732 100644
--- a/contrib/libs/CMakeLists.linux-x86_64.txt
+++ b/contrib/libs/CMakeLists.linux-x86_64.txt
@@ -51,6 +51,7 @@ add_subdirectory(openldap)
add_subdirectory(opentelemetry-proto)
add_subdirectory(pcre)
add_subdirectory(pdqsort)
+add_subdirectory(pfr)
add_subdirectory(poco)
add_subdirectory(protobuf)
add_subdirectory(protoc)
diff --git a/contrib/libs/isa-l/erasure_code/ec_base.c b/contrib/libs/isa-l/erasure_code/ec_base.c
new file mode 100644
index 0000000000..9a8fbc759e
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/ec_base.c
@@ -0,0 +1,371 @@
+/**********************************************************************
+ Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**********************************************************************/
+
+#include <limits.h>
+#include <string.h> // for memset
+#include "erasure_code.h"
+#include "ec_base.h" // for GF tables
+
+void ec_init_tables(int k, int rows, unsigned char *a, unsigned char *g_tbls)
+{
+ int i, j;
+
+ for (i = 0; i < rows; i++) {
+ for (j = 0; j < k; j++) {
+ gf_vect_mul_init(*a++, g_tbls);
+ g_tbls += 32;
+ }
+ }
+}
+
+unsigned char gf_mul_erasure(unsigned char a, unsigned char b)
+{
+#ifndef GF_LARGE_TABLES
+ int i;
+
+ if ((a == 0) || (b == 0))
+ return 0;
+
+ return gff_base[(i = gflog_base[a] + gflog_base[b]) > 254 ? i - 255 : i];
+#else
+ return gf_mul_table_base[b * 256 + a];
+#endif
+}
+
+unsigned char gf_inv(unsigned char a)
+{
+#ifndef GF_LARGE_TABLES
+ if (a == 0)
+ return 0;
+
+ return gff_base[255 - gflog_base[a]];
+#else
+ return gf_inv_table_base[a];
+#endif
+}
+
+void gf_gen_rs_matrix(unsigned char *a, int m, int k)
+{
+ int i, j;
+ unsigned char p, gen = 1;
+
+ memset(a, 0, k * m);
+ for (i = 0; i < k; i++)
+ a[k * i + i] = 1;
+
+ for (i = k; i < m; i++) {
+ p = 1;
+ for (j = 0; j < k; j++) {
+ a[k * i + j] = p;
+ p = gf_mul_erasure(p, gen);
+ }
+ gen = gf_mul_erasure(gen, 2);
+ }
+}
+
+void gf_gen_cauchy1_matrix(unsigned char *a, int m, int k)
+{
+ int i, j;
+ unsigned char *p;
+
+ // Identity matrix in high position
+ memset(a, 0, k * m);
+ for (i = 0; i < k; i++)
+ a[k * i + i] = 1;
+
+ // For the rest choose 1/(i + j) | i != j
+ p = &a[k * k];
+ for (i = k; i < m; i++)
+ for (j = 0; j < k; j++)
+ *p++ = gf_inv(i ^ j);
+
+}
+
+int gf_invert_matrix(unsigned char *in_mat, unsigned char *out_mat, const int n)
+{
+ int i, j, k;
+ unsigned char temp;
+
+ // Set out_mat[] to the identity matrix
+ for (i = 0; i < n * n; i++) // memset(out_mat, 0, n*n)
+ out_mat[i] = 0;
+
+ for (i = 0; i < n; i++)
+ out_mat[i * n + i] = 1;
+
+ // Inverse
+ for (i = 0; i < n; i++) {
+ // Check for 0 in pivot element
+ if (in_mat[i * n + i] == 0) {
+ // Find a row with non-zero in current column and swap
+ for (j = i + 1; j < n; j++)
+ if (in_mat[j * n + i])
+ break;
+
+ if (j == n) // Couldn't find means it's singular
+ return -1;
+
+ for (k = 0; k < n; k++) { // Swap rows i,j
+ temp = in_mat[i * n + k];
+ in_mat[i * n + k] = in_mat[j * n + k];
+ in_mat[j * n + k] = temp;
+
+ temp = out_mat[i * n + k];
+ out_mat[i * n + k] = out_mat[j * n + k];
+ out_mat[j * n + k] = temp;
+ }
+ }
+
+ temp = gf_inv(in_mat[i * n + i]); // 1/pivot
+ for (j = 0; j < n; j++) { // Scale row i by 1/pivot
+ in_mat[i * n + j] = gf_mul_erasure(in_mat[i * n + j], temp);
+ out_mat[i * n + j] = gf_mul_erasure(out_mat[i * n + j], temp);
+ }
+
+ for (j = 0; j < n; j++) {
+ if (j == i)
+ continue;
+
+ temp = in_mat[j * n + i];
+ for (k = 0; k < n; k++) {
+ out_mat[j * n + k] ^= gf_mul_erasure(temp, out_mat[i * n + k]);
+ in_mat[j * n + k] ^= gf_mul_erasure(temp, in_mat[i * n + k]);
+ }
+ }
+ }
+ return 0;
+}
+
+// Calculates const table gftbl in GF(2^8) from single input A
+// gftbl(A) = {A{00}, A{01}, A{02}, ... , A{0f} }, {A{00}, A{10}, A{20}, ... , A{f0} }
+
+void gf_vect_mul_init(unsigned char c, unsigned char *tbl)
+{
+ unsigned char c2 = (c << 1) ^ ((c & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ unsigned char c4 = (c2 << 1) ^ ((c2 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ unsigned char c8 = (c4 << 1) ^ ((c4 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+
+#if __WORDSIZE == 64 || _WIN64 || __x86_64__
+ unsigned long long v1, v2, v4, v8, *t;
+ unsigned long long v10, v20, v40, v80;
+ unsigned char c17, c18, c20, c24;
+
+ t = (unsigned long long *)tbl;
+
+ v1 = c * 0x0100010001000100ull;
+ v2 = c2 * 0x0101000001010000ull;
+ v4 = c4 * 0x0101010100000000ull;
+ v8 = c8 * 0x0101010101010101ull;
+
+ v4 = v1 ^ v2 ^ v4;
+ t[0] = v4;
+ t[1] = v8 ^ v4;
+
+ c17 = (c8 << 1) ^ ((c8 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ c18 = (c17 << 1) ^ ((c17 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ c20 = (c18 << 1) ^ ((c18 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ c24 = (c20 << 1) ^ ((c20 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+
+ v10 = c17 * 0x0100010001000100ull;
+ v20 = c18 * 0x0101000001010000ull;
+ v40 = c20 * 0x0101010100000000ull;
+ v80 = c24 * 0x0101010101010101ull;
+
+ v40 = v10 ^ v20 ^ v40;
+ t[2] = v40;
+ t[3] = v80 ^ v40;
+
+#else // 32-bit or other
+ unsigned char c3, c5, c6, c7, c9, c10, c11, c12, c13, c14, c15;
+ unsigned char c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, c30,
+ c31;
+
+ c3 = c2 ^ c;
+ c5 = c4 ^ c;
+ c6 = c4 ^ c2;
+ c7 = c4 ^ c3;
+
+ c9 = c8 ^ c;
+ c10 = c8 ^ c2;
+ c11 = c8 ^ c3;
+ c12 = c8 ^ c4;
+ c13 = c8 ^ c5;
+ c14 = c8 ^ c6;
+ c15 = c8 ^ c7;
+
+ tbl[0] = 0;
+ tbl[1] = c;
+ tbl[2] = c2;
+ tbl[3] = c3;
+ tbl[4] = c4;
+ tbl[5] = c5;
+ tbl[6] = c6;
+ tbl[7] = c7;
+ tbl[8] = c8;
+ tbl[9] = c9;
+ tbl[10] = c10;
+ tbl[11] = c11;
+ tbl[12] = c12;
+ tbl[13] = c13;
+ tbl[14] = c14;
+ tbl[15] = c15;
+
+ c17 = (c8 << 1) ^ ((c8 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ c18 = (c17 << 1) ^ ((c17 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ c19 = c18 ^ c17;
+ c20 = (c18 << 1) ^ ((c18 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ c21 = c20 ^ c17;
+ c22 = c20 ^ c18;
+ c23 = c20 ^ c19;
+ c24 = (c20 << 1) ^ ((c20 & 0x80) ? 0x1d : 0); //Mult by GF{2}
+ c25 = c24 ^ c17;
+ c26 = c24 ^ c18;
+ c27 = c24 ^ c19;
+ c28 = c24 ^ c20;
+ c29 = c24 ^ c21;
+ c30 = c24 ^ c22;
+ c31 = c24 ^ c23;
+
+ tbl[16] = 0;
+ tbl[17] = c17;
+ tbl[18] = c18;
+ tbl[19] = c19;
+ tbl[20] = c20;
+ tbl[21] = c21;
+ tbl[22] = c22;
+ tbl[23] = c23;
+ tbl[24] = c24;
+ tbl[25] = c25;
+ tbl[26] = c26;
+ tbl[27] = c27;
+ tbl[28] = c28;
+ tbl[29] = c29;
+ tbl[30] = c30;
+ tbl[31] = c31;
+
+#endif //__WORDSIZE == 64 || _WIN64 || __x86_64__
+}
+
+void gf_vect_dot_prod_base(int len, int vlen, unsigned char *v,
+ unsigned char **src, unsigned char *dest)
+{
+ int i, j;
+ unsigned char s;
+ for (i = 0; i < len; i++) {
+ s = 0;
+ for (j = 0; j < vlen; j++)
+ s ^= gf_mul_erasure(src[j][i], v[j * 32 + 1]);
+
+ dest[i] = s;
+ }
+}
+
+void gf_vect_mad_base(int len, int vec, int vec_i,
+ unsigned char *v, unsigned char *src, unsigned char *dest)
+{
+ int i;
+ unsigned char s;
+ for (i = 0; i < len; i++) {
+ s = dest[i];
+ s ^= gf_mul_erasure(src[i], v[vec_i * 32 + 1]);
+ dest[i] = s;
+ }
+}
+
+void ec_encode_data_base(int len, int srcs, int dests, unsigned char *v,
+ unsigned char **src, unsigned char **dest)
+{
+ int i, j, l;
+ unsigned char s;
+
+ for (l = 0; l < dests; l++) {
+ for (i = 0; i < len; i++) {
+ s = 0;
+ for (j = 0; j < srcs; j++)
+ s ^= gf_mul_erasure(src[j][i], v[j * 32 + l * srcs * 32 + 1]);
+
+ dest[l][i] = s;
+ }
+ }
+}
+
+void ec_encode_data_update_base(int len, int k, int rows, int vec_i, unsigned char *v,
+ unsigned char *data, unsigned char **dest)
+{
+ int i, l;
+ unsigned char s;
+
+ for (l = 0; l < rows; l++) {
+ for (i = 0; i < len; i++) {
+ s = dest[l][i];
+ s ^= gf_mul_erasure(data[i], v[vec_i * 32 + l * k * 32 + 1]);
+
+ dest[l][i] = s;
+ }
+ }
+}
+
+void gf_vect_mul_base(int len, unsigned char *a, unsigned char *src, unsigned char *dest)
+{
+ //2nd element of table array is ref value used to fill it in
+ unsigned char c = a[1];
+ while (len-- > 0)
+ *dest++ = gf_mul_erasure(c, *src++);
+}
+
+struct slver {
+ unsigned short snum;
+ unsigned char ver;
+ unsigned char core;
+};
+
+// Version info
+struct slver gf_vect_mul_init_slver_00020035;
+struct slver gf_vect_mul_init_slver = { 0x0035, 0x02, 0x00 };
+
+struct slver ec_encode_data_base_slver_00010135;
+struct slver ec_encode_data_base_slver = { 0x0135, 0x01, 0x00 };
+
+struct slver gf_vect_mul_base_slver_00010136;
+struct slver gf_vect_mul_base_slver = { 0x0136, 0x01, 0x00 };
+
+struct slver gf_vect_dot_prod_base_slver_00010137;
+struct slver gf_vect_dot_prod_base_slver = { 0x0137, 0x01, 0x00 };
+
+struct slver gf_mul_slver_00000214;
+struct slver gf_mul_slver = { 0x0214, 0x00, 0x00 };
+
+struct slver gf_invert_matrix_slver_00000215;
+struct slver gf_invert_matrix_slver = { 0x0215, 0x00, 0x00 };
+
+struct slver gf_gen_rs_matrix_slver_00000216;
+struct slver gf_gen_rs_matrix_slver = { 0x0216, 0x00, 0x00 };
+
+struct slver gf_gen_cauchy1_matrix_slver_00000217;
+struct slver gf_gen_cauchy1_matrix_slver = { 0x0217, 0x00, 0x00 };
diff --git a/contrib/libs/isa-l/erasure_code/ec_base.h b/contrib/libs/isa-l/erasure_code/ec_base.h
new file mode 100644
index 0000000000..070b276652
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/ec_base.h
@@ -0,0 +1,6680 @@
+/**********************************************************************
+ Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**********************************************************************/
+
+#ifndef _EC_BASE_H_
+#define _EC_BASE_H_
+
+// Global GF(256) tables
+#ifndef GF_LARGE_TABLES
+static const unsigned char gff_base[] = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a,
+ 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98, 0x2d, 0x5a,
+ 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30,
+ 0x60, 0xc0, 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35,
+ 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c,
+ 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2,
+ 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f,
+ 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,
+ 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1,
+ 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf, 0x43, 0x86,
+ 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd,
+ 0x67, 0xce, 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93,
+ 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17,
+ 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42,
+ 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4,
+ 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,
+ 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5,
+ 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb, 0xab, 0x4b,
+ 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57,
+ 0xae, 0x41, 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e,
+ 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2,
+ 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56,
+ 0xac, 0x45, 0x8a, 0x09, 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a,
+ 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,
+ 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36,
+ 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01
+};
+
+static const unsigned char gflog_base[] = {
+ 0x00, 0xff, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf,
+ 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x04, 0x64, 0xe0, 0x0e,
+ 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08,
+ 0x4c, 0x71, 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21,
+ 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d, 0xb5,
+ 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78,
+ 0x4d, 0xe4, 0x72, 0xa6, 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd,
+ 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,
+ 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2,
+ 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e, 0x42, 0xb6, 0xa3,
+ 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85,
+ 0xba, 0x3d, 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b,
+ 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x07, 0x70,
+ 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed,
+ 0x31, 0xc5, 0xfe, 0x18, 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8,
+ 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,
+ 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87,
+ 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2, 0x56, 0xd3, 0xab,
+ 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d,
+ 0x41, 0xa2, 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76,
+ 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, 0x6c, 0xa1,
+ 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1,
+ 0xbb, 0xcc, 0x3e, 0x5a, 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9,
+ 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,
+ 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6,
+ 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
+};
+#else
+static const unsigned char gf_mul_table_base[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
+ 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
+ 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53,
+ 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d,
+ 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71,
+ 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b,
+ 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85,
+ 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+ 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3,
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1,
+ 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb,
+ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5,
+ 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+ 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3,
+ 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd,
+ 0xfe, 0xff, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e,
+ 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22,
+ 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36,
+ 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a,
+ 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
+ 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72,
+ 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86,
+ 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a,
+ 0x9c, 0x9e, 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae,
+ 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 0xc0, 0xc2,
+ 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6,
+ 0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea,
+ 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
+ 0x1d, 0x1f, 0x19, 0x1b, 0x15, 0x17, 0x11, 0x13, 0x0d, 0x0f,
+ 0x09, 0x0b, 0x05, 0x07, 0x01, 0x03, 0x3d, 0x3f, 0x39, 0x3b,
+ 0x35, 0x37, 0x31, 0x33, 0x2d, 0x2f, 0x29, 0x2b, 0x25, 0x27,
+ 0x21, 0x23, 0x5d, 0x5f, 0x59, 0x5b, 0x55, 0x57, 0x51, 0x53,
+ 0x4d, 0x4f, 0x49, 0x4b, 0x45, 0x47, 0x41, 0x43, 0x7d, 0x7f,
+ 0x79, 0x7b, 0x75, 0x77, 0x71, 0x73, 0x6d, 0x6f, 0x69, 0x6b,
+ 0x65, 0x67, 0x61, 0x63, 0x9d, 0x9f, 0x99, 0x9b, 0x95, 0x97,
+ 0x91, 0x93, 0x8d, 0x8f, 0x89, 0x8b, 0x85, 0x87, 0x81, 0x83,
+ 0xbd, 0xbf, 0xb9, 0xbb, 0xb5, 0xb7, 0xb1, 0xb3, 0xad, 0xaf,
+ 0xa9, 0xab, 0xa5, 0xa7, 0xa1, 0xa3, 0xdd, 0xdf, 0xd9, 0xdb,
+ 0xd5, 0xd7, 0xd1, 0xd3, 0xcd, 0xcf, 0xc9, 0xcb, 0xc5, 0xc7,
+ 0xc1, 0xc3, 0xfd, 0xff, 0xf9, 0xfb, 0xf5, 0xf7, 0xf1, 0xf3,
+ 0xed, 0xef, 0xe9, 0xeb, 0xe5, 0xe7, 0xe1, 0xe3, 0x00, 0x03,
+ 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d,
+ 0x14, 0x17, 0x12, 0x11, 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f,
+ 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
+ 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b,
+ 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, 0x50, 0x53, 0x56, 0x55,
+ 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47,
+ 0x42, 0x41, 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9,
+ 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 0xf0, 0xf3,
+ 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed,
+ 0xe4, 0xe7, 0xe2, 0xe1, 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf,
+ 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1,
+ 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b,
+ 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 0x9d, 0x9e, 0x9b, 0x98,
+ 0x91, 0x92, 0x97, 0x94, 0x85, 0x86, 0x83, 0x80, 0x89, 0x8a,
+ 0x8f, 0x8c, 0xad, 0xae, 0xab, 0xa8, 0xa1, 0xa2, 0xa7, 0xa4,
+ 0xb5, 0xb6, 0xb3, 0xb0, 0xb9, 0xba, 0xbf, 0xbc, 0xfd, 0xfe,
+ 0xfb, 0xf8, 0xf1, 0xf2, 0xf7, 0xf4, 0xe5, 0xe6, 0xe3, 0xe0,
+ 0xe9, 0xea, 0xef, 0xec, 0xcd, 0xce, 0xcb, 0xc8, 0xc1, 0xc2,
+ 0xc7, 0xc4, 0xd5, 0xd6, 0xd3, 0xd0, 0xd9, 0xda, 0xdf, 0xdc,
+ 0x5d, 0x5e, 0x5b, 0x58, 0x51, 0x52, 0x57, 0x54, 0x45, 0x46,
+ 0x43, 0x40, 0x49, 0x4a, 0x4f, 0x4c, 0x6d, 0x6e, 0x6b, 0x68,
+ 0x61, 0x62, 0x67, 0x64, 0x75, 0x76, 0x73, 0x70, 0x79, 0x7a,
+ 0x7f, 0x7c, 0x3d, 0x3e, 0x3b, 0x38, 0x31, 0x32, 0x37, 0x34,
+ 0x25, 0x26, 0x23, 0x20, 0x29, 0x2a, 0x2f, 0x2c, 0x0d, 0x0e,
+ 0x0b, 0x08, 0x01, 0x02, 0x07, 0x04, 0x15, 0x16, 0x13, 0x10,
+ 0x19, 0x1a, 0x1f, 0x1c, 0x00, 0x04, 0x08, 0x0c, 0x10, 0x14,
+ 0x18, 0x1c, 0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38, 0x3c,
+ 0x40, 0x44, 0x48, 0x4c, 0x50, 0x54, 0x58, 0x5c, 0x60, 0x64,
+ 0x68, 0x6c, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x84, 0x88, 0x8c,
+ 0x90, 0x94, 0x98, 0x9c, 0xa0, 0xa4, 0xa8, 0xac, 0xb0, 0xb4,
+ 0xb8, 0xbc, 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
+ 0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf4, 0xf8, 0xfc, 0x1d, 0x19,
+ 0x15, 0x11, 0x0d, 0x09, 0x05, 0x01, 0x3d, 0x39, 0x35, 0x31,
+ 0x2d, 0x29, 0x25, 0x21, 0x5d, 0x59, 0x55, 0x51, 0x4d, 0x49,
+ 0x45, 0x41, 0x7d, 0x79, 0x75, 0x71, 0x6d, 0x69, 0x65, 0x61,
+ 0x9d, 0x99, 0x95, 0x91, 0x8d, 0x89, 0x85, 0x81, 0xbd, 0xb9,
+ 0xb5, 0xb1, 0xad, 0xa9, 0xa5, 0xa1, 0xdd, 0xd9, 0xd5, 0xd1,
+ 0xcd, 0xc9, 0xc5, 0xc1, 0xfd, 0xf9, 0xf5, 0xf1, 0xed, 0xe9,
+ 0xe5, 0xe1, 0x3a, 0x3e, 0x32, 0x36, 0x2a, 0x2e, 0x22, 0x26,
+ 0x1a, 0x1e, 0x12, 0x16, 0x0a, 0x0e, 0x02, 0x06, 0x7a, 0x7e,
+ 0x72, 0x76, 0x6a, 0x6e, 0x62, 0x66, 0x5a, 0x5e, 0x52, 0x56,
+ 0x4a, 0x4e, 0x42, 0x46, 0xba, 0xbe, 0xb2, 0xb6, 0xaa, 0xae,
+ 0xa2, 0xa6, 0x9a, 0x9e, 0x92, 0x96, 0x8a, 0x8e, 0x82, 0x86,
+ 0xfa, 0xfe, 0xf2, 0xf6, 0xea, 0xee, 0xe2, 0xe6, 0xda, 0xde,
+ 0xd2, 0xd6, 0xca, 0xce, 0xc2, 0xc6, 0x27, 0x23, 0x2f, 0x2b,
+ 0x37, 0x33, 0x3f, 0x3b, 0x07, 0x03, 0x0f, 0x0b, 0x17, 0x13,
+ 0x1f, 0x1b, 0x67, 0x63, 0x6f, 0x6b, 0x77, 0x73, 0x7f, 0x7b,
+ 0x47, 0x43, 0x4f, 0x4b, 0x57, 0x53, 0x5f, 0x5b, 0xa7, 0xa3,
+ 0xaf, 0xab, 0xb7, 0xb3, 0xbf, 0xbb, 0x87, 0x83, 0x8f, 0x8b,
+ 0x97, 0x93, 0x9f, 0x9b, 0xe7, 0xe3, 0xef, 0xeb, 0xf7, 0xf3,
+ 0xff, 0xfb, 0xc7, 0xc3, 0xcf, 0xcb, 0xd7, 0xd3, 0xdf, 0xdb,
+ 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x11, 0x1e, 0x1b, 0x28, 0x2d,
+ 0x22, 0x27, 0x3c, 0x39, 0x36, 0x33, 0x50, 0x55, 0x5a, 0x5f,
+ 0x44, 0x41, 0x4e, 0x4b, 0x78, 0x7d, 0x72, 0x77, 0x6c, 0x69,
+ 0x66, 0x63, 0xa0, 0xa5, 0xaa, 0xaf, 0xb4, 0xb1, 0xbe, 0xbb,
+ 0x88, 0x8d, 0x82, 0x87, 0x9c, 0x99, 0x96, 0x93, 0xf0, 0xf5,
+ 0xfa, 0xff, 0xe4, 0xe1, 0xee, 0xeb, 0xd8, 0xdd, 0xd2, 0xd7,
+ 0xcc, 0xc9, 0xc6, 0xc3, 0x5d, 0x58, 0x57, 0x52, 0x49, 0x4c,
+ 0x43, 0x46, 0x75, 0x70, 0x7f, 0x7a, 0x61, 0x64, 0x6b, 0x6e,
+ 0x0d, 0x08, 0x07, 0x02, 0x19, 0x1c, 0x13, 0x16, 0x25, 0x20,
+ 0x2f, 0x2a, 0x31, 0x34, 0x3b, 0x3e, 0xfd, 0xf8, 0xf7, 0xf2,
+ 0xe9, 0xec, 0xe3, 0xe6, 0xd5, 0xd0, 0xdf, 0xda, 0xc1, 0xc4,
+ 0xcb, 0xce, 0xad, 0xa8, 0xa7, 0xa2, 0xb9, 0xbc, 0xb3, 0xb6,
+ 0x85, 0x80, 0x8f, 0x8a, 0x91, 0x94, 0x9b, 0x9e, 0xba, 0xbf,
+ 0xb0, 0xb5, 0xae, 0xab, 0xa4, 0xa1, 0x92, 0x97, 0x98, 0x9d,
+ 0x86, 0x83, 0x8c, 0x89, 0xea, 0xef, 0xe0, 0xe5, 0xfe, 0xfb,
+ 0xf4, 0xf1, 0xc2, 0xc7, 0xc8, 0xcd, 0xd6, 0xd3, 0xdc, 0xd9,
+ 0x1a, 0x1f, 0x10, 0x15, 0x0e, 0x0b, 0x04, 0x01, 0x32, 0x37,
+ 0x38, 0x3d, 0x26, 0x23, 0x2c, 0x29, 0x4a, 0x4f, 0x40, 0x45,
+ 0x5e, 0x5b, 0x54, 0x51, 0x62, 0x67, 0x68, 0x6d, 0x76, 0x73,
+ 0x7c, 0x79, 0xe7, 0xe2, 0xed, 0xe8, 0xf3, 0xf6, 0xf9, 0xfc,
+ 0xcf, 0xca, 0xc5, 0xc0, 0xdb, 0xde, 0xd1, 0xd4, 0xb7, 0xb2,
+ 0xbd, 0xb8, 0xa3, 0xa6, 0xa9, 0xac, 0x9f, 0x9a, 0x95, 0x90,
+ 0x8b, 0x8e, 0x81, 0x84, 0x47, 0x42, 0x4d, 0x48, 0x53, 0x56,
+ 0x59, 0x5c, 0x6f, 0x6a, 0x65, 0x60, 0x7b, 0x7e, 0x71, 0x74,
+ 0x17, 0x12, 0x1d, 0x18, 0x03, 0x06, 0x09, 0x0c, 0x3f, 0x3a,
+ 0x35, 0x30, 0x2b, 0x2e, 0x21, 0x24, 0x00, 0x06, 0x0c, 0x0a,
+ 0x18, 0x1e, 0x14, 0x12, 0x30, 0x36, 0x3c, 0x3a, 0x28, 0x2e,
+ 0x24, 0x22, 0x60, 0x66, 0x6c, 0x6a, 0x78, 0x7e, 0x74, 0x72,
+ 0x50, 0x56, 0x5c, 0x5a, 0x48, 0x4e, 0x44, 0x42, 0xc0, 0xc6,
+ 0xcc, 0xca, 0xd8, 0xde, 0xd4, 0xd2, 0xf0, 0xf6, 0xfc, 0xfa,
+ 0xe8, 0xee, 0xe4, 0xe2, 0xa0, 0xa6, 0xac, 0xaa, 0xb8, 0xbe,
+ 0xb4, 0xb2, 0x90, 0x96, 0x9c, 0x9a, 0x88, 0x8e, 0x84, 0x82,
+ 0x9d, 0x9b, 0x91, 0x97, 0x85, 0x83, 0x89, 0x8f, 0xad, 0xab,
+ 0xa1, 0xa7, 0xb5, 0xb3, 0xb9, 0xbf, 0xfd, 0xfb, 0xf1, 0xf7,
+ 0xe5, 0xe3, 0xe9, 0xef, 0xcd, 0xcb, 0xc1, 0xc7, 0xd5, 0xd3,
+ 0xd9, 0xdf, 0x5d, 0x5b, 0x51, 0x57, 0x45, 0x43, 0x49, 0x4f,
+ 0x6d, 0x6b, 0x61, 0x67, 0x75, 0x73, 0x79, 0x7f, 0x3d, 0x3b,
+ 0x31, 0x37, 0x25, 0x23, 0x29, 0x2f, 0x0d, 0x0b, 0x01, 0x07,
+ 0x15, 0x13, 0x19, 0x1f, 0x27, 0x21, 0x2b, 0x2d, 0x3f, 0x39,
+ 0x33, 0x35, 0x17, 0x11, 0x1b, 0x1d, 0x0f, 0x09, 0x03, 0x05,
+ 0x47, 0x41, 0x4b, 0x4d, 0x5f, 0x59, 0x53, 0x55, 0x77, 0x71,
+ 0x7b, 0x7d, 0x6f, 0x69, 0x63, 0x65, 0xe7, 0xe1, 0xeb, 0xed,
+ 0xff, 0xf9, 0xf3, 0xf5, 0xd7, 0xd1, 0xdb, 0xdd, 0xcf, 0xc9,
+ 0xc3, 0xc5, 0x87, 0x81, 0x8b, 0x8d, 0x9f, 0x99, 0x93, 0x95,
+ 0xb7, 0xb1, 0xbb, 0xbd, 0xaf, 0xa9, 0xa3, 0xa5, 0xba, 0xbc,
+ 0xb6, 0xb0, 0xa2, 0xa4, 0xae, 0xa8, 0x8a, 0x8c, 0x86, 0x80,
+ 0x92, 0x94, 0x9e, 0x98, 0xda, 0xdc, 0xd6, 0xd0, 0xc2, 0xc4,
+ 0xce, 0xc8, 0xea, 0xec, 0xe6, 0xe0, 0xf2, 0xf4, 0xfe, 0xf8,
+ 0x7a, 0x7c, 0x76, 0x70, 0x62, 0x64, 0x6e, 0x68, 0x4a, 0x4c,
+ 0x46, 0x40, 0x52, 0x54, 0x5e, 0x58, 0x1a, 0x1c, 0x16, 0x10,
+ 0x02, 0x04, 0x0e, 0x08, 0x2a, 0x2c, 0x26, 0x20, 0x32, 0x34,
+ 0x3e, 0x38, 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,
+ 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77,
+ 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41,
+ 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb,
+ 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
+ 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf,
+ 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xdd, 0xda, 0xd3, 0xd4,
+ 0xc1, 0xc6, 0xcf, 0xc8, 0xe5, 0xe2, 0xeb, 0xec, 0xf9, 0xfe,
+ 0xf7, 0xf0, 0xad, 0xaa, 0xa3, 0xa4, 0xb1, 0xb6, 0xbf, 0xb8,
+ 0x95, 0x92, 0x9b, 0x9c, 0x89, 0x8e, 0x87, 0x80, 0x3d, 0x3a,
+ 0x33, 0x34, 0x21, 0x26, 0x2f, 0x28, 0x05, 0x02, 0x0b, 0x0c,
+ 0x19, 0x1e, 0x17, 0x10, 0x4d, 0x4a, 0x43, 0x44, 0x51, 0x56,
+ 0x5f, 0x58, 0x75, 0x72, 0x7b, 0x7c, 0x69, 0x6e, 0x67, 0x60,
+ 0xa7, 0xa0, 0xa9, 0xae, 0xbb, 0xbc, 0xb5, 0xb2, 0x9f, 0x98,
+ 0x91, 0x96, 0x83, 0x84, 0x8d, 0x8a, 0xd7, 0xd0, 0xd9, 0xde,
+ 0xcb, 0xcc, 0xc5, 0xc2, 0xef, 0xe8, 0xe1, 0xe6, 0xf3, 0xf4,
+ 0xfd, 0xfa, 0x47, 0x40, 0x49, 0x4e, 0x5b, 0x5c, 0x55, 0x52,
+ 0x7f, 0x78, 0x71, 0x76, 0x63, 0x64, 0x6d, 0x6a, 0x37, 0x30,
+ 0x39, 0x3e, 0x2b, 0x2c, 0x25, 0x22, 0x0f, 0x08, 0x01, 0x06,
+ 0x13, 0x14, 0x1d, 0x1a, 0x7a, 0x7d, 0x74, 0x73, 0x66, 0x61,
+ 0x68, 0x6f, 0x42, 0x45, 0x4c, 0x4b, 0x5e, 0x59, 0x50, 0x57,
+ 0x0a, 0x0d, 0x04, 0x03, 0x16, 0x11, 0x18, 0x1f, 0x32, 0x35,
+ 0x3c, 0x3b, 0x2e, 0x29, 0x20, 0x27, 0x9a, 0x9d, 0x94, 0x93,
+ 0x86, 0x81, 0x88, 0x8f, 0xa2, 0xa5, 0xac, 0xab, 0xbe, 0xb9,
+ 0xb0, 0xb7, 0xea, 0xed, 0xe4, 0xe3, 0xf6, 0xf1, 0xf8, 0xff,
+ 0xd2, 0xd5, 0xdc, 0xdb, 0xce, 0xc9, 0xc0, 0xc7, 0x00, 0x08,
+ 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58,
+ 0x60, 0x68, 0x70, 0x78, 0x80, 0x88, 0x90, 0x98, 0xa0, 0xa8,
+ 0xb0, 0xb8, 0xc0, 0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8,
+ 0x1d, 0x15, 0x0d, 0x05, 0x3d, 0x35, 0x2d, 0x25, 0x5d, 0x55,
+ 0x4d, 0x45, 0x7d, 0x75, 0x6d, 0x65, 0x9d, 0x95, 0x8d, 0x85,
+ 0xbd, 0xb5, 0xad, 0xa5, 0xdd, 0xd5, 0xcd, 0xc5, 0xfd, 0xf5,
+ 0xed, 0xe5, 0x3a, 0x32, 0x2a, 0x22, 0x1a, 0x12, 0x0a, 0x02,
+ 0x7a, 0x72, 0x6a, 0x62, 0x5a, 0x52, 0x4a, 0x42, 0xba, 0xb2,
+ 0xaa, 0xa2, 0x9a, 0x92, 0x8a, 0x82, 0xfa, 0xf2, 0xea, 0xe2,
+ 0xda, 0xd2, 0xca, 0xc2, 0x27, 0x2f, 0x37, 0x3f, 0x07, 0x0f,
+ 0x17, 0x1f, 0x67, 0x6f, 0x77, 0x7f, 0x47, 0x4f, 0x57, 0x5f,
+ 0xa7, 0xaf, 0xb7, 0xbf, 0x87, 0x8f, 0x97, 0x9f, 0xe7, 0xef,
+ 0xf7, 0xff, 0xc7, 0xcf, 0xd7, 0xdf, 0x74, 0x7c, 0x64, 0x6c,
+ 0x54, 0x5c, 0x44, 0x4c, 0x34, 0x3c, 0x24, 0x2c, 0x14, 0x1c,
+ 0x04, 0x0c, 0xf4, 0xfc, 0xe4, 0xec, 0xd4, 0xdc, 0xc4, 0xcc,
+ 0xb4, 0xbc, 0xa4, 0xac, 0x94, 0x9c, 0x84, 0x8c, 0x69, 0x61,
+ 0x79, 0x71, 0x49, 0x41, 0x59, 0x51, 0x29, 0x21, 0x39, 0x31,
+ 0x09, 0x01, 0x19, 0x11, 0xe9, 0xe1, 0xf9, 0xf1, 0xc9, 0xc1,
+ 0xd9, 0xd1, 0xa9, 0xa1, 0xb9, 0xb1, 0x89, 0x81, 0x99, 0x91,
+ 0x4e, 0x46, 0x5e, 0x56, 0x6e, 0x66, 0x7e, 0x76, 0x0e, 0x06,
+ 0x1e, 0x16, 0x2e, 0x26, 0x3e, 0x36, 0xce, 0xc6, 0xde, 0xd6,
+ 0xee, 0xe6, 0xfe, 0xf6, 0x8e, 0x86, 0x9e, 0x96, 0xae, 0xa6,
+ 0xbe, 0xb6, 0x53, 0x5b, 0x43, 0x4b, 0x73, 0x7b, 0x63, 0x6b,
+ 0x13, 0x1b, 0x03, 0x0b, 0x33, 0x3b, 0x23, 0x2b, 0xd3, 0xdb,
+ 0xc3, 0xcb, 0xf3, 0xfb, 0xe3, 0xeb, 0x93, 0x9b, 0x83, 0x8b,
+ 0xb3, 0xbb, 0xa3, 0xab, 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d,
+ 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
+ 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1,
+ 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, 0x3d, 0x34, 0x2f, 0x26,
+ 0x19, 0x10, 0x0b, 0x02, 0x75, 0x7c, 0x67, 0x6e, 0x51, 0x58,
+ 0x43, 0x4a, 0xad, 0xa4, 0xbf, 0xb6, 0x89, 0x80, 0x9b, 0x92,
+ 0xe5, 0xec, 0xf7, 0xfe, 0xc1, 0xc8, 0xd3, 0xda, 0x7a, 0x73,
+ 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, 0x32, 0x3b, 0x20, 0x29,
+ 0x16, 0x1f, 0x04, 0x0d, 0xea, 0xe3, 0xf8, 0xf1, 0xce, 0xc7,
+ 0xdc, 0xd5, 0xa2, 0xab, 0xb0, 0xb9, 0x86, 0x8f, 0x94, 0x9d,
+ 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06,
+ 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, 0xd7, 0xde, 0xc5, 0xcc,
+ 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2,
+ 0xa9, 0xa0, 0xf4, 0xfd, 0xe6, 0xef, 0xd0, 0xd9, 0xc2, 0xcb,
+ 0xbc, 0xb5, 0xae, 0xa7, 0x98, 0x91, 0x8a, 0x83, 0x64, 0x6d,
+ 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, 0x2c, 0x25, 0x3e, 0x37,
+ 0x08, 0x01, 0x1a, 0x13, 0xc9, 0xc0, 0xdb, 0xd2, 0xed, 0xe4,
+ 0xff, 0xf6, 0x81, 0x88, 0x93, 0x9a, 0xa5, 0xac, 0xb7, 0xbe,
+ 0x59, 0x50, 0x4b, 0x42, 0x7d, 0x74, 0x6f, 0x66, 0x11, 0x18,
+ 0x03, 0x0a, 0x35, 0x3c, 0x27, 0x2e, 0x8e, 0x87, 0x9c, 0x95,
+ 0xaa, 0xa3, 0xb8, 0xb1, 0xc6, 0xcf, 0xd4, 0xdd, 0xe2, 0xeb,
+ 0xf0, 0xf9, 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
+ 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0xb3, 0xba,
+ 0xa1, 0xa8, 0x97, 0x9e, 0x85, 0x8c, 0xfb, 0xf2, 0xe9, 0xe0,
+ 0xdf, 0xd6, 0xcd, 0xc4, 0x23, 0x2a, 0x31, 0x38, 0x07, 0x0e,
+ 0x15, 0x1c, 0x6b, 0x62, 0x79, 0x70, 0x4f, 0x46, 0x5d, 0x54,
+ 0x00, 0x0a, 0x14, 0x1e, 0x28, 0x22, 0x3c, 0x36, 0x50, 0x5a,
+ 0x44, 0x4e, 0x78, 0x72, 0x6c, 0x66, 0xa0, 0xaa, 0xb4, 0xbe,
+ 0x88, 0x82, 0x9c, 0x96, 0xf0, 0xfa, 0xe4, 0xee, 0xd8, 0xd2,
+ 0xcc, 0xc6, 0x5d, 0x57, 0x49, 0x43, 0x75, 0x7f, 0x61, 0x6b,
+ 0x0d, 0x07, 0x19, 0x13, 0x25, 0x2f, 0x31, 0x3b, 0xfd, 0xf7,
+ 0xe9, 0xe3, 0xd5, 0xdf, 0xc1, 0xcb, 0xad, 0xa7, 0xb9, 0xb3,
+ 0x85, 0x8f, 0x91, 0x9b, 0xba, 0xb0, 0xae, 0xa4, 0x92, 0x98,
+ 0x86, 0x8c, 0xea, 0xe0, 0xfe, 0xf4, 0xc2, 0xc8, 0xd6, 0xdc,
+ 0x1a, 0x10, 0x0e, 0x04, 0x32, 0x38, 0x26, 0x2c, 0x4a, 0x40,
+ 0x5e, 0x54, 0x62, 0x68, 0x76, 0x7c, 0xe7, 0xed, 0xf3, 0xf9,
+ 0xcf, 0xc5, 0xdb, 0xd1, 0xb7, 0xbd, 0xa3, 0xa9, 0x9f, 0x95,
+ 0x8b, 0x81, 0x47, 0x4d, 0x53, 0x59, 0x6f, 0x65, 0x7b, 0x71,
+ 0x17, 0x1d, 0x03, 0x09, 0x3f, 0x35, 0x2b, 0x21, 0x69, 0x63,
+ 0x7d, 0x77, 0x41, 0x4b, 0x55, 0x5f, 0x39, 0x33, 0x2d, 0x27,
+ 0x11, 0x1b, 0x05, 0x0f, 0xc9, 0xc3, 0xdd, 0xd7, 0xe1, 0xeb,
+ 0xf5, 0xff, 0x99, 0x93, 0x8d, 0x87, 0xb1, 0xbb, 0xa5, 0xaf,
+ 0x34, 0x3e, 0x20, 0x2a, 0x1c, 0x16, 0x08, 0x02, 0x64, 0x6e,
+ 0x70, 0x7a, 0x4c, 0x46, 0x58, 0x52, 0x94, 0x9e, 0x80, 0x8a,
+ 0xbc, 0xb6, 0xa8, 0xa2, 0xc4, 0xce, 0xd0, 0xda, 0xec, 0xe6,
+ 0xf8, 0xf2, 0xd3, 0xd9, 0xc7, 0xcd, 0xfb, 0xf1, 0xef, 0xe5,
+ 0x83, 0x89, 0x97, 0x9d, 0xab, 0xa1, 0xbf, 0xb5, 0x73, 0x79,
+ 0x67, 0x6d, 0x5b, 0x51, 0x4f, 0x45, 0x23, 0x29, 0x37, 0x3d,
+ 0x0b, 0x01, 0x1f, 0x15, 0x8e, 0x84, 0x9a, 0x90, 0xa6, 0xac,
+ 0xb2, 0xb8, 0xde, 0xd4, 0xca, 0xc0, 0xf6, 0xfc, 0xe2, 0xe8,
+ 0x2e, 0x24, 0x3a, 0x30, 0x06, 0x0c, 0x12, 0x18, 0x7e, 0x74,
+ 0x6a, 0x60, 0x56, 0x5c, 0x42, 0x48, 0x00, 0x0b, 0x16, 0x1d,
+ 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f,
+ 0x62, 0x69, 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81,
+ 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, 0x7d, 0x76,
+ 0x6b, 0x60, 0x51, 0x5a, 0x47, 0x4c, 0x25, 0x2e, 0x33, 0x38,
+ 0x09, 0x02, 0x1f, 0x14, 0xcd, 0xc6, 0xdb, 0xd0, 0xe1, 0xea,
+ 0xf7, 0xfc, 0x95, 0x9e, 0x83, 0x88, 0xb9, 0xb2, 0xaf, 0xa4,
+ 0xfa, 0xf1, 0xec, 0xe7, 0xd6, 0xdd, 0xc0, 0xcb, 0xa2, 0xa9,
+ 0xb4, 0xbf, 0x8e, 0x85, 0x98, 0x93, 0x4a, 0x41, 0x5c, 0x57,
+ 0x66, 0x6d, 0x70, 0x7b, 0x12, 0x19, 0x04, 0x0f, 0x3e, 0x35,
+ 0x28, 0x23, 0x87, 0x8c, 0x91, 0x9a, 0xab, 0xa0, 0xbd, 0xb6,
+ 0xdf, 0xd4, 0xc9, 0xc2, 0xf3, 0xf8, 0xe5, 0xee, 0x37, 0x3c,
+ 0x21, 0x2a, 0x1b, 0x10, 0x0d, 0x06, 0x6f, 0x64, 0x79, 0x72,
+ 0x43, 0x48, 0x55, 0x5e, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce,
+ 0xd3, 0xd8, 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80,
+ 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, 0x01, 0x0a,
+ 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x94, 0x9f, 0x82, 0x89,
+ 0xb8, 0xb3, 0xae, 0xa5, 0xcc, 0xc7, 0xda, 0xd1, 0xe0, 0xeb,
+ 0xf6, 0xfd, 0x24, 0x2f, 0x32, 0x39, 0x08, 0x03, 0x1e, 0x15,
+ 0x7c, 0x77, 0x6a, 0x61, 0x50, 0x5b, 0x46, 0x4d, 0x13, 0x18,
+ 0x05, 0x0e, 0x3f, 0x34, 0x29, 0x22, 0x4b, 0x40, 0x5d, 0x56,
+ 0x67, 0x6c, 0x71, 0x7a, 0xa3, 0xa8, 0xb5, 0xbe, 0x8f, 0x84,
+ 0x99, 0x92, 0xfb, 0xf0, 0xed, 0xe6, 0xd7, 0xdc, 0xc1, 0xca,
+ 0x6e, 0x65, 0x78, 0x73, 0x42, 0x49, 0x54, 0x5f, 0x36, 0x3d,
+ 0x20, 0x2b, 0x1a, 0x11, 0x0c, 0x07, 0xde, 0xd5, 0xc8, 0xc3,
+ 0xf2, 0xf9, 0xe4, 0xef, 0x86, 0x8d, 0x90, 0x9b, 0xaa, 0xa1,
+ 0xbc, 0xb7, 0x00, 0x0c, 0x18, 0x14, 0x30, 0x3c, 0x28, 0x24,
+ 0x60, 0x6c, 0x78, 0x74, 0x50, 0x5c, 0x48, 0x44, 0xc0, 0xcc,
+ 0xd8, 0xd4, 0xf0, 0xfc, 0xe8, 0xe4, 0xa0, 0xac, 0xb8, 0xb4,
+ 0x90, 0x9c, 0x88, 0x84, 0x9d, 0x91, 0x85, 0x89, 0xad, 0xa1,
+ 0xb5, 0xb9, 0xfd, 0xf1, 0xe5, 0xe9, 0xcd, 0xc1, 0xd5, 0xd9,
+ 0x5d, 0x51, 0x45, 0x49, 0x6d, 0x61, 0x75, 0x79, 0x3d, 0x31,
+ 0x25, 0x29, 0x0d, 0x01, 0x15, 0x19, 0x27, 0x2b, 0x3f, 0x33,
+ 0x17, 0x1b, 0x0f, 0x03, 0x47, 0x4b, 0x5f, 0x53, 0x77, 0x7b,
+ 0x6f, 0x63, 0xe7, 0xeb, 0xff, 0xf3, 0xd7, 0xdb, 0xcf, 0xc3,
+ 0x87, 0x8b, 0x9f, 0x93, 0xb7, 0xbb, 0xaf, 0xa3, 0xba, 0xb6,
+ 0xa2, 0xae, 0x8a, 0x86, 0x92, 0x9e, 0xda, 0xd6, 0xc2, 0xce,
+ 0xea, 0xe6, 0xf2, 0xfe, 0x7a, 0x76, 0x62, 0x6e, 0x4a, 0x46,
+ 0x52, 0x5e, 0x1a, 0x16, 0x02, 0x0e, 0x2a, 0x26, 0x32, 0x3e,
+ 0x4e, 0x42, 0x56, 0x5a, 0x7e, 0x72, 0x66, 0x6a, 0x2e, 0x22,
+ 0x36, 0x3a, 0x1e, 0x12, 0x06, 0x0a, 0x8e, 0x82, 0x96, 0x9a,
+ 0xbe, 0xb2, 0xa6, 0xaa, 0xee, 0xe2, 0xf6, 0xfa, 0xde, 0xd2,
+ 0xc6, 0xca, 0xd3, 0xdf, 0xcb, 0xc7, 0xe3, 0xef, 0xfb, 0xf7,
+ 0xb3, 0xbf, 0xab, 0xa7, 0x83, 0x8f, 0x9b, 0x97, 0x13, 0x1f,
+ 0x0b, 0x07, 0x23, 0x2f, 0x3b, 0x37, 0x73, 0x7f, 0x6b, 0x67,
+ 0x43, 0x4f, 0x5b, 0x57, 0x69, 0x65, 0x71, 0x7d, 0x59, 0x55,
+ 0x41, 0x4d, 0x09, 0x05, 0x11, 0x1d, 0x39, 0x35, 0x21, 0x2d,
+ 0xa9, 0xa5, 0xb1, 0xbd, 0x99, 0x95, 0x81, 0x8d, 0xc9, 0xc5,
+ 0xd1, 0xdd, 0xf9, 0xf5, 0xe1, 0xed, 0xf4, 0xf8, 0xec, 0xe0,
+ 0xc4, 0xc8, 0xdc, 0xd0, 0x94, 0x98, 0x8c, 0x80, 0xa4, 0xa8,
+ 0xbc, 0xb0, 0x34, 0x38, 0x2c, 0x20, 0x04, 0x08, 0x1c, 0x10,
+ 0x54, 0x58, 0x4c, 0x40, 0x64, 0x68, 0x7c, 0x70, 0x00, 0x0d,
+ 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f,
+ 0x5c, 0x51, 0x46, 0x4b, 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9,
+ 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
+ 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8,
+ 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, 0x6d, 0x60, 0x77, 0x7a,
+ 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c,
+ 0x2b, 0x26, 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44,
+ 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, 0xb7, 0xba,
+ 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8,
+ 0xeb, 0xe6, 0xf1, 0xfc, 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3,
+ 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
+ 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f,
+ 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, 0xce, 0xc3, 0xd4, 0xd9,
+ 0xfa, 0xf7, 0xe0, 0xed, 0xa6, 0xab, 0xbc, 0xb1, 0x92, 0x9f,
+ 0x88, 0x85, 0x1e, 0x13, 0x04, 0x09, 0x2a, 0x27, 0x30, 0x3d,
+ 0x76, 0x7b, 0x6c, 0x61, 0x42, 0x4f, 0x58, 0x55, 0x73, 0x7e,
+ 0x69, 0x64, 0x47, 0x4a, 0x5d, 0x50, 0x1b, 0x16, 0x01, 0x0c,
+ 0x2f, 0x22, 0x35, 0x38, 0xa3, 0xae, 0xb9, 0xb4, 0x97, 0x9a,
+ 0x8d, 0x80, 0xcb, 0xc6, 0xd1, 0xdc, 0xff, 0xf2, 0xe5, 0xe8,
+ 0xa9, 0xa4, 0xb3, 0xbe, 0x9d, 0x90, 0x87, 0x8a, 0xc1, 0xcc,
+ 0xdb, 0xd6, 0xf5, 0xf8, 0xef, 0xe2, 0x79, 0x74, 0x63, 0x6e,
+ 0x4d, 0x40, 0x57, 0x5a, 0x11, 0x1c, 0x0b, 0x06, 0x25, 0x28,
+ 0x3f, 0x32, 0x14, 0x19, 0x0e, 0x03, 0x20, 0x2d, 0x3a, 0x37,
+ 0x7c, 0x71, 0x66, 0x6b, 0x48, 0x45, 0x52, 0x5f, 0xc4, 0xc9,
+ 0xde, 0xd3, 0xf0, 0xfd, 0xea, 0xe7, 0xac, 0xa1, 0xb6, 0xbb,
+ 0x98, 0x95, 0x82, 0x8f, 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36,
+ 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
+ 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e,
+ 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, 0xdd, 0xd3, 0xc1, 0xcf,
+ 0xe5, 0xeb, 0xf9, 0xf7, 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b,
+ 0x89, 0x87, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
+ 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0xa7, 0xa9,
+ 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d, 0xd7, 0xd9, 0xcb, 0xc5,
+ 0xef, 0xe1, 0xf3, 0xfd, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71,
+ 0x63, 0x6d, 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d,
+ 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04,
+ 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, 0x9a, 0x94, 0x86, 0x88,
+ 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc,
+ 0xce, 0xc0, 0x53, 0x5d, 0x4f, 0x41, 0x6b, 0x65, 0x77, 0x79,
+ 0x23, 0x2d, 0x3f, 0x31, 0x1b, 0x15, 0x07, 0x09, 0xb3, 0xbd,
+ 0xaf, 0xa1, 0x8b, 0x85, 0x97, 0x99, 0xc3, 0xcd, 0xdf, 0xd1,
+ 0xfb, 0xf5, 0xe7, 0xe9, 0x8e, 0x80, 0x92, 0x9c, 0xb6, 0xb8,
+ 0xaa, 0xa4, 0xfe, 0xf0, 0xe2, 0xec, 0xc6, 0xc8, 0xda, 0xd4,
+ 0x6e, 0x60, 0x72, 0x7c, 0x56, 0x58, 0x4a, 0x44, 0x1e, 0x10,
+ 0x02, 0x0c, 0x26, 0x28, 0x3a, 0x34, 0xf4, 0xfa, 0xe8, 0xe6,
+ 0xcc, 0xc2, 0xd0, 0xde, 0x84, 0x8a, 0x98, 0x96, 0xbc, 0xb2,
+ 0xa0, 0xae, 0x14, 0x1a, 0x08, 0x06, 0x2c, 0x22, 0x30, 0x3e,
+ 0x64, 0x6a, 0x78, 0x76, 0x5c, 0x52, 0x40, 0x4e, 0x29, 0x27,
+ 0x35, 0x3b, 0x11, 0x1f, 0x0d, 0x03, 0x59, 0x57, 0x45, 0x4b,
+ 0x61, 0x6f, 0x7d, 0x73, 0xc9, 0xc7, 0xd5, 0xdb, 0xf1, 0xff,
+ 0xed, 0xe3, 0xb9, 0xb7, 0xa5, 0xab, 0x81, 0x8f, 0x9d, 0x93,
+ 0x00, 0x0f, 0x1e, 0x11, 0x3c, 0x33, 0x22, 0x2d, 0x78, 0x77,
+ 0x66, 0x69, 0x44, 0x4b, 0x5a, 0x55, 0xf0, 0xff, 0xee, 0xe1,
+ 0xcc, 0xc3, 0xd2, 0xdd, 0x88, 0x87, 0x96, 0x99, 0xb4, 0xbb,
+ 0xaa, 0xa5, 0xfd, 0xf2, 0xe3, 0xec, 0xc1, 0xce, 0xdf, 0xd0,
+ 0x85, 0x8a, 0x9b, 0x94, 0xb9, 0xb6, 0xa7, 0xa8, 0x0d, 0x02,
+ 0x13, 0x1c, 0x31, 0x3e, 0x2f, 0x20, 0x75, 0x7a, 0x6b, 0x64,
+ 0x49, 0x46, 0x57, 0x58, 0xe7, 0xe8, 0xf9, 0xf6, 0xdb, 0xd4,
+ 0xc5, 0xca, 0x9f, 0x90, 0x81, 0x8e, 0xa3, 0xac, 0xbd, 0xb2,
+ 0x17, 0x18, 0x09, 0x06, 0x2b, 0x24, 0x35, 0x3a, 0x6f, 0x60,
+ 0x71, 0x7e, 0x53, 0x5c, 0x4d, 0x42, 0x1a, 0x15, 0x04, 0x0b,
+ 0x26, 0x29, 0x38, 0x37, 0x62, 0x6d, 0x7c, 0x73, 0x5e, 0x51,
+ 0x40, 0x4f, 0xea, 0xe5, 0xf4, 0xfb, 0xd6, 0xd9, 0xc8, 0xc7,
+ 0x92, 0x9d, 0x8c, 0x83, 0xae, 0xa1, 0xb0, 0xbf, 0xd3, 0xdc,
+ 0xcd, 0xc2, 0xef, 0xe0, 0xf1, 0xfe, 0xab, 0xa4, 0xb5, 0xba,
+ 0x97, 0x98, 0x89, 0x86, 0x23, 0x2c, 0x3d, 0x32, 0x1f, 0x10,
+ 0x01, 0x0e, 0x5b, 0x54, 0x45, 0x4a, 0x67, 0x68, 0x79, 0x76,
+ 0x2e, 0x21, 0x30, 0x3f, 0x12, 0x1d, 0x0c, 0x03, 0x56, 0x59,
+ 0x48, 0x47, 0x6a, 0x65, 0x74, 0x7b, 0xde, 0xd1, 0xc0, 0xcf,
+ 0xe2, 0xed, 0xfc, 0xf3, 0xa6, 0xa9, 0xb8, 0xb7, 0x9a, 0x95,
+ 0x84, 0x8b, 0x34, 0x3b, 0x2a, 0x25, 0x08, 0x07, 0x16, 0x19,
+ 0x4c, 0x43, 0x52, 0x5d, 0x70, 0x7f, 0x6e, 0x61, 0xc4, 0xcb,
+ 0xda, 0xd5, 0xf8, 0xf7, 0xe6, 0xe9, 0xbc, 0xb3, 0xa2, 0xad,
+ 0x80, 0x8f, 0x9e, 0x91, 0xc9, 0xc6, 0xd7, 0xd8, 0xf5, 0xfa,
+ 0xeb, 0xe4, 0xb1, 0xbe, 0xaf, 0xa0, 0x8d, 0x82, 0x93, 0x9c,
+ 0x39, 0x36, 0x27, 0x28, 0x05, 0x0a, 0x1b, 0x14, 0x41, 0x4e,
+ 0x5f, 0x50, 0x7d, 0x72, 0x63, 0x6c, 0x00, 0x10, 0x20, 0x30,
+ 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0,
+ 0xe0, 0xf0, 0x1d, 0x0d, 0x3d, 0x2d, 0x5d, 0x4d, 0x7d, 0x6d,
+ 0x9d, 0x8d, 0xbd, 0xad, 0xdd, 0xcd, 0xfd, 0xed, 0x3a, 0x2a,
+ 0x1a, 0x0a, 0x7a, 0x6a, 0x5a, 0x4a, 0xba, 0xaa, 0x9a, 0x8a,
+ 0xfa, 0xea, 0xda, 0xca, 0x27, 0x37, 0x07, 0x17, 0x67, 0x77,
+ 0x47, 0x57, 0xa7, 0xb7, 0x87, 0x97, 0xe7, 0xf7, 0xc7, 0xd7,
+ 0x74, 0x64, 0x54, 0x44, 0x34, 0x24, 0x14, 0x04, 0xf4, 0xe4,
+ 0xd4, 0xc4, 0xb4, 0xa4, 0x94, 0x84, 0x69, 0x79, 0x49, 0x59,
+ 0x29, 0x39, 0x09, 0x19, 0xe9, 0xf9, 0xc9, 0xd9, 0xa9, 0xb9,
+ 0x89, 0x99, 0x4e, 0x5e, 0x6e, 0x7e, 0x0e, 0x1e, 0x2e, 0x3e,
+ 0xce, 0xde, 0xee, 0xfe, 0x8e, 0x9e, 0xae, 0xbe, 0x53, 0x43,
+ 0x73, 0x63, 0x13, 0x03, 0x33, 0x23, 0xd3, 0xc3, 0xf3, 0xe3,
+ 0x93, 0x83, 0xb3, 0xa3, 0xe8, 0xf8, 0xc8, 0xd8, 0xa8, 0xb8,
+ 0x88, 0x98, 0x68, 0x78, 0x48, 0x58, 0x28, 0x38, 0x08, 0x18,
+ 0xf5, 0xe5, 0xd5, 0xc5, 0xb5, 0xa5, 0x95, 0x85, 0x75, 0x65,
+ 0x55, 0x45, 0x35, 0x25, 0x15, 0x05, 0xd2, 0xc2, 0xf2, 0xe2,
+ 0x92, 0x82, 0xb2, 0xa2, 0x52, 0x42, 0x72, 0x62, 0x12, 0x02,
+ 0x32, 0x22, 0xcf, 0xdf, 0xef, 0xff, 0x8f, 0x9f, 0xaf, 0xbf,
+ 0x4f, 0x5f, 0x6f, 0x7f, 0x0f, 0x1f, 0x2f, 0x3f, 0x9c, 0x8c,
+ 0xbc, 0xac, 0xdc, 0xcc, 0xfc, 0xec, 0x1c, 0x0c, 0x3c, 0x2c,
+ 0x5c, 0x4c, 0x7c, 0x6c, 0x81, 0x91, 0xa1, 0xb1, 0xc1, 0xd1,
+ 0xe1, 0xf1, 0x01, 0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71,
+ 0xa6, 0xb6, 0x86, 0x96, 0xe6, 0xf6, 0xc6, 0xd6, 0x26, 0x36,
+ 0x06, 0x16, 0x66, 0x76, 0x46, 0x56, 0xbb, 0xab, 0x9b, 0x8b,
+ 0xfb, 0xeb, 0xdb, 0xcb, 0x3b, 0x2b, 0x1b, 0x0b, 0x7b, 0x6b,
+ 0x5b, 0x4b, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x0d, 0x1c,
+ 0x2f, 0x3e, 0x49, 0x58, 0x6b, 0x7a, 0x85, 0x94, 0xa7, 0xb6,
+ 0xc1, 0xd0, 0xe3, 0xf2, 0x1a, 0x0b, 0x38, 0x29, 0x5e, 0x4f,
+ 0x7c, 0x6d, 0x92, 0x83, 0xb0, 0xa1, 0xd6, 0xc7, 0xf4, 0xe5,
+ 0x17, 0x06, 0x35, 0x24, 0x53, 0x42, 0x71, 0x60, 0x9f, 0x8e,
+ 0xbd, 0xac, 0xdb, 0xca, 0xf9, 0xe8, 0x34, 0x25, 0x16, 0x07,
+ 0x70, 0x61, 0x52, 0x43, 0xbc, 0xad, 0x9e, 0x8f, 0xf8, 0xe9,
+ 0xda, 0xcb, 0x39, 0x28, 0x1b, 0x0a, 0x7d, 0x6c, 0x5f, 0x4e,
+ 0xb1, 0xa0, 0x93, 0x82, 0xf5, 0xe4, 0xd7, 0xc6, 0x2e, 0x3f,
+ 0x0c, 0x1d, 0x6a, 0x7b, 0x48, 0x59, 0xa6, 0xb7, 0x84, 0x95,
+ 0xe2, 0xf3, 0xc0, 0xd1, 0x23, 0x32, 0x01, 0x10, 0x67, 0x76,
+ 0x45, 0x54, 0xab, 0xba, 0x89, 0x98, 0xef, 0xfe, 0xcd, 0xdc,
+ 0x68, 0x79, 0x4a, 0x5b, 0x2c, 0x3d, 0x0e, 0x1f, 0xe0, 0xf1,
+ 0xc2, 0xd3, 0xa4, 0xb5, 0x86, 0x97, 0x65, 0x74, 0x47, 0x56,
+ 0x21, 0x30, 0x03, 0x12, 0xed, 0xfc, 0xcf, 0xde, 0xa9, 0xb8,
+ 0x8b, 0x9a, 0x72, 0x63, 0x50, 0x41, 0x36, 0x27, 0x14, 0x05,
+ 0xfa, 0xeb, 0xd8, 0xc9, 0xbe, 0xaf, 0x9c, 0x8d, 0x7f, 0x6e,
+ 0x5d, 0x4c, 0x3b, 0x2a, 0x19, 0x08, 0xf7, 0xe6, 0xd5, 0xc4,
+ 0xb3, 0xa2, 0x91, 0x80, 0x5c, 0x4d, 0x7e, 0x6f, 0x18, 0x09,
+ 0x3a, 0x2b, 0xd4, 0xc5, 0xf6, 0xe7, 0x90, 0x81, 0xb2, 0xa3,
+ 0x51, 0x40, 0x73, 0x62, 0x15, 0x04, 0x37, 0x26, 0xd9, 0xc8,
+ 0xfb, 0xea, 0x9d, 0x8c, 0xbf, 0xae, 0x46, 0x57, 0x64, 0x75,
+ 0x02, 0x13, 0x20, 0x31, 0xce, 0xdf, 0xec, 0xfd, 0x8a, 0x9b,
+ 0xa8, 0xb9, 0x4b, 0x5a, 0x69, 0x78, 0x0f, 0x1e, 0x2d, 0x3c,
+ 0xc3, 0xd2, 0xe1, 0xf0, 0x87, 0x96, 0xa5, 0xb4, 0x00, 0x12,
+ 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, 0x90, 0x82, 0xb4, 0xa6,
+ 0xd8, 0xca, 0xfc, 0xee, 0x3d, 0x2f, 0x19, 0x0b, 0x75, 0x67,
+ 0x51, 0x43, 0xad, 0xbf, 0x89, 0x9b, 0xe5, 0xf7, 0xc1, 0xd3,
+ 0x7a, 0x68, 0x5e, 0x4c, 0x32, 0x20, 0x16, 0x04, 0xea, 0xf8,
+ 0xce, 0xdc, 0xa2, 0xb0, 0x86, 0x94, 0x47, 0x55, 0x63, 0x71,
+ 0x0f, 0x1d, 0x2b, 0x39, 0xd7, 0xc5, 0xf3, 0xe1, 0x9f, 0x8d,
+ 0xbb, 0xa9, 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
+ 0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, 0xc9, 0xdb,
+ 0xed, 0xff, 0x81, 0x93, 0xa5, 0xb7, 0x59, 0x4b, 0x7d, 0x6f,
+ 0x11, 0x03, 0x35, 0x27, 0x8e, 0x9c, 0xaa, 0xb8, 0xc6, 0xd4,
+ 0xe2, 0xf0, 0x1e, 0x0c, 0x3a, 0x28, 0x56, 0x44, 0x72, 0x60,
+ 0xb3, 0xa1, 0x97, 0x85, 0xfb, 0xe9, 0xdf, 0xcd, 0x23, 0x31,
+ 0x07, 0x15, 0x6b, 0x79, 0x4f, 0x5d, 0xf5, 0xe7, 0xd1, 0xc3,
+ 0xbd, 0xaf, 0x99, 0x8b, 0x65, 0x77, 0x41, 0x53, 0x2d, 0x3f,
+ 0x09, 0x1b, 0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6,
+ 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26, 0x8f, 0x9d,
+ 0xab, 0xb9, 0xc7, 0xd5, 0xe3, 0xf1, 0x1f, 0x0d, 0x3b, 0x29,
+ 0x57, 0x45, 0x73, 0x61, 0xb2, 0xa0, 0x96, 0x84, 0xfa, 0xe8,
+ 0xde, 0xcc, 0x22, 0x30, 0x06, 0x14, 0x6a, 0x78, 0x4e, 0x5c,
+ 0x01, 0x13, 0x25, 0x37, 0x49, 0x5b, 0x6d, 0x7f, 0x91, 0x83,
+ 0xb5, 0xa7, 0xd9, 0xcb, 0xfd, 0xef, 0x3c, 0x2e, 0x18, 0x0a,
+ 0x74, 0x66, 0x50, 0x42, 0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6,
+ 0xc0, 0xd2, 0x7b, 0x69, 0x5f, 0x4d, 0x33, 0x21, 0x17, 0x05,
+ 0xeb, 0xf9, 0xcf, 0xdd, 0xa3, 0xb1, 0x87, 0x95, 0x46, 0x54,
+ 0x62, 0x70, 0x0e, 0x1c, 0x2a, 0x38, 0xd6, 0xc4, 0xf2, 0xe0,
+ 0x9e, 0x8c, 0xba, 0xa8, 0x00, 0x13, 0x26, 0x35, 0x4c, 0x5f,
+ 0x6a, 0x79, 0x98, 0x8b, 0xbe, 0xad, 0xd4, 0xc7, 0xf2, 0xe1,
+ 0x2d, 0x3e, 0x0b, 0x18, 0x61, 0x72, 0x47, 0x54, 0xb5, 0xa6,
+ 0x93, 0x80, 0xf9, 0xea, 0xdf, 0xcc, 0x5a, 0x49, 0x7c, 0x6f,
+ 0x16, 0x05, 0x30, 0x23, 0xc2, 0xd1, 0xe4, 0xf7, 0x8e, 0x9d,
+ 0xa8, 0xbb, 0x77, 0x64, 0x51, 0x42, 0x3b, 0x28, 0x1d, 0x0e,
+ 0xef, 0xfc, 0xc9, 0xda, 0xa3, 0xb0, 0x85, 0x96, 0xb4, 0xa7,
+ 0x92, 0x81, 0xf8, 0xeb, 0xde, 0xcd, 0x2c, 0x3f, 0x0a, 0x19,
+ 0x60, 0x73, 0x46, 0x55, 0x99, 0x8a, 0xbf, 0xac, 0xd5, 0xc6,
+ 0xf3, 0xe0, 0x01, 0x12, 0x27, 0x34, 0x4d, 0x5e, 0x6b, 0x78,
+ 0xee, 0xfd, 0xc8, 0xdb, 0xa2, 0xb1, 0x84, 0x97, 0x76, 0x65,
+ 0x50, 0x43, 0x3a, 0x29, 0x1c, 0x0f, 0xc3, 0xd0, 0xe5, 0xf6,
+ 0x8f, 0x9c, 0xa9, 0xba, 0x5b, 0x48, 0x7d, 0x6e, 0x17, 0x04,
+ 0x31, 0x22, 0x75, 0x66, 0x53, 0x40, 0x39, 0x2a, 0x1f, 0x0c,
+ 0xed, 0xfe, 0xcb, 0xd8, 0xa1, 0xb2, 0x87, 0x94, 0x58, 0x4b,
+ 0x7e, 0x6d, 0x14, 0x07, 0x32, 0x21, 0xc0, 0xd3, 0xe6, 0xf5,
+ 0x8c, 0x9f, 0xaa, 0xb9, 0x2f, 0x3c, 0x09, 0x1a, 0x63, 0x70,
+ 0x45, 0x56, 0xb7, 0xa4, 0x91, 0x82, 0xfb, 0xe8, 0xdd, 0xce,
+ 0x02, 0x11, 0x24, 0x37, 0x4e, 0x5d, 0x68, 0x7b, 0x9a, 0x89,
+ 0xbc, 0xaf, 0xd6, 0xc5, 0xf0, 0xe3, 0xc1, 0xd2, 0xe7, 0xf4,
+ 0x8d, 0x9e, 0xab, 0xb8, 0x59, 0x4a, 0x7f, 0x6c, 0x15, 0x06,
+ 0x33, 0x20, 0xec, 0xff, 0xca, 0xd9, 0xa0, 0xb3, 0x86, 0x95,
+ 0x74, 0x67, 0x52, 0x41, 0x38, 0x2b, 0x1e, 0x0d, 0x9b, 0x88,
+ 0xbd, 0xae, 0xd7, 0xc4, 0xf1, 0xe2, 0x03, 0x10, 0x25, 0x36,
+ 0x4f, 0x5c, 0x69, 0x7a, 0xb6, 0xa5, 0x90, 0x83, 0xfa, 0xe9,
+ 0xdc, 0xcf, 0x2e, 0x3d, 0x08, 0x1b, 0x62, 0x71, 0x44, 0x57,
+ 0x00, 0x14, 0x28, 0x3c, 0x50, 0x44, 0x78, 0x6c, 0xa0, 0xb4,
+ 0x88, 0x9c, 0xf0, 0xe4, 0xd8, 0xcc, 0x5d, 0x49, 0x75, 0x61,
+ 0x0d, 0x19, 0x25, 0x31, 0xfd, 0xe9, 0xd5, 0xc1, 0xad, 0xb9,
+ 0x85, 0x91, 0xba, 0xae, 0x92, 0x86, 0xea, 0xfe, 0xc2, 0xd6,
+ 0x1a, 0x0e, 0x32, 0x26, 0x4a, 0x5e, 0x62, 0x76, 0xe7, 0xf3,
+ 0xcf, 0xdb, 0xb7, 0xa3, 0x9f, 0x8b, 0x47, 0x53, 0x6f, 0x7b,
+ 0x17, 0x03, 0x3f, 0x2b, 0x69, 0x7d, 0x41, 0x55, 0x39, 0x2d,
+ 0x11, 0x05, 0xc9, 0xdd, 0xe1, 0xf5, 0x99, 0x8d, 0xb1, 0xa5,
+ 0x34, 0x20, 0x1c, 0x08, 0x64, 0x70, 0x4c, 0x58, 0x94, 0x80,
+ 0xbc, 0xa8, 0xc4, 0xd0, 0xec, 0xf8, 0xd3, 0xc7, 0xfb, 0xef,
+ 0x83, 0x97, 0xab, 0xbf, 0x73, 0x67, 0x5b, 0x4f, 0x23, 0x37,
+ 0x0b, 0x1f, 0x8e, 0x9a, 0xa6, 0xb2, 0xde, 0xca, 0xf6, 0xe2,
+ 0x2e, 0x3a, 0x06, 0x12, 0x7e, 0x6a, 0x56, 0x42, 0xd2, 0xc6,
+ 0xfa, 0xee, 0x82, 0x96, 0xaa, 0xbe, 0x72, 0x66, 0x5a, 0x4e,
+ 0x22, 0x36, 0x0a, 0x1e, 0x8f, 0x9b, 0xa7, 0xb3, 0xdf, 0xcb,
+ 0xf7, 0xe3, 0x2f, 0x3b, 0x07, 0x13, 0x7f, 0x6b, 0x57, 0x43,
+ 0x68, 0x7c, 0x40, 0x54, 0x38, 0x2c, 0x10, 0x04, 0xc8, 0xdc,
+ 0xe0, 0xf4, 0x98, 0x8c, 0xb0, 0xa4, 0x35, 0x21, 0x1d, 0x09,
+ 0x65, 0x71, 0x4d, 0x59, 0x95, 0x81, 0xbd, 0xa9, 0xc5, 0xd1,
+ 0xed, 0xf9, 0xbb, 0xaf, 0x93, 0x87, 0xeb, 0xff, 0xc3, 0xd7,
+ 0x1b, 0x0f, 0x33, 0x27, 0x4b, 0x5f, 0x63, 0x77, 0xe6, 0xf2,
+ 0xce, 0xda, 0xb6, 0xa2, 0x9e, 0x8a, 0x46, 0x52, 0x6e, 0x7a,
+ 0x16, 0x02, 0x3e, 0x2a, 0x01, 0x15, 0x29, 0x3d, 0x51, 0x45,
+ 0x79, 0x6d, 0xa1, 0xb5, 0x89, 0x9d, 0xf1, 0xe5, 0xd9, 0xcd,
+ 0x5c, 0x48, 0x74, 0x60, 0x0c, 0x18, 0x24, 0x30, 0xfc, 0xe8,
+ 0xd4, 0xc0, 0xac, 0xb8, 0x84, 0x90, 0x00, 0x15, 0x2a, 0x3f,
+ 0x54, 0x41, 0x7e, 0x6b, 0xa8, 0xbd, 0x82, 0x97, 0xfc, 0xe9,
+ 0xd6, 0xc3, 0x4d, 0x58, 0x67, 0x72, 0x19, 0x0c, 0x33, 0x26,
+ 0xe5, 0xf0, 0xcf, 0xda, 0xb1, 0xa4, 0x9b, 0x8e, 0x9a, 0x8f,
+ 0xb0, 0xa5, 0xce, 0xdb, 0xe4, 0xf1, 0x32, 0x27, 0x18, 0x0d,
+ 0x66, 0x73, 0x4c, 0x59, 0xd7, 0xc2, 0xfd, 0xe8, 0x83, 0x96,
+ 0xa9, 0xbc, 0x7f, 0x6a, 0x55, 0x40, 0x2b, 0x3e, 0x01, 0x14,
+ 0x29, 0x3c, 0x03, 0x16, 0x7d, 0x68, 0x57, 0x42, 0x81, 0x94,
+ 0xab, 0xbe, 0xd5, 0xc0, 0xff, 0xea, 0x64, 0x71, 0x4e, 0x5b,
+ 0x30, 0x25, 0x1a, 0x0f, 0xcc, 0xd9, 0xe6, 0xf3, 0x98, 0x8d,
+ 0xb2, 0xa7, 0xb3, 0xa6, 0x99, 0x8c, 0xe7, 0xf2, 0xcd, 0xd8,
+ 0x1b, 0x0e, 0x31, 0x24, 0x4f, 0x5a, 0x65, 0x70, 0xfe, 0xeb,
+ 0xd4, 0xc1, 0xaa, 0xbf, 0x80, 0x95, 0x56, 0x43, 0x7c, 0x69,
+ 0x02, 0x17, 0x28, 0x3d, 0x52, 0x47, 0x78, 0x6d, 0x06, 0x13,
+ 0x2c, 0x39, 0xfa, 0xef, 0xd0, 0xc5, 0xae, 0xbb, 0x84, 0x91,
+ 0x1f, 0x0a, 0x35, 0x20, 0x4b, 0x5e, 0x61, 0x74, 0xb7, 0xa2,
+ 0x9d, 0x88, 0xe3, 0xf6, 0xc9, 0xdc, 0xc8, 0xdd, 0xe2, 0xf7,
+ 0x9c, 0x89, 0xb6, 0xa3, 0x60, 0x75, 0x4a, 0x5f, 0x34, 0x21,
+ 0x1e, 0x0b, 0x85, 0x90, 0xaf, 0xba, 0xd1, 0xc4, 0xfb, 0xee,
+ 0x2d, 0x38, 0x07, 0x12, 0x79, 0x6c, 0x53, 0x46, 0x7b, 0x6e,
+ 0x51, 0x44, 0x2f, 0x3a, 0x05, 0x10, 0xd3, 0xc6, 0xf9, 0xec,
+ 0x87, 0x92, 0xad, 0xb8, 0x36, 0x23, 0x1c, 0x09, 0x62, 0x77,
+ 0x48, 0x5d, 0x9e, 0x8b, 0xb4, 0xa1, 0xca, 0xdf, 0xe0, 0xf5,
+ 0xe1, 0xf4, 0xcb, 0xde, 0xb5, 0xa0, 0x9f, 0x8a, 0x49, 0x5c,
+ 0x63, 0x76, 0x1d, 0x08, 0x37, 0x22, 0xac, 0xb9, 0x86, 0x93,
+ 0xf8, 0xed, 0xd2, 0xc7, 0x04, 0x11, 0x2e, 0x3b, 0x50, 0x45,
+ 0x7a, 0x6f, 0x00, 0x16, 0x2c, 0x3a, 0x58, 0x4e, 0x74, 0x62,
+ 0xb0, 0xa6, 0x9c, 0x8a, 0xe8, 0xfe, 0xc4, 0xd2, 0x7d, 0x6b,
+ 0x51, 0x47, 0x25, 0x33, 0x09, 0x1f, 0xcd, 0xdb, 0xe1, 0xf7,
+ 0x95, 0x83, 0xb9, 0xaf, 0xfa, 0xec, 0xd6, 0xc0, 0xa2, 0xb4,
+ 0x8e, 0x98, 0x4a, 0x5c, 0x66, 0x70, 0x12, 0x04, 0x3e, 0x28,
+ 0x87, 0x91, 0xab, 0xbd, 0xdf, 0xc9, 0xf3, 0xe5, 0x37, 0x21,
+ 0x1b, 0x0d, 0x6f, 0x79, 0x43, 0x55, 0xe9, 0xff, 0xc5, 0xd3,
+ 0xb1, 0xa7, 0x9d, 0x8b, 0x59, 0x4f, 0x75, 0x63, 0x01, 0x17,
+ 0x2d, 0x3b, 0x94, 0x82, 0xb8, 0xae, 0xcc, 0xda, 0xe0, 0xf6,
+ 0x24, 0x32, 0x08, 0x1e, 0x7c, 0x6a, 0x50, 0x46, 0x13, 0x05,
+ 0x3f, 0x29, 0x4b, 0x5d, 0x67, 0x71, 0xa3, 0xb5, 0x8f, 0x99,
+ 0xfb, 0xed, 0xd7, 0xc1, 0x6e, 0x78, 0x42, 0x54, 0x36, 0x20,
+ 0x1a, 0x0c, 0xde, 0xc8, 0xf2, 0xe4, 0x86, 0x90, 0xaa, 0xbc,
+ 0xcf, 0xd9, 0xe3, 0xf5, 0x97, 0x81, 0xbb, 0xad, 0x7f, 0x69,
+ 0x53, 0x45, 0x27, 0x31, 0x0b, 0x1d, 0xb2, 0xa4, 0x9e, 0x88,
+ 0xea, 0xfc, 0xc6, 0xd0, 0x02, 0x14, 0x2e, 0x38, 0x5a, 0x4c,
+ 0x76, 0x60, 0x35, 0x23, 0x19, 0x0f, 0x6d, 0x7b, 0x41, 0x57,
+ 0x85, 0x93, 0xa9, 0xbf, 0xdd, 0xcb, 0xf1, 0xe7, 0x48, 0x5e,
+ 0x64, 0x72, 0x10, 0x06, 0x3c, 0x2a, 0xf8, 0xee, 0xd4, 0xc2,
+ 0xa0, 0xb6, 0x8c, 0x9a, 0x26, 0x30, 0x0a, 0x1c, 0x7e, 0x68,
+ 0x52, 0x44, 0x96, 0x80, 0xba, 0xac, 0xce, 0xd8, 0xe2, 0xf4,
+ 0x5b, 0x4d, 0x77, 0x61, 0x03, 0x15, 0x2f, 0x39, 0xeb, 0xfd,
+ 0xc7, 0xd1, 0xb3, 0xa5, 0x9f, 0x89, 0xdc, 0xca, 0xf0, 0xe6,
+ 0x84, 0x92, 0xa8, 0xbe, 0x6c, 0x7a, 0x40, 0x56, 0x34, 0x22,
+ 0x18, 0x0e, 0xa1, 0xb7, 0x8d, 0x9b, 0xf9, 0xef, 0xd5, 0xc3,
+ 0x11, 0x07, 0x3d, 0x2b, 0x49, 0x5f, 0x65, 0x73, 0x00, 0x17,
+ 0x2e, 0x39, 0x5c, 0x4b, 0x72, 0x65, 0xb8, 0xaf, 0x96, 0x81,
+ 0xe4, 0xf3, 0xca, 0xdd, 0x6d, 0x7a, 0x43, 0x54, 0x31, 0x26,
+ 0x1f, 0x08, 0xd5, 0xc2, 0xfb, 0xec, 0x89, 0x9e, 0xa7, 0xb0,
+ 0xda, 0xcd, 0xf4, 0xe3, 0x86, 0x91, 0xa8, 0xbf, 0x62, 0x75,
+ 0x4c, 0x5b, 0x3e, 0x29, 0x10, 0x07, 0xb7, 0xa0, 0x99, 0x8e,
+ 0xeb, 0xfc, 0xc5, 0xd2, 0x0f, 0x18, 0x21, 0x36, 0x53, 0x44,
+ 0x7d, 0x6a, 0xa9, 0xbe, 0x87, 0x90, 0xf5, 0xe2, 0xdb, 0xcc,
+ 0x11, 0x06, 0x3f, 0x28, 0x4d, 0x5a, 0x63, 0x74, 0xc4, 0xd3,
+ 0xea, 0xfd, 0x98, 0x8f, 0xb6, 0xa1, 0x7c, 0x6b, 0x52, 0x45,
+ 0x20, 0x37, 0x0e, 0x19, 0x73, 0x64, 0x5d, 0x4a, 0x2f, 0x38,
+ 0x01, 0x16, 0xcb, 0xdc, 0xe5, 0xf2, 0x97, 0x80, 0xb9, 0xae,
+ 0x1e, 0x09, 0x30, 0x27, 0x42, 0x55, 0x6c, 0x7b, 0xa6, 0xb1,
+ 0x88, 0x9f, 0xfa, 0xed, 0xd4, 0xc3, 0x4f, 0x58, 0x61, 0x76,
+ 0x13, 0x04, 0x3d, 0x2a, 0xf7, 0xe0, 0xd9, 0xce, 0xab, 0xbc,
+ 0x85, 0x92, 0x22, 0x35, 0x0c, 0x1b, 0x7e, 0x69, 0x50, 0x47,
+ 0x9a, 0x8d, 0xb4, 0xa3, 0xc6, 0xd1, 0xe8, 0xff, 0x95, 0x82,
+ 0xbb, 0xac, 0xc9, 0xde, 0xe7, 0xf0, 0x2d, 0x3a, 0x03, 0x14,
+ 0x71, 0x66, 0x5f, 0x48, 0xf8, 0xef, 0xd6, 0xc1, 0xa4, 0xb3,
+ 0x8a, 0x9d, 0x40, 0x57, 0x6e, 0x79, 0x1c, 0x0b, 0x32, 0x25,
+ 0xe6, 0xf1, 0xc8, 0xdf, 0xba, 0xad, 0x94, 0x83, 0x5e, 0x49,
+ 0x70, 0x67, 0x02, 0x15, 0x2c, 0x3b, 0x8b, 0x9c, 0xa5, 0xb2,
+ 0xd7, 0xc0, 0xf9, 0xee, 0x33, 0x24, 0x1d, 0x0a, 0x6f, 0x78,
+ 0x41, 0x56, 0x3c, 0x2b, 0x12, 0x05, 0x60, 0x77, 0x4e, 0x59,
+ 0x84, 0x93, 0xaa, 0xbd, 0xd8, 0xcf, 0xf6, 0xe1, 0x51, 0x46,
+ 0x7f, 0x68, 0x0d, 0x1a, 0x23, 0x34, 0xe9, 0xfe, 0xc7, 0xd0,
+ 0xb5, 0xa2, 0x9b, 0x8c, 0x00, 0x18, 0x30, 0x28, 0x60, 0x78,
+ 0x50, 0x48, 0xc0, 0xd8, 0xf0, 0xe8, 0xa0, 0xb8, 0x90, 0x88,
+ 0x9d, 0x85, 0xad, 0xb5, 0xfd, 0xe5, 0xcd, 0xd5, 0x5d, 0x45,
+ 0x6d, 0x75, 0x3d, 0x25, 0x0d, 0x15, 0x27, 0x3f, 0x17, 0x0f,
+ 0x47, 0x5f, 0x77, 0x6f, 0xe7, 0xff, 0xd7, 0xcf, 0x87, 0x9f,
+ 0xb7, 0xaf, 0xba, 0xa2, 0x8a, 0x92, 0xda, 0xc2, 0xea, 0xf2,
+ 0x7a, 0x62, 0x4a, 0x52, 0x1a, 0x02, 0x2a, 0x32, 0x4e, 0x56,
+ 0x7e, 0x66, 0x2e, 0x36, 0x1e, 0x06, 0x8e, 0x96, 0xbe, 0xa6,
+ 0xee, 0xf6, 0xde, 0xc6, 0xd3, 0xcb, 0xe3, 0xfb, 0xb3, 0xab,
+ 0x83, 0x9b, 0x13, 0x0b, 0x23, 0x3b, 0x73, 0x6b, 0x43, 0x5b,
+ 0x69, 0x71, 0x59, 0x41, 0x09, 0x11, 0x39, 0x21, 0xa9, 0xb1,
+ 0x99, 0x81, 0xc9, 0xd1, 0xf9, 0xe1, 0xf4, 0xec, 0xc4, 0xdc,
+ 0x94, 0x8c, 0xa4, 0xbc, 0x34, 0x2c, 0x04, 0x1c, 0x54, 0x4c,
+ 0x64, 0x7c, 0x9c, 0x84, 0xac, 0xb4, 0xfc, 0xe4, 0xcc, 0xd4,
+ 0x5c, 0x44, 0x6c, 0x74, 0x3c, 0x24, 0x0c, 0x14, 0x01, 0x19,
+ 0x31, 0x29, 0x61, 0x79, 0x51, 0x49, 0xc1, 0xd9, 0xf1, 0xe9,
+ 0xa1, 0xb9, 0x91, 0x89, 0xbb, 0xa3, 0x8b, 0x93, 0xdb, 0xc3,
+ 0xeb, 0xf3, 0x7b, 0x63, 0x4b, 0x53, 0x1b, 0x03, 0x2b, 0x33,
+ 0x26, 0x3e, 0x16, 0x0e, 0x46, 0x5e, 0x76, 0x6e, 0xe6, 0xfe,
+ 0xd6, 0xce, 0x86, 0x9e, 0xb6, 0xae, 0xd2, 0xca, 0xe2, 0xfa,
+ 0xb2, 0xaa, 0x82, 0x9a, 0x12, 0x0a, 0x22, 0x3a, 0x72, 0x6a,
+ 0x42, 0x5a, 0x4f, 0x57, 0x7f, 0x67, 0x2f, 0x37, 0x1f, 0x07,
+ 0x8f, 0x97, 0xbf, 0xa7, 0xef, 0xf7, 0xdf, 0xc7, 0xf5, 0xed,
+ 0xc5, 0xdd, 0x95, 0x8d, 0xa5, 0xbd, 0x35, 0x2d, 0x05, 0x1d,
+ 0x55, 0x4d, 0x65, 0x7d, 0x68, 0x70, 0x58, 0x40, 0x08, 0x10,
+ 0x38, 0x20, 0xa8, 0xb0, 0x98, 0x80, 0xc8, 0xd0, 0xf8, 0xe0,
+ 0x00, 0x19, 0x32, 0x2b, 0x64, 0x7d, 0x56, 0x4f, 0xc8, 0xd1,
+ 0xfa, 0xe3, 0xac, 0xb5, 0x9e, 0x87, 0x8d, 0x94, 0xbf, 0xa6,
+ 0xe9, 0xf0, 0xdb, 0xc2, 0x45, 0x5c, 0x77, 0x6e, 0x21, 0x38,
+ 0x13, 0x0a, 0x07, 0x1e, 0x35, 0x2c, 0x63, 0x7a, 0x51, 0x48,
+ 0xcf, 0xd6, 0xfd, 0xe4, 0xab, 0xb2, 0x99, 0x80, 0x8a, 0x93,
+ 0xb8, 0xa1, 0xee, 0xf7, 0xdc, 0xc5, 0x42, 0x5b, 0x70, 0x69,
+ 0x26, 0x3f, 0x14, 0x0d, 0x0e, 0x17, 0x3c, 0x25, 0x6a, 0x73,
+ 0x58, 0x41, 0xc6, 0xdf, 0xf4, 0xed, 0xa2, 0xbb, 0x90, 0x89,
+ 0x83, 0x9a, 0xb1, 0xa8, 0xe7, 0xfe, 0xd5, 0xcc, 0x4b, 0x52,
+ 0x79, 0x60, 0x2f, 0x36, 0x1d, 0x04, 0x09, 0x10, 0x3b, 0x22,
+ 0x6d, 0x74, 0x5f, 0x46, 0xc1, 0xd8, 0xf3, 0xea, 0xa5, 0xbc,
+ 0x97, 0x8e, 0x84, 0x9d, 0xb6, 0xaf, 0xe0, 0xf9, 0xd2, 0xcb,
+ 0x4c, 0x55, 0x7e, 0x67, 0x28, 0x31, 0x1a, 0x03, 0x1c, 0x05,
+ 0x2e, 0x37, 0x78, 0x61, 0x4a, 0x53, 0xd4, 0xcd, 0xe6, 0xff,
+ 0xb0, 0xa9, 0x82, 0x9b, 0x91, 0x88, 0xa3, 0xba, 0xf5, 0xec,
+ 0xc7, 0xde, 0x59, 0x40, 0x6b, 0x72, 0x3d, 0x24, 0x0f, 0x16,
+ 0x1b, 0x02, 0x29, 0x30, 0x7f, 0x66, 0x4d, 0x54, 0xd3, 0xca,
+ 0xe1, 0xf8, 0xb7, 0xae, 0x85, 0x9c, 0x96, 0x8f, 0xa4, 0xbd,
+ 0xf2, 0xeb, 0xc0, 0xd9, 0x5e, 0x47, 0x6c, 0x75, 0x3a, 0x23,
+ 0x08, 0x11, 0x12, 0x0b, 0x20, 0x39, 0x76, 0x6f, 0x44, 0x5d,
+ 0xda, 0xc3, 0xe8, 0xf1, 0xbe, 0xa7, 0x8c, 0x95, 0x9f, 0x86,
+ 0xad, 0xb4, 0xfb, 0xe2, 0xc9, 0xd0, 0x57, 0x4e, 0x65, 0x7c,
+ 0x33, 0x2a, 0x01, 0x18, 0x15, 0x0c, 0x27, 0x3e, 0x71, 0x68,
+ 0x43, 0x5a, 0xdd, 0xc4, 0xef, 0xf6, 0xb9, 0xa0, 0x8b, 0x92,
+ 0x98, 0x81, 0xaa, 0xb3, 0xfc, 0xe5, 0xce, 0xd7, 0x50, 0x49,
+ 0x62, 0x7b, 0x34, 0x2d, 0x06, 0x1f, 0x00, 0x1a, 0x34, 0x2e,
+ 0x68, 0x72, 0x5c, 0x46, 0xd0, 0xca, 0xe4, 0xfe, 0xb8, 0xa2,
+ 0x8c, 0x96, 0xbd, 0xa7, 0x89, 0x93, 0xd5, 0xcf, 0xe1, 0xfb,
+ 0x6d, 0x77, 0x59, 0x43, 0x05, 0x1f, 0x31, 0x2b, 0x67, 0x7d,
+ 0x53, 0x49, 0x0f, 0x15, 0x3b, 0x21, 0xb7, 0xad, 0x83, 0x99,
+ 0xdf, 0xc5, 0xeb, 0xf1, 0xda, 0xc0, 0xee, 0xf4, 0xb2, 0xa8,
+ 0x86, 0x9c, 0x0a, 0x10, 0x3e, 0x24, 0x62, 0x78, 0x56, 0x4c,
+ 0xce, 0xd4, 0xfa, 0xe0, 0xa6, 0xbc, 0x92, 0x88, 0x1e, 0x04,
+ 0x2a, 0x30, 0x76, 0x6c, 0x42, 0x58, 0x73, 0x69, 0x47, 0x5d,
+ 0x1b, 0x01, 0x2f, 0x35, 0xa3, 0xb9, 0x97, 0x8d, 0xcb, 0xd1,
+ 0xff, 0xe5, 0xa9, 0xb3, 0x9d, 0x87, 0xc1, 0xdb, 0xf5, 0xef,
+ 0x79, 0x63, 0x4d, 0x57, 0x11, 0x0b, 0x25, 0x3f, 0x14, 0x0e,
+ 0x20, 0x3a, 0x7c, 0x66, 0x48, 0x52, 0xc4, 0xde, 0xf0, 0xea,
+ 0xac, 0xb6, 0x98, 0x82, 0x81, 0x9b, 0xb5, 0xaf, 0xe9, 0xf3,
+ 0xdd, 0xc7, 0x51, 0x4b, 0x65, 0x7f, 0x39, 0x23, 0x0d, 0x17,
+ 0x3c, 0x26, 0x08, 0x12, 0x54, 0x4e, 0x60, 0x7a, 0xec, 0xf6,
+ 0xd8, 0xc2, 0x84, 0x9e, 0xb0, 0xaa, 0xe6, 0xfc, 0xd2, 0xc8,
+ 0x8e, 0x94, 0xba, 0xa0, 0x36, 0x2c, 0x02, 0x18, 0x5e, 0x44,
+ 0x6a, 0x70, 0x5b, 0x41, 0x6f, 0x75, 0x33, 0x29, 0x07, 0x1d,
+ 0x8b, 0x91, 0xbf, 0xa5, 0xe3, 0xf9, 0xd7, 0xcd, 0x4f, 0x55,
+ 0x7b, 0x61, 0x27, 0x3d, 0x13, 0x09, 0x9f, 0x85, 0xab, 0xb1,
+ 0xf7, 0xed, 0xc3, 0xd9, 0xf2, 0xe8, 0xc6, 0xdc, 0x9a, 0x80,
+ 0xae, 0xb4, 0x22, 0x38, 0x16, 0x0c, 0x4a, 0x50, 0x7e, 0x64,
+ 0x28, 0x32, 0x1c, 0x06, 0x40, 0x5a, 0x74, 0x6e, 0xf8, 0xe2,
+ 0xcc, 0xd6, 0x90, 0x8a, 0xa4, 0xbe, 0x95, 0x8f, 0xa1, 0xbb,
+ 0xfd, 0xe7, 0xc9, 0xd3, 0x45, 0x5f, 0x71, 0x6b, 0x2d, 0x37,
+ 0x19, 0x03, 0x00, 0x1b, 0x36, 0x2d, 0x6c, 0x77, 0x5a, 0x41,
+ 0xd8, 0xc3, 0xee, 0xf5, 0xb4, 0xaf, 0x82, 0x99, 0xad, 0xb6,
+ 0x9b, 0x80, 0xc1, 0xda, 0xf7, 0xec, 0x75, 0x6e, 0x43, 0x58,
+ 0x19, 0x02, 0x2f, 0x34, 0x47, 0x5c, 0x71, 0x6a, 0x2b, 0x30,
+ 0x1d, 0x06, 0x9f, 0x84, 0xa9, 0xb2, 0xf3, 0xe8, 0xc5, 0xde,
+ 0xea, 0xf1, 0xdc, 0xc7, 0x86, 0x9d, 0xb0, 0xab, 0x32, 0x29,
+ 0x04, 0x1f, 0x5e, 0x45, 0x68, 0x73, 0x8e, 0x95, 0xb8, 0xa3,
+ 0xe2, 0xf9, 0xd4, 0xcf, 0x56, 0x4d, 0x60, 0x7b, 0x3a, 0x21,
+ 0x0c, 0x17, 0x23, 0x38, 0x15, 0x0e, 0x4f, 0x54, 0x79, 0x62,
+ 0xfb, 0xe0, 0xcd, 0xd6, 0x97, 0x8c, 0xa1, 0xba, 0xc9, 0xd2,
+ 0xff, 0xe4, 0xa5, 0xbe, 0x93, 0x88, 0x11, 0x0a, 0x27, 0x3c,
+ 0x7d, 0x66, 0x4b, 0x50, 0x64, 0x7f, 0x52, 0x49, 0x08, 0x13,
+ 0x3e, 0x25, 0xbc, 0xa7, 0x8a, 0x91, 0xd0, 0xcb, 0xe6, 0xfd,
+ 0x01, 0x1a, 0x37, 0x2c, 0x6d, 0x76, 0x5b, 0x40, 0xd9, 0xc2,
+ 0xef, 0xf4, 0xb5, 0xae, 0x83, 0x98, 0xac, 0xb7, 0x9a, 0x81,
+ 0xc0, 0xdb, 0xf6, 0xed, 0x74, 0x6f, 0x42, 0x59, 0x18, 0x03,
+ 0x2e, 0x35, 0x46, 0x5d, 0x70, 0x6b, 0x2a, 0x31, 0x1c, 0x07,
+ 0x9e, 0x85, 0xa8, 0xb3, 0xf2, 0xe9, 0xc4, 0xdf, 0xeb, 0xf0,
+ 0xdd, 0xc6, 0x87, 0x9c, 0xb1, 0xaa, 0x33, 0x28, 0x05, 0x1e,
+ 0x5f, 0x44, 0x69, 0x72, 0x8f, 0x94, 0xb9, 0xa2, 0xe3, 0xf8,
+ 0xd5, 0xce, 0x57, 0x4c, 0x61, 0x7a, 0x3b, 0x20, 0x0d, 0x16,
+ 0x22, 0x39, 0x14, 0x0f, 0x4e, 0x55, 0x78, 0x63, 0xfa, 0xe1,
+ 0xcc, 0xd7, 0x96, 0x8d, 0xa0, 0xbb, 0xc8, 0xd3, 0xfe, 0xe5,
+ 0xa4, 0xbf, 0x92, 0x89, 0x10, 0x0b, 0x26, 0x3d, 0x7c, 0x67,
+ 0x4a, 0x51, 0x65, 0x7e, 0x53, 0x48, 0x09, 0x12, 0x3f, 0x24,
+ 0xbd, 0xa6, 0x8b, 0x90, 0xd1, 0xca, 0xe7, 0xfc, 0x00, 0x1c,
+ 0x38, 0x24, 0x70, 0x6c, 0x48, 0x54, 0xe0, 0xfc, 0xd8, 0xc4,
+ 0x90, 0x8c, 0xa8, 0xb4, 0xdd, 0xc1, 0xe5, 0xf9, 0xad, 0xb1,
+ 0x95, 0x89, 0x3d, 0x21, 0x05, 0x19, 0x4d, 0x51, 0x75, 0x69,
+ 0xa7, 0xbb, 0x9f, 0x83, 0xd7, 0xcb, 0xef, 0xf3, 0x47, 0x5b,
+ 0x7f, 0x63, 0x37, 0x2b, 0x0f, 0x13, 0x7a, 0x66, 0x42, 0x5e,
+ 0x0a, 0x16, 0x32, 0x2e, 0x9a, 0x86, 0xa2, 0xbe, 0xea, 0xf6,
+ 0xd2, 0xce, 0x53, 0x4f, 0x6b, 0x77, 0x23, 0x3f, 0x1b, 0x07,
+ 0xb3, 0xaf, 0x8b, 0x97, 0xc3, 0xdf, 0xfb, 0xe7, 0x8e, 0x92,
+ 0xb6, 0xaa, 0xfe, 0xe2, 0xc6, 0xda, 0x6e, 0x72, 0x56, 0x4a,
+ 0x1e, 0x02, 0x26, 0x3a, 0xf4, 0xe8, 0xcc, 0xd0, 0x84, 0x98,
+ 0xbc, 0xa0, 0x14, 0x08, 0x2c, 0x30, 0x64, 0x78, 0x5c, 0x40,
+ 0x29, 0x35, 0x11, 0x0d, 0x59, 0x45, 0x61, 0x7d, 0xc9, 0xd5,
+ 0xf1, 0xed, 0xb9, 0xa5, 0x81, 0x9d, 0xa6, 0xba, 0x9e, 0x82,
+ 0xd6, 0xca, 0xee, 0xf2, 0x46, 0x5a, 0x7e, 0x62, 0x36, 0x2a,
+ 0x0e, 0x12, 0x7b, 0x67, 0x43, 0x5f, 0x0b, 0x17, 0x33, 0x2f,
+ 0x9b, 0x87, 0xa3, 0xbf, 0xeb, 0xf7, 0xd3, 0xcf, 0x01, 0x1d,
+ 0x39, 0x25, 0x71, 0x6d, 0x49, 0x55, 0xe1, 0xfd, 0xd9, 0xc5,
+ 0x91, 0x8d, 0xa9, 0xb5, 0xdc, 0xc0, 0xe4, 0xf8, 0xac, 0xb0,
+ 0x94, 0x88, 0x3c, 0x20, 0x04, 0x18, 0x4c, 0x50, 0x74, 0x68,
+ 0xf5, 0xe9, 0xcd, 0xd1, 0x85, 0x99, 0xbd, 0xa1, 0x15, 0x09,
+ 0x2d, 0x31, 0x65, 0x79, 0x5d, 0x41, 0x28, 0x34, 0x10, 0x0c,
+ 0x58, 0x44, 0x60, 0x7c, 0xc8, 0xd4, 0xf0, 0xec, 0xb8, 0xa4,
+ 0x80, 0x9c, 0x52, 0x4e, 0x6a, 0x76, 0x22, 0x3e, 0x1a, 0x06,
+ 0xb2, 0xae, 0x8a, 0x96, 0xc2, 0xde, 0xfa, 0xe6, 0x8f, 0x93,
+ 0xb7, 0xab, 0xff, 0xe3, 0xc7, 0xdb, 0x6f, 0x73, 0x57, 0x4b,
+ 0x1f, 0x03, 0x27, 0x3b, 0x00, 0x1d, 0x3a, 0x27, 0x74, 0x69,
+ 0x4e, 0x53, 0xe8, 0xf5, 0xd2, 0xcf, 0x9c, 0x81, 0xa6, 0xbb,
+ 0xcd, 0xd0, 0xf7, 0xea, 0xb9, 0xa4, 0x83, 0x9e, 0x25, 0x38,
+ 0x1f, 0x02, 0x51, 0x4c, 0x6b, 0x76, 0x87, 0x9a, 0xbd, 0xa0,
+ 0xf3, 0xee, 0xc9, 0xd4, 0x6f, 0x72, 0x55, 0x48, 0x1b, 0x06,
+ 0x21, 0x3c, 0x4a, 0x57, 0x70, 0x6d, 0x3e, 0x23, 0x04, 0x19,
+ 0xa2, 0xbf, 0x98, 0x85, 0xd6, 0xcb, 0xec, 0xf1, 0x13, 0x0e,
+ 0x29, 0x34, 0x67, 0x7a, 0x5d, 0x40, 0xfb, 0xe6, 0xc1, 0xdc,
+ 0x8f, 0x92, 0xb5, 0xa8, 0xde, 0xc3, 0xe4, 0xf9, 0xaa, 0xb7,
+ 0x90, 0x8d, 0x36, 0x2b, 0x0c, 0x11, 0x42, 0x5f, 0x78, 0x65,
+ 0x94, 0x89, 0xae, 0xb3, 0xe0, 0xfd, 0xda, 0xc7, 0x7c, 0x61,
+ 0x46, 0x5b, 0x08, 0x15, 0x32, 0x2f, 0x59, 0x44, 0x63, 0x7e,
+ 0x2d, 0x30, 0x17, 0x0a, 0xb1, 0xac, 0x8b, 0x96, 0xc5, 0xd8,
+ 0xff, 0xe2, 0x26, 0x3b, 0x1c, 0x01, 0x52, 0x4f, 0x68, 0x75,
+ 0xce, 0xd3, 0xf4, 0xe9, 0xba, 0xa7, 0x80, 0x9d, 0xeb, 0xf6,
+ 0xd1, 0xcc, 0x9f, 0x82, 0xa5, 0xb8, 0x03, 0x1e, 0x39, 0x24,
+ 0x77, 0x6a, 0x4d, 0x50, 0xa1, 0xbc, 0x9b, 0x86, 0xd5, 0xc8,
+ 0xef, 0xf2, 0x49, 0x54, 0x73, 0x6e, 0x3d, 0x20, 0x07, 0x1a,
+ 0x6c, 0x71, 0x56, 0x4b, 0x18, 0x05, 0x22, 0x3f, 0x84, 0x99,
+ 0xbe, 0xa3, 0xf0, 0xed, 0xca, 0xd7, 0x35, 0x28, 0x0f, 0x12,
+ 0x41, 0x5c, 0x7b, 0x66, 0xdd, 0xc0, 0xe7, 0xfa, 0xa9, 0xb4,
+ 0x93, 0x8e, 0xf8, 0xe5, 0xc2, 0xdf, 0x8c, 0x91, 0xb6, 0xab,
+ 0x10, 0x0d, 0x2a, 0x37, 0x64, 0x79, 0x5e, 0x43, 0xb2, 0xaf,
+ 0x88, 0x95, 0xc6, 0xdb, 0xfc, 0xe1, 0x5a, 0x47, 0x60, 0x7d,
+ 0x2e, 0x33, 0x14, 0x09, 0x7f, 0x62, 0x45, 0x58, 0x0b, 0x16,
+ 0x31, 0x2c, 0x97, 0x8a, 0xad, 0xb0, 0xe3, 0xfe, 0xd9, 0xc4,
+ 0x00, 0x1e, 0x3c, 0x22, 0x78, 0x66, 0x44, 0x5a, 0xf0, 0xee,
+ 0xcc, 0xd2, 0x88, 0x96, 0xb4, 0xaa, 0xfd, 0xe3, 0xc1, 0xdf,
+ 0x85, 0x9b, 0xb9, 0xa7, 0x0d, 0x13, 0x31, 0x2f, 0x75, 0x6b,
+ 0x49, 0x57, 0xe7, 0xf9, 0xdb, 0xc5, 0x9f, 0x81, 0xa3, 0xbd,
+ 0x17, 0x09, 0x2b, 0x35, 0x6f, 0x71, 0x53, 0x4d, 0x1a, 0x04,
+ 0x26, 0x38, 0x62, 0x7c, 0x5e, 0x40, 0xea, 0xf4, 0xd6, 0xc8,
+ 0x92, 0x8c, 0xae, 0xb0, 0xd3, 0xcd, 0xef, 0xf1, 0xab, 0xb5,
+ 0x97, 0x89, 0x23, 0x3d, 0x1f, 0x01, 0x5b, 0x45, 0x67, 0x79,
+ 0x2e, 0x30, 0x12, 0x0c, 0x56, 0x48, 0x6a, 0x74, 0xde, 0xc0,
+ 0xe2, 0xfc, 0xa6, 0xb8, 0x9a, 0x84, 0x34, 0x2a, 0x08, 0x16,
+ 0x4c, 0x52, 0x70, 0x6e, 0xc4, 0xda, 0xf8, 0xe6, 0xbc, 0xa2,
+ 0x80, 0x9e, 0xc9, 0xd7, 0xf5, 0xeb, 0xb1, 0xaf, 0x8d, 0x93,
+ 0x39, 0x27, 0x05, 0x1b, 0x41, 0x5f, 0x7d, 0x63, 0xbb, 0xa5,
+ 0x87, 0x99, 0xc3, 0xdd, 0xff, 0xe1, 0x4b, 0x55, 0x77, 0x69,
+ 0x33, 0x2d, 0x0f, 0x11, 0x46, 0x58, 0x7a, 0x64, 0x3e, 0x20,
+ 0x02, 0x1c, 0xb6, 0xa8, 0x8a, 0x94, 0xce, 0xd0, 0xf2, 0xec,
+ 0x5c, 0x42, 0x60, 0x7e, 0x24, 0x3a, 0x18, 0x06, 0xac, 0xb2,
+ 0x90, 0x8e, 0xd4, 0xca, 0xe8, 0xf6, 0xa1, 0xbf, 0x9d, 0x83,
+ 0xd9, 0xc7, 0xe5, 0xfb, 0x51, 0x4f, 0x6d, 0x73, 0x29, 0x37,
+ 0x15, 0x0b, 0x68, 0x76, 0x54, 0x4a, 0x10, 0x0e, 0x2c, 0x32,
+ 0x98, 0x86, 0xa4, 0xba, 0xe0, 0xfe, 0xdc, 0xc2, 0x95, 0x8b,
+ 0xa9, 0xb7, 0xed, 0xf3, 0xd1, 0xcf, 0x65, 0x7b, 0x59, 0x47,
+ 0x1d, 0x03, 0x21, 0x3f, 0x8f, 0x91, 0xb3, 0xad, 0xf7, 0xe9,
+ 0xcb, 0xd5, 0x7f, 0x61, 0x43, 0x5d, 0x07, 0x19, 0x3b, 0x25,
+ 0x72, 0x6c, 0x4e, 0x50, 0x0a, 0x14, 0x36, 0x28, 0x82, 0x9c,
+ 0xbe, 0xa0, 0xfa, 0xe4, 0xc6, 0xd8, 0x00, 0x1f, 0x3e, 0x21,
+ 0x7c, 0x63, 0x42, 0x5d, 0xf8, 0xe7, 0xc6, 0xd9, 0x84, 0x9b,
+ 0xba, 0xa5, 0xed, 0xf2, 0xd3, 0xcc, 0x91, 0x8e, 0xaf, 0xb0,
+ 0x15, 0x0a, 0x2b, 0x34, 0x69, 0x76, 0x57, 0x48, 0xc7, 0xd8,
+ 0xf9, 0xe6, 0xbb, 0xa4, 0x85, 0x9a, 0x3f, 0x20, 0x01, 0x1e,
+ 0x43, 0x5c, 0x7d, 0x62, 0x2a, 0x35, 0x14, 0x0b, 0x56, 0x49,
+ 0x68, 0x77, 0xd2, 0xcd, 0xec, 0xf3, 0xae, 0xb1, 0x90, 0x8f,
+ 0x93, 0x8c, 0xad, 0xb2, 0xef, 0xf0, 0xd1, 0xce, 0x6b, 0x74,
+ 0x55, 0x4a, 0x17, 0x08, 0x29, 0x36, 0x7e, 0x61, 0x40, 0x5f,
+ 0x02, 0x1d, 0x3c, 0x23, 0x86, 0x99, 0xb8, 0xa7, 0xfa, 0xe5,
+ 0xc4, 0xdb, 0x54, 0x4b, 0x6a, 0x75, 0x28, 0x37, 0x16, 0x09,
+ 0xac, 0xb3, 0x92, 0x8d, 0xd0, 0xcf, 0xee, 0xf1, 0xb9, 0xa6,
+ 0x87, 0x98, 0xc5, 0xda, 0xfb, 0xe4, 0x41, 0x5e, 0x7f, 0x60,
+ 0x3d, 0x22, 0x03, 0x1c, 0x3b, 0x24, 0x05, 0x1a, 0x47, 0x58,
+ 0x79, 0x66, 0xc3, 0xdc, 0xfd, 0xe2, 0xbf, 0xa0, 0x81, 0x9e,
+ 0xd6, 0xc9, 0xe8, 0xf7, 0xaa, 0xb5, 0x94, 0x8b, 0x2e, 0x31,
+ 0x10, 0x0f, 0x52, 0x4d, 0x6c, 0x73, 0xfc, 0xe3, 0xc2, 0xdd,
+ 0x80, 0x9f, 0xbe, 0xa1, 0x04, 0x1b, 0x3a, 0x25, 0x78, 0x67,
+ 0x46, 0x59, 0x11, 0x0e, 0x2f, 0x30, 0x6d, 0x72, 0x53, 0x4c,
+ 0xe9, 0xf6, 0xd7, 0xc8, 0x95, 0x8a, 0xab, 0xb4, 0xa8, 0xb7,
+ 0x96, 0x89, 0xd4, 0xcb, 0xea, 0xf5, 0x50, 0x4f, 0x6e, 0x71,
+ 0x2c, 0x33, 0x12, 0x0d, 0x45, 0x5a, 0x7b, 0x64, 0x39, 0x26,
+ 0x07, 0x18, 0xbd, 0xa2, 0x83, 0x9c, 0xc1, 0xde, 0xff, 0xe0,
+ 0x6f, 0x70, 0x51, 0x4e, 0x13, 0x0c, 0x2d, 0x32, 0x97, 0x88,
+ 0xa9, 0xb6, 0xeb, 0xf4, 0xd5, 0xca, 0x82, 0x9d, 0xbc, 0xa3,
+ 0xfe, 0xe1, 0xc0, 0xdf, 0x7a, 0x65, 0x44, 0x5b, 0x06, 0x19,
+ 0x38, 0x27, 0x00, 0x20, 0x40, 0x60, 0x80, 0xa0, 0xc0, 0xe0,
+ 0x1d, 0x3d, 0x5d, 0x7d, 0x9d, 0xbd, 0xdd, 0xfd, 0x3a, 0x1a,
+ 0x7a, 0x5a, 0xba, 0x9a, 0xfa, 0xda, 0x27, 0x07, 0x67, 0x47,
+ 0xa7, 0x87, 0xe7, 0xc7, 0x74, 0x54, 0x34, 0x14, 0xf4, 0xd4,
+ 0xb4, 0x94, 0x69, 0x49, 0x29, 0x09, 0xe9, 0xc9, 0xa9, 0x89,
+ 0x4e, 0x6e, 0x0e, 0x2e, 0xce, 0xee, 0x8e, 0xae, 0x53, 0x73,
+ 0x13, 0x33, 0xd3, 0xf3, 0x93, 0xb3, 0xe8, 0xc8, 0xa8, 0x88,
+ 0x68, 0x48, 0x28, 0x08, 0xf5, 0xd5, 0xb5, 0x95, 0x75, 0x55,
+ 0x35, 0x15, 0xd2, 0xf2, 0x92, 0xb2, 0x52, 0x72, 0x12, 0x32,
+ 0xcf, 0xef, 0x8f, 0xaf, 0x4f, 0x6f, 0x0f, 0x2f, 0x9c, 0xbc,
+ 0xdc, 0xfc, 0x1c, 0x3c, 0x5c, 0x7c, 0x81, 0xa1, 0xc1, 0xe1,
+ 0x01, 0x21, 0x41, 0x61, 0xa6, 0x86, 0xe6, 0xc6, 0x26, 0x06,
+ 0x66, 0x46, 0xbb, 0x9b, 0xfb, 0xdb, 0x3b, 0x1b, 0x7b, 0x5b,
+ 0xcd, 0xed, 0x8d, 0xad, 0x4d, 0x6d, 0x0d, 0x2d, 0xd0, 0xf0,
+ 0x90, 0xb0, 0x50, 0x70, 0x10, 0x30, 0xf7, 0xd7, 0xb7, 0x97,
+ 0x77, 0x57, 0x37, 0x17, 0xea, 0xca, 0xaa, 0x8a, 0x6a, 0x4a,
+ 0x2a, 0x0a, 0xb9, 0x99, 0xf9, 0xd9, 0x39, 0x19, 0x79, 0x59,
+ 0xa4, 0x84, 0xe4, 0xc4, 0x24, 0x04, 0x64, 0x44, 0x83, 0xa3,
+ 0xc3, 0xe3, 0x03, 0x23, 0x43, 0x63, 0x9e, 0xbe, 0xde, 0xfe,
+ 0x1e, 0x3e, 0x5e, 0x7e, 0x25, 0x05, 0x65, 0x45, 0xa5, 0x85,
+ 0xe5, 0xc5, 0x38, 0x18, 0x78, 0x58, 0xb8, 0x98, 0xf8, 0xd8,
+ 0x1f, 0x3f, 0x5f, 0x7f, 0x9f, 0xbf, 0xdf, 0xff, 0x02, 0x22,
+ 0x42, 0x62, 0x82, 0xa2, 0xc2, 0xe2, 0x51, 0x71, 0x11, 0x31,
+ 0xd1, 0xf1, 0x91, 0xb1, 0x4c, 0x6c, 0x0c, 0x2c, 0xcc, 0xec,
+ 0x8c, 0xac, 0x6b, 0x4b, 0x2b, 0x0b, 0xeb, 0xcb, 0xab, 0x8b,
+ 0x76, 0x56, 0x36, 0x16, 0xf6, 0xd6, 0xb6, 0x96, 0x00, 0x21,
+ 0x42, 0x63, 0x84, 0xa5, 0xc6, 0xe7, 0x15, 0x34, 0x57, 0x76,
+ 0x91, 0xb0, 0xd3, 0xf2, 0x2a, 0x0b, 0x68, 0x49, 0xae, 0x8f,
+ 0xec, 0xcd, 0x3f, 0x1e, 0x7d, 0x5c, 0xbb, 0x9a, 0xf9, 0xd8,
+ 0x54, 0x75, 0x16, 0x37, 0xd0, 0xf1, 0x92, 0xb3, 0x41, 0x60,
+ 0x03, 0x22, 0xc5, 0xe4, 0x87, 0xa6, 0x7e, 0x5f, 0x3c, 0x1d,
+ 0xfa, 0xdb, 0xb8, 0x99, 0x6b, 0x4a, 0x29, 0x08, 0xef, 0xce,
+ 0xad, 0x8c, 0xa8, 0x89, 0xea, 0xcb, 0x2c, 0x0d, 0x6e, 0x4f,
+ 0xbd, 0x9c, 0xff, 0xde, 0x39, 0x18, 0x7b, 0x5a, 0x82, 0xa3,
+ 0xc0, 0xe1, 0x06, 0x27, 0x44, 0x65, 0x97, 0xb6, 0xd5, 0xf4,
+ 0x13, 0x32, 0x51, 0x70, 0xfc, 0xdd, 0xbe, 0x9f, 0x78, 0x59,
+ 0x3a, 0x1b, 0xe9, 0xc8, 0xab, 0x8a, 0x6d, 0x4c, 0x2f, 0x0e,
+ 0xd6, 0xf7, 0x94, 0xb5, 0x52, 0x73, 0x10, 0x31, 0xc3, 0xe2,
+ 0x81, 0xa0, 0x47, 0x66, 0x05, 0x24, 0x4d, 0x6c, 0x0f, 0x2e,
+ 0xc9, 0xe8, 0x8b, 0xaa, 0x58, 0x79, 0x1a, 0x3b, 0xdc, 0xfd,
+ 0x9e, 0xbf, 0x67, 0x46, 0x25, 0x04, 0xe3, 0xc2, 0xa1, 0x80,
+ 0x72, 0x53, 0x30, 0x11, 0xf6, 0xd7, 0xb4, 0x95, 0x19, 0x38,
+ 0x5b, 0x7a, 0x9d, 0xbc, 0xdf, 0xfe, 0x0c, 0x2d, 0x4e, 0x6f,
+ 0x88, 0xa9, 0xca, 0xeb, 0x33, 0x12, 0x71, 0x50, 0xb7, 0x96,
+ 0xf5, 0xd4, 0x26, 0x07, 0x64, 0x45, 0xa2, 0x83, 0xe0, 0xc1,
+ 0xe5, 0xc4, 0xa7, 0x86, 0x61, 0x40, 0x23, 0x02, 0xf0, 0xd1,
+ 0xb2, 0x93, 0x74, 0x55, 0x36, 0x17, 0xcf, 0xee, 0x8d, 0xac,
+ 0x4b, 0x6a, 0x09, 0x28, 0xda, 0xfb, 0x98, 0xb9, 0x5e, 0x7f,
+ 0x1c, 0x3d, 0xb1, 0x90, 0xf3, 0xd2, 0x35, 0x14, 0x77, 0x56,
+ 0xa4, 0x85, 0xe6, 0xc7, 0x20, 0x01, 0x62, 0x43, 0x9b, 0xba,
+ 0xd9, 0xf8, 0x1f, 0x3e, 0x5d, 0x7c, 0x8e, 0xaf, 0xcc, 0xed,
+ 0x0a, 0x2b, 0x48, 0x69, 0x00, 0x22, 0x44, 0x66, 0x88, 0xaa,
+ 0xcc, 0xee, 0x0d, 0x2f, 0x49, 0x6b, 0x85, 0xa7, 0xc1, 0xe3,
+ 0x1a, 0x38, 0x5e, 0x7c, 0x92, 0xb0, 0xd6, 0xf4, 0x17, 0x35,
+ 0x53, 0x71, 0x9f, 0xbd, 0xdb, 0xf9, 0x34, 0x16, 0x70, 0x52,
+ 0xbc, 0x9e, 0xf8, 0xda, 0x39, 0x1b, 0x7d, 0x5f, 0xb1, 0x93,
+ 0xf5, 0xd7, 0x2e, 0x0c, 0x6a, 0x48, 0xa6, 0x84, 0xe2, 0xc0,
+ 0x23, 0x01, 0x67, 0x45, 0xab, 0x89, 0xef, 0xcd, 0x68, 0x4a,
+ 0x2c, 0x0e, 0xe0, 0xc2, 0xa4, 0x86, 0x65, 0x47, 0x21, 0x03,
+ 0xed, 0xcf, 0xa9, 0x8b, 0x72, 0x50, 0x36, 0x14, 0xfa, 0xd8,
+ 0xbe, 0x9c, 0x7f, 0x5d, 0x3b, 0x19, 0xf7, 0xd5, 0xb3, 0x91,
+ 0x5c, 0x7e, 0x18, 0x3a, 0xd4, 0xf6, 0x90, 0xb2, 0x51, 0x73,
+ 0x15, 0x37, 0xd9, 0xfb, 0x9d, 0xbf, 0x46, 0x64, 0x02, 0x20,
+ 0xce, 0xec, 0x8a, 0xa8, 0x4b, 0x69, 0x0f, 0x2d, 0xc3, 0xe1,
+ 0x87, 0xa5, 0xd0, 0xf2, 0x94, 0xb6, 0x58, 0x7a, 0x1c, 0x3e,
+ 0xdd, 0xff, 0x99, 0xbb, 0x55, 0x77, 0x11, 0x33, 0xca, 0xe8,
+ 0x8e, 0xac, 0x42, 0x60, 0x06, 0x24, 0xc7, 0xe5, 0x83, 0xa1,
+ 0x4f, 0x6d, 0x0b, 0x29, 0xe4, 0xc6, 0xa0, 0x82, 0x6c, 0x4e,
+ 0x28, 0x0a, 0xe9, 0xcb, 0xad, 0x8f, 0x61, 0x43, 0x25, 0x07,
+ 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xf3, 0xd1,
+ 0xb7, 0x95, 0x7b, 0x59, 0x3f, 0x1d, 0xb8, 0x9a, 0xfc, 0xde,
+ 0x30, 0x12, 0x74, 0x56, 0xb5, 0x97, 0xf1, 0xd3, 0x3d, 0x1f,
+ 0x79, 0x5b, 0xa2, 0x80, 0xe6, 0xc4, 0x2a, 0x08, 0x6e, 0x4c,
+ 0xaf, 0x8d, 0xeb, 0xc9, 0x27, 0x05, 0x63, 0x41, 0x8c, 0xae,
+ 0xc8, 0xea, 0x04, 0x26, 0x40, 0x62, 0x81, 0xa3, 0xc5, 0xe7,
+ 0x09, 0x2b, 0x4d, 0x6f, 0x96, 0xb4, 0xd2, 0xf0, 0x1e, 0x3c,
+ 0x5a, 0x78, 0x9b, 0xb9, 0xdf, 0xfd, 0x13, 0x31, 0x57, 0x75,
+ 0x00, 0x23, 0x46, 0x65, 0x8c, 0xaf, 0xca, 0xe9, 0x05, 0x26,
+ 0x43, 0x60, 0x89, 0xaa, 0xcf, 0xec, 0x0a, 0x29, 0x4c, 0x6f,
+ 0x86, 0xa5, 0xc0, 0xe3, 0x0f, 0x2c, 0x49, 0x6a, 0x83, 0xa0,
+ 0xc5, 0xe6, 0x14, 0x37, 0x52, 0x71, 0x98, 0xbb, 0xde, 0xfd,
+ 0x11, 0x32, 0x57, 0x74, 0x9d, 0xbe, 0xdb, 0xf8, 0x1e, 0x3d,
+ 0x58, 0x7b, 0x92, 0xb1, 0xd4, 0xf7, 0x1b, 0x38, 0x5d, 0x7e,
+ 0x97, 0xb4, 0xd1, 0xf2, 0x28, 0x0b, 0x6e, 0x4d, 0xa4, 0x87,
+ 0xe2, 0xc1, 0x2d, 0x0e, 0x6b, 0x48, 0xa1, 0x82, 0xe7, 0xc4,
+ 0x22, 0x01, 0x64, 0x47, 0xae, 0x8d, 0xe8, 0xcb, 0x27, 0x04,
+ 0x61, 0x42, 0xab, 0x88, 0xed, 0xce, 0x3c, 0x1f, 0x7a, 0x59,
+ 0xb0, 0x93, 0xf6, 0xd5, 0x39, 0x1a, 0x7f, 0x5c, 0xb5, 0x96,
+ 0xf3, 0xd0, 0x36, 0x15, 0x70, 0x53, 0xba, 0x99, 0xfc, 0xdf,
+ 0x33, 0x10, 0x75, 0x56, 0xbf, 0x9c, 0xf9, 0xda, 0x50, 0x73,
+ 0x16, 0x35, 0xdc, 0xff, 0x9a, 0xb9, 0x55, 0x76, 0x13, 0x30,
+ 0xd9, 0xfa, 0x9f, 0xbc, 0x5a, 0x79, 0x1c, 0x3f, 0xd6, 0xf5,
+ 0x90, 0xb3, 0x5f, 0x7c, 0x19, 0x3a, 0xd3, 0xf0, 0x95, 0xb6,
+ 0x44, 0x67, 0x02, 0x21, 0xc8, 0xeb, 0x8e, 0xad, 0x41, 0x62,
+ 0x07, 0x24, 0xcd, 0xee, 0x8b, 0xa8, 0x4e, 0x6d, 0x08, 0x2b,
+ 0xc2, 0xe1, 0x84, 0xa7, 0x4b, 0x68, 0x0d, 0x2e, 0xc7, 0xe4,
+ 0x81, 0xa2, 0x78, 0x5b, 0x3e, 0x1d, 0xf4, 0xd7, 0xb2, 0x91,
+ 0x7d, 0x5e, 0x3b, 0x18, 0xf1, 0xd2, 0xb7, 0x94, 0x72, 0x51,
+ 0x34, 0x17, 0xfe, 0xdd, 0xb8, 0x9b, 0x77, 0x54, 0x31, 0x12,
+ 0xfb, 0xd8, 0xbd, 0x9e, 0x6c, 0x4f, 0x2a, 0x09, 0xe0, 0xc3,
+ 0xa6, 0x85, 0x69, 0x4a, 0x2f, 0x0c, 0xe5, 0xc6, 0xa3, 0x80,
+ 0x66, 0x45, 0x20, 0x03, 0xea, 0xc9, 0xac, 0x8f, 0x63, 0x40,
+ 0x25, 0x06, 0xef, 0xcc, 0xa9, 0x8a, 0x00, 0x24, 0x48, 0x6c,
+ 0x90, 0xb4, 0xd8, 0xfc, 0x3d, 0x19, 0x75, 0x51, 0xad, 0x89,
+ 0xe5, 0xc1, 0x7a, 0x5e, 0x32, 0x16, 0xea, 0xce, 0xa2, 0x86,
+ 0x47, 0x63, 0x0f, 0x2b, 0xd7, 0xf3, 0x9f, 0xbb, 0xf4, 0xd0,
+ 0xbc, 0x98, 0x64, 0x40, 0x2c, 0x08, 0xc9, 0xed, 0x81, 0xa5,
+ 0x59, 0x7d, 0x11, 0x35, 0x8e, 0xaa, 0xc6, 0xe2, 0x1e, 0x3a,
+ 0x56, 0x72, 0xb3, 0x97, 0xfb, 0xdf, 0x23, 0x07, 0x6b, 0x4f,
+ 0xf5, 0xd1, 0xbd, 0x99, 0x65, 0x41, 0x2d, 0x09, 0xc8, 0xec,
+ 0x80, 0xa4, 0x58, 0x7c, 0x10, 0x34, 0x8f, 0xab, 0xc7, 0xe3,
+ 0x1f, 0x3b, 0x57, 0x73, 0xb2, 0x96, 0xfa, 0xde, 0x22, 0x06,
+ 0x6a, 0x4e, 0x01, 0x25, 0x49, 0x6d, 0x91, 0xb5, 0xd9, 0xfd,
+ 0x3c, 0x18, 0x74, 0x50, 0xac, 0x88, 0xe4, 0xc0, 0x7b, 0x5f,
+ 0x33, 0x17, 0xeb, 0xcf, 0xa3, 0x87, 0x46, 0x62, 0x0e, 0x2a,
+ 0xd6, 0xf2, 0x9e, 0xba, 0xf7, 0xd3, 0xbf, 0x9b, 0x67, 0x43,
+ 0x2f, 0x0b, 0xca, 0xee, 0x82, 0xa6, 0x5a, 0x7e, 0x12, 0x36,
+ 0x8d, 0xa9, 0xc5, 0xe1, 0x1d, 0x39, 0x55, 0x71, 0xb0, 0x94,
+ 0xf8, 0xdc, 0x20, 0x04, 0x68, 0x4c, 0x03, 0x27, 0x4b, 0x6f,
+ 0x93, 0xb7, 0xdb, 0xff, 0x3e, 0x1a, 0x76, 0x52, 0xae, 0x8a,
+ 0xe6, 0xc2, 0x79, 0x5d, 0x31, 0x15, 0xe9, 0xcd, 0xa1, 0x85,
+ 0x44, 0x60, 0x0c, 0x28, 0xd4, 0xf0, 0x9c, 0xb8, 0x02, 0x26,
+ 0x4a, 0x6e, 0x92, 0xb6, 0xda, 0xfe, 0x3f, 0x1b, 0x77, 0x53,
+ 0xaf, 0x8b, 0xe7, 0xc3, 0x78, 0x5c, 0x30, 0x14, 0xe8, 0xcc,
+ 0xa0, 0x84, 0x45, 0x61, 0x0d, 0x29, 0xd5, 0xf1, 0x9d, 0xb9,
+ 0xf6, 0xd2, 0xbe, 0x9a, 0x66, 0x42, 0x2e, 0x0a, 0xcb, 0xef,
+ 0x83, 0xa7, 0x5b, 0x7f, 0x13, 0x37, 0x8c, 0xa8, 0xc4, 0xe0,
+ 0x1c, 0x38, 0x54, 0x70, 0xb1, 0x95, 0xf9, 0xdd, 0x21, 0x05,
+ 0x69, 0x4d, 0x00, 0x25, 0x4a, 0x6f, 0x94, 0xb1, 0xde, 0xfb,
+ 0x35, 0x10, 0x7f, 0x5a, 0xa1, 0x84, 0xeb, 0xce, 0x6a, 0x4f,
+ 0x20, 0x05, 0xfe, 0xdb, 0xb4, 0x91, 0x5f, 0x7a, 0x15, 0x30,
+ 0xcb, 0xee, 0x81, 0xa4, 0xd4, 0xf1, 0x9e, 0xbb, 0x40, 0x65,
+ 0x0a, 0x2f, 0xe1, 0xc4, 0xab, 0x8e, 0x75, 0x50, 0x3f, 0x1a,
+ 0xbe, 0x9b, 0xf4, 0xd1, 0x2a, 0x0f, 0x60, 0x45, 0x8b, 0xae,
+ 0xc1, 0xe4, 0x1f, 0x3a, 0x55, 0x70, 0xb5, 0x90, 0xff, 0xda,
+ 0x21, 0x04, 0x6b, 0x4e, 0x80, 0xa5, 0xca, 0xef, 0x14, 0x31,
+ 0x5e, 0x7b, 0xdf, 0xfa, 0x95, 0xb0, 0x4b, 0x6e, 0x01, 0x24,
+ 0xea, 0xcf, 0xa0, 0x85, 0x7e, 0x5b, 0x34, 0x11, 0x61, 0x44,
+ 0x2b, 0x0e, 0xf5, 0xd0, 0xbf, 0x9a, 0x54, 0x71, 0x1e, 0x3b,
+ 0xc0, 0xe5, 0x8a, 0xaf, 0x0b, 0x2e, 0x41, 0x64, 0x9f, 0xba,
+ 0xd5, 0xf0, 0x3e, 0x1b, 0x74, 0x51, 0xaa, 0x8f, 0xe0, 0xc5,
+ 0x77, 0x52, 0x3d, 0x18, 0xe3, 0xc6, 0xa9, 0x8c, 0x42, 0x67,
+ 0x08, 0x2d, 0xd6, 0xf3, 0x9c, 0xb9, 0x1d, 0x38, 0x57, 0x72,
+ 0x89, 0xac, 0xc3, 0xe6, 0x28, 0x0d, 0x62, 0x47, 0xbc, 0x99,
+ 0xf6, 0xd3, 0xa3, 0x86, 0xe9, 0xcc, 0x37, 0x12, 0x7d, 0x58,
+ 0x96, 0xb3, 0xdc, 0xf9, 0x02, 0x27, 0x48, 0x6d, 0xc9, 0xec,
+ 0x83, 0xa6, 0x5d, 0x78, 0x17, 0x32, 0xfc, 0xd9, 0xb6, 0x93,
+ 0x68, 0x4d, 0x22, 0x07, 0xc2, 0xe7, 0x88, 0xad, 0x56, 0x73,
+ 0x1c, 0x39, 0xf7, 0xd2, 0xbd, 0x98, 0x63, 0x46, 0x29, 0x0c,
+ 0xa8, 0x8d, 0xe2, 0xc7, 0x3c, 0x19, 0x76, 0x53, 0x9d, 0xb8,
+ 0xd7, 0xf2, 0x09, 0x2c, 0x43, 0x66, 0x16, 0x33, 0x5c, 0x79,
+ 0x82, 0xa7, 0xc8, 0xed, 0x23, 0x06, 0x69, 0x4c, 0xb7, 0x92,
+ 0xfd, 0xd8, 0x7c, 0x59, 0x36, 0x13, 0xe8, 0xcd, 0xa2, 0x87,
+ 0x49, 0x6c, 0x03, 0x26, 0xdd, 0xf8, 0x97, 0xb2, 0x00, 0x26,
+ 0x4c, 0x6a, 0x98, 0xbe, 0xd4, 0xf2, 0x2d, 0x0b, 0x61, 0x47,
+ 0xb5, 0x93, 0xf9, 0xdf, 0x5a, 0x7c, 0x16, 0x30, 0xc2, 0xe4,
+ 0x8e, 0xa8, 0x77, 0x51, 0x3b, 0x1d, 0xef, 0xc9, 0xa3, 0x85,
+ 0xb4, 0x92, 0xf8, 0xde, 0x2c, 0x0a, 0x60, 0x46, 0x99, 0xbf,
+ 0xd5, 0xf3, 0x01, 0x27, 0x4d, 0x6b, 0xee, 0xc8, 0xa2, 0x84,
+ 0x76, 0x50, 0x3a, 0x1c, 0xc3, 0xe5, 0x8f, 0xa9, 0x5b, 0x7d,
+ 0x17, 0x31, 0x75, 0x53, 0x39, 0x1f, 0xed, 0xcb, 0xa1, 0x87,
+ 0x58, 0x7e, 0x14, 0x32, 0xc0, 0xe6, 0x8c, 0xaa, 0x2f, 0x09,
+ 0x63, 0x45, 0xb7, 0x91, 0xfb, 0xdd, 0x02, 0x24, 0x4e, 0x68,
+ 0x9a, 0xbc, 0xd6, 0xf0, 0xc1, 0xe7, 0x8d, 0xab, 0x59, 0x7f,
+ 0x15, 0x33, 0xec, 0xca, 0xa0, 0x86, 0x74, 0x52, 0x38, 0x1e,
+ 0x9b, 0xbd, 0xd7, 0xf1, 0x03, 0x25, 0x4f, 0x69, 0xb6, 0x90,
+ 0xfa, 0xdc, 0x2e, 0x08, 0x62, 0x44, 0xea, 0xcc, 0xa6, 0x80,
+ 0x72, 0x54, 0x3e, 0x18, 0xc7, 0xe1, 0x8b, 0xad, 0x5f, 0x79,
+ 0x13, 0x35, 0xb0, 0x96, 0xfc, 0xda, 0x28, 0x0e, 0x64, 0x42,
+ 0x9d, 0xbb, 0xd1, 0xf7, 0x05, 0x23, 0x49, 0x6f, 0x5e, 0x78,
+ 0x12, 0x34, 0xc6, 0xe0, 0x8a, 0xac, 0x73, 0x55, 0x3f, 0x19,
+ 0xeb, 0xcd, 0xa7, 0x81, 0x04, 0x22, 0x48, 0x6e, 0x9c, 0xba,
+ 0xd0, 0xf6, 0x29, 0x0f, 0x65, 0x43, 0xb1, 0x97, 0xfd, 0xdb,
+ 0x9f, 0xb9, 0xd3, 0xf5, 0x07, 0x21, 0x4b, 0x6d, 0xb2, 0x94,
+ 0xfe, 0xd8, 0x2a, 0x0c, 0x66, 0x40, 0xc5, 0xe3, 0x89, 0xaf,
+ 0x5d, 0x7b, 0x11, 0x37, 0xe8, 0xce, 0xa4, 0x82, 0x70, 0x56,
+ 0x3c, 0x1a, 0x2b, 0x0d, 0x67, 0x41, 0xb3, 0x95, 0xff, 0xd9,
+ 0x06, 0x20, 0x4a, 0x6c, 0x9e, 0xb8, 0xd2, 0xf4, 0x71, 0x57,
+ 0x3d, 0x1b, 0xe9, 0xcf, 0xa5, 0x83, 0x5c, 0x7a, 0x10, 0x36,
+ 0xc4, 0xe2, 0x88, 0xae, 0x00, 0x27, 0x4e, 0x69, 0x9c, 0xbb,
+ 0xd2, 0xf5, 0x25, 0x02, 0x6b, 0x4c, 0xb9, 0x9e, 0xf7, 0xd0,
+ 0x4a, 0x6d, 0x04, 0x23, 0xd6, 0xf1, 0x98, 0xbf, 0x6f, 0x48,
+ 0x21, 0x06, 0xf3, 0xd4, 0xbd, 0x9a, 0x94, 0xb3, 0xda, 0xfd,
+ 0x08, 0x2f, 0x46, 0x61, 0xb1, 0x96, 0xff, 0xd8, 0x2d, 0x0a,
+ 0x63, 0x44, 0xde, 0xf9, 0x90, 0xb7, 0x42, 0x65, 0x0c, 0x2b,
+ 0xfb, 0xdc, 0xb5, 0x92, 0x67, 0x40, 0x29, 0x0e, 0x35, 0x12,
+ 0x7b, 0x5c, 0xa9, 0x8e, 0xe7, 0xc0, 0x10, 0x37, 0x5e, 0x79,
+ 0x8c, 0xab, 0xc2, 0xe5, 0x7f, 0x58, 0x31, 0x16, 0xe3, 0xc4,
+ 0xad, 0x8a, 0x5a, 0x7d, 0x14, 0x33, 0xc6, 0xe1, 0x88, 0xaf,
+ 0xa1, 0x86, 0xef, 0xc8, 0x3d, 0x1a, 0x73, 0x54, 0x84, 0xa3,
+ 0xca, 0xed, 0x18, 0x3f, 0x56, 0x71, 0xeb, 0xcc, 0xa5, 0x82,
+ 0x77, 0x50, 0x39, 0x1e, 0xce, 0xe9, 0x80, 0xa7, 0x52, 0x75,
+ 0x1c, 0x3b, 0x6a, 0x4d, 0x24, 0x03, 0xf6, 0xd1, 0xb8, 0x9f,
+ 0x4f, 0x68, 0x01, 0x26, 0xd3, 0xf4, 0x9d, 0xba, 0x20, 0x07,
+ 0x6e, 0x49, 0xbc, 0x9b, 0xf2, 0xd5, 0x05, 0x22, 0x4b, 0x6c,
+ 0x99, 0xbe, 0xd7, 0xf0, 0xfe, 0xd9, 0xb0, 0x97, 0x62, 0x45,
+ 0x2c, 0x0b, 0xdb, 0xfc, 0x95, 0xb2, 0x47, 0x60, 0x09, 0x2e,
+ 0xb4, 0x93, 0xfa, 0xdd, 0x28, 0x0f, 0x66, 0x41, 0x91, 0xb6,
+ 0xdf, 0xf8, 0x0d, 0x2a, 0x43, 0x64, 0x5f, 0x78, 0x11, 0x36,
+ 0xc3, 0xe4, 0x8d, 0xaa, 0x7a, 0x5d, 0x34, 0x13, 0xe6, 0xc1,
+ 0xa8, 0x8f, 0x15, 0x32, 0x5b, 0x7c, 0x89, 0xae, 0xc7, 0xe0,
+ 0x30, 0x17, 0x7e, 0x59, 0xac, 0x8b, 0xe2, 0xc5, 0xcb, 0xec,
+ 0x85, 0xa2, 0x57, 0x70, 0x19, 0x3e, 0xee, 0xc9, 0xa0, 0x87,
+ 0x72, 0x55, 0x3c, 0x1b, 0x81, 0xa6, 0xcf, 0xe8, 0x1d, 0x3a,
+ 0x53, 0x74, 0xa4, 0x83, 0xea, 0xcd, 0x38, 0x1f, 0x76, 0x51,
+ 0x00, 0x28, 0x50, 0x78, 0xa0, 0x88, 0xf0, 0xd8, 0x5d, 0x75,
+ 0x0d, 0x25, 0xfd, 0xd5, 0xad, 0x85, 0xba, 0x92, 0xea, 0xc2,
+ 0x1a, 0x32, 0x4a, 0x62, 0xe7, 0xcf, 0xb7, 0x9f, 0x47, 0x6f,
+ 0x17, 0x3f, 0x69, 0x41, 0x39, 0x11, 0xc9, 0xe1, 0x99, 0xb1,
+ 0x34, 0x1c, 0x64, 0x4c, 0x94, 0xbc, 0xc4, 0xec, 0xd3, 0xfb,
+ 0x83, 0xab, 0x73, 0x5b, 0x23, 0x0b, 0x8e, 0xa6, 0xde, 0xf6,
+ 0x2e, 0x06, 0x7e, 0x56, 0xd2, 0xfa, 0x82, 0xaa, 0x72, 0x5a,
+ 0x22, 0x0a, 0x8f, 0xa7, 0xdf, 0xf7, 0x2f, 0x07, 0x7f, 0x57,
+ 0x68, 0x40, 0x38, 0x10, 0xc8, 0xe0, 0x98, 0xb0, 0x35, 0x1d,
+ 0x65, 0x4d, 0x95, 0xbd, 0xc5, 0xed, 0xbb, 0x93, 0xeb, 0xc3,
+ 0x1b, 0x33, 0x4b, 0x63, 0xe6, 0xce, 0xb6, 0x9e, 0x46, 0x6e,
+ 0x16, 0x3e, 0x01, 0x29, 0x51, 0x79, 0xa1, 0x89, 0xf1, 0xd9,
+ 0x5c, 0x74, 0x0c, 0x24, 0xfc, 0xd4, 0xac, 0x84, 0xb9, 0x91,
+ 0xe9, 0xc1, 0x19, 0x31, 0x49, 0x61, 0xe4, 0xcc, 0xb4, 0x9c,
+ 0x44, 0x6c, 0x14, 0x3c, 0x03, 0x2b, 0x53, 0x7b, 0xa3, 0x8b,
+ 0xf3, 0xdb, 0x5e, 0x76, 0x0e, 0x26, 0xfe, 0xd6, 0xae, 0x86,
+ 0xd0, 0xf8, 0x80, 0xa8, 0x70, 0x58, 0x20, 0x08, 0x8d, 0xa5,
+ 0xdd, 0xf5, 0x2d, 0x05, 0x7d, 0x55, 0x6a, 0x42, 0x3a, 0x12,
+ 0xca, 0xe2, 0x9a, 0xb2, 0x37, 0x1f, 0x67, 0x4f, 0x97, 0xbf,
+ 0xc7, 0xef, 0x6b, 0x43, 0x3b, 0x13, 0xcb, 0xe3, 0x9b, 0xb3,
+ 0x36, 0x1e, 0x66, 0x4e, 0x96, 0xbe, 0xc6, 0xee, 0xd1, 0xf9,
+ 0x81, 0xa9, 0x71, 0x59, 0x21, 0x09, 0x8c, 0xa4, 0xdc, 0xf4,
+ 0x2c, 0x04, 0x7c, 0x54, 0x02, 0x2a, 0x52, 0x7a, 0xa2, 0x8a,
+ 0xf2, 0xda, 0x5f, 0x77, 0x0f, 0x27, 0xff, 0xd7, 0xaf, 0x87,
+ 0xb8, 0x90, 0xe8, 0xc0, 0x18, 0x30, 0x48, 0x60, 0xe5, 0xcd,
+ 0xb5, 0x9d, 0x45, 0x6d, 0x15, 0x3d, 0x00, 0x29, 0x52, 0x7b,
+ 0xa4, 0x8d, 0xf6, 0xdf, 0x55, 0x7c, 0x07, 0x2e, 0xf1, 0xd8,
+ 0xa3, 0x8a, 0xaa, 0x83, 0xf8, 0xd1, 0x0e, 0x27, 0x5c, 0x75,
+ 0xff, 0xd6, 0xad, 0x84, 0x5b, 0x72, 0x09, 0x20, 0x49, 0x60,
+ 0x1b, 0x32, 0xed, 0xc4, 0xbf, 0x96, 0x1c, 0x35, 0x4e, 0x67,
+ 0xb8, 0x91, 0xea, 0xc3, 0xe3, 0xca, 0xb1, 0x98, 0x47, 0x6e,
+ 0x15, 0x3c, 0xb6, 0x9f, 0xe4, 0xcd, 0x12, 0x3b, 0x40, 0x69,
+ 0x92, 0xbb, 0xc0, 0xe9, 0x36, 0x1f, 0x64, 0x4d, 0xc7, 0xee,
+ 0x95, 0xbc, 0x63, 0x4a, 0x31, 0x18, 0x38, 0x11, 0x6a, 0x43,
+ 0x9c, 0xb5, 0xce, 0xe7, 0x6d, 0x44, 0x3f, 0x16, 0xc9, 0xe0,
+ 0x9b, 0xb2, 0xdb, 0xf2, 0x89, 0xa0, 0x7f, 0x56, 0x2d, 0x04,
+ 0x8e, 0xa7, 0xdc, 0xf5, 0x2a, 0x03, 0x78, 0x51, 0x71, 0x58,
+ 0x23, 0x0a, 0xd5, 0xfc, 0x87, 0xae, 0x24, 0x0d, 0x76, 0x5f,
+ 0x80, 0xa9, 0xd2, 0xfb, 0x39, 0x10, 0x6b, 0x42, 0x9d, 0xb4,
+ 0xcf, 0xe6, 0x6c, 0x45, 0x3e, 0x17, 0xc8, 0xe1, 0x9a, 0xb3,
+ 0x93, 0xba, 0xc1, 0xe8, 0x37, 0x1e, 0x65, 0x4c, 0xc6, 0xef,
+ 0x94, 0xbd, 0x62, 0x4b, 0x30, 0x19, 0x70, 0x59, 0x22, 0x0b,
+ 0xd4, 0xfd, 0x86, 0xaf, 0x25, 0x0c, 0x77, 0x5e, 0x81, 0xa8,
+ 0xd3, 0xfa, 0xda, 0xf3, 0x88, 0xa1, 0x7e, 0x57, 0x2c, 0x05,
+ 0x8f, 0xa6, 0xdd, 0xf4, 0x2b, 0x02, 0x79, 0x50, 0xab, 0x82,
+ 0xf9, 0xd0, 0x0f, 0x26, 0x5d, 0x74, 0xfe, 0xd7, 0xac, 0x85,
+ 0x5a, 0x73, 0x08, 0x21, 0x01, 0x28, 0x53, 0x7a, 0xa5, 0x8c,
+ 0xf7, 0xde, 0x54, 0x7d, 0x06, 0x2f, 0xf0, 0xd9, 0xa2, 0x8b,
+ 0xe2, 0xcb, 0xb0, 0x99, 0x46, 0x6f, 0x14, 0x3d, 0xb7, 0x9e,
+ 0xe5, 0xcc, 0x13, 0x3a, 0x41, 0x68, 0x48, 0x61, 0x1a, 0x33,
+ 0xec, 0xc5, 0xbe, 0x97, 0x1d, 0x34, 0x4f, 0x66, 0xb9, 0x90,
+ 0xeb, 0xc2, 0x00, 0x2a, 0x54, 0x7e, 0xa8, 0x82, 0xfc, 0xd6,
+ 0x4d, 0x67, 0x19, 0x33, 0xe5, 0xcf, 0xb1, 0x9b, 0x9a, 0xb0,
+ 0xce, 0xe4, 0x32, 0x18, 0x66, 0x4c, 0xd7, 0xfd, 0x83, 0xa9,
+ 0x7f, 0x55, 0x2b, 0x01, 0x29, 0x03, 0x7d, 0x57, 0x81, 0xab,
+ 0xd5, 0xff, 0x64, 0x4e, 0x30, 0x1a, 0xcc, 0xe6, 0x98, 0xb2,
+ 0xb3, 0x99, 0xe7, 0xcd, 0x1b, 0x31, 0x4f, 0x65, 0xfe, 0xd4,
+ 0xaa, 0x80, 0x56, 0x7c, 0x02, 0x28, 0x52, 0x78, 0x06, 0x2c,
+ 0xfa, 0xd0, 0xae, 0x84, 0x1f, 0x35, 0x4b, 0x61, 0xb7, 0x9d,
+ 0xe3, 0xc9, 0xc8, 0xe2, 0x9c, 0xb6, 0x60, 0x4a, 0x34, 0x1e,
+ 0x85, 0xaf, 0xd1, 0xfb, 0x2d, 0x07, 0x79, 0x53, 0x7b, 0x51,
+ 0x2f, 0x05, 0xd3, 0xf9, 0x87, 0xad, 0x36, 0x1c, 0x62, 0x48,
+ 0x9e, 0xb4, 0xca, 0xe0, 0xe1, 0xcb, 0xb5, 0x9f, 0x49, 0x63,
+ 0x1d, 0x37, 0xac, 0x86, 0xf8, 0xd2, 0x04, 0x2e, 0x50, 0x7a,
+ 0xa4, 0x8e, 0xf0, 0xda, 0x0c, 0x26, 0x58, 0x72, 0xe9, 0xc3,
+ 0xbd, 0x97, 0x41, 0x6b, 0x15, 0x3f, 0x3e, 0x14, 0x6a, 0x40,
+ 0x96, 0xbc, 0xc2, 0xe8, 0x73, 0x59, 0x27, 0x0d, 0xdb, 0xf1,
+ 0x8f, 0xa5, 0x8d, 0xa7, 0xd9, 0xf3, 0x25, 0x0f, 0x71, 0x5b,
+ 0xc0, 0xea, 0x94, 0xbe, 0x68, 0x42, 0x3c, 0x16, 0x17, 0x3d,
+ 0x43, 0x69, 0xbf, 0x95, 0xeb, 0xc1, 0x5a, 0x70, 0x0e, 0x24,
+ 0xf2, 0xd8, 0xa6, 0x8c, 0xf6, 0xdc, 0xa2, 0x88, 0x5e, 0x74,
+ 0x0a, 0x20, 0xbb, 0x91, 0xef, 0xc5, 0x13, 0x39, 0x47, 0x6d,
+ 0x6c, 0x46, 0x38, 0x12, 0xc4, 0xee, 0x90, 0xba, 0x21, 0x0b,
+ 0x75, 0x5f, 0x89, 0xa3, 0xdd, 0xf7, 0xdf, 0xf5, 0x8b, 0xa1,
+ 0x77, 0x5d, 0x23, 0x09, 0x92, 0xb8, 0xc6, 0xec, 0x3a, 0x10,
+ 0x6e, 0x44, 0x45, 0x6f, 0x11, 0x3b, 0xed, 0xc7, 0xb9, 0x93,
+ 0x08, 0x22, 0x5c, 0x76, 0xa0, 0x8a, 0xf4, 0xde, 0x00, 0x2b,
+ 0x56, 0x7d, 0xac, 0x87, 0xfa, 0xd1, 0x45, 0x6e, 0x13, 0x38,
+ 0xe9, 0xc2, 0xbf, 0x94, 0x8a, 0xa1, 0xdc, 0xf7, 0x26, 0x0d,
+ 0x70, 0x5b, 0xcf, 0xe4, 0x99, 0xb2, 0x63, 0x48, 0x35, 0x1e,
+ 0x09, 0x22, 0x5f, 0x74, 0xa5, 0x8e, 0xf3, 0xd8, 0x4c, 0x67,
+ 0x1a, 0x31, 0xe0, 0xcb, 0xb6, 0x9d, 0x83, 0xa8, 0xd5, 0xfe,
+ 0x2f, 0x04, 0x79, 0x52, 0xc6, 0xed, 0x90, 0xbb, 0x6a, 0x41,
+ 0x3c, 0x17, 0x12, 0x39, 0x44, 0x6f, 0xbe, 0x95, 0xe8, 0xc3,
+ 0x57, 0x7c, 0x01, 0x2a, 0xfb, 0xd0, 0xad, 0x86, 0x98, 0xb3,
+ 0xce, 0xe5, 0x34, 0x1f, 0x62, 0x49, 0xdd, 0xf6, 0x8b, 0xa0,
+ 0x71, 0x5a, 0x27, 0x0c, 0x1b, 0x30, 0x4d, 0x66, 0xb7, 0x9c,
+ 0xe1, 0xca, 0x5e, 0x75, 0x08, 0x23, 0xf2, 0xd9, 0xa4, 0x8f,
+ 0x91, 0xba, 0xc7, 0xec, 0x3d, 0x16, 0x6b, 0x40, 0xd4, 0xff,
+ 0x82, 0xa9, 0x78, 0x53, 0x2e, 0x05, 0x24, 0x0f, 0x72, 0x59,
+ 0x88, 0xa3, 0xde, 0xf5, 0x61, 0x4a, 0x37, 0x1c, 0xcd, 0xe6,
+ 0x9b, 0xb0, 0xae, 0x85, 0xf8, 0xd3, 0x02, 0x29, 0x54, 0x7f,
+ 0xeb, 0xc0, 0xbd, 0x96, 0x47, 0x6c, 0x11, 0x3a, 0x2d, 0x06,
+ 0x7b, 0x50, 0x81, 0xaa, 0xd7, 0xfc, 0x68, 0x43, 0x3e, 0x15,
+ 0xc4, 0xef, 0x92, 0xb9, 0xa7, 0x8c, 0xf1, 0xda, 0x0b, 0x20,
+ 0x5d, 0x76, 0xe2, 0xc9, 0xb4, 0x9f, 0x4e, 0x65, 0x18, 0x33,
+ 0x36, 0x1d, 0x60, 0x4b, 0x9a, 0xb1, 0xcc, 0xe7, 0x73, 0x58,
+ 0x25, 0x0e, 0xdf, 0xf4, 0x89, 0xa2, 0xbc, 0x97, 0xea, 0xc1,
+ 0x10, 0x3b, 0x46, 0x6d, 0xf9, 0xd2, 0xaf, 0x84, 0x55, 0x7e,
+ 0x03, 0x28, 0x3f, 0x14, 0x69, 0x42, 0x93, 0xb8, 0xc5, 0xee,
+ 0x7a, 0x51, 0x2c, 0x07, 0xd6, 0xfd, 0x80, 0xab, 0xb5, 0x9e,
+ 0xe3, 0xc8, 0x19, 0x32, 0x4f, 0x64, 0xf0, 0xdb, 0xa6, 0x8d,
+ 0x5c, 0x77, 0x0a, 0x21, 0x00, 0x2c, 0x58, 0x74, 0xb0, 0x9c,
+ 0xe8, 0xc4, 0x7d, 0x51, 0x25, 0x09, 0xcd, 0xe1, 0x95, 0xb9,
+ 0xfa, 0xd6, 0xa2, 0x8e, 0x4a, 0x66, 0x12, 0x3e, 0x87, 0xab,
+ 0xdf, 0xf3, 0x37, 0x1b, 0x6f, 0x43, 0xe9, 0xc5, 0xb1, 0x9d,
+ 0x59, 0x75, 0x01, 0x2d, 0x94, 0xb8, 0xcc, 0xe0, 0x24, 0x08,
+ 0x7c, 0x50, 0x13, 0x3f, 0x4b, 0x67, 0xa3, 0x8f, 0xfb, 0xd7,
+ 0x6e, 0x42, 0x36, 0x1a, 0xde, 0xf2, 0x86, 0xaa, 0xcf, 0xe3,
+ 0x97, 0xbb, 0x7f, 0x53, 0x27, 0x0b, 0xb2, 0x9e, 0xea, 0xc6,
+ 0x02, 0x2e, 0x5a, 0x76, 0x35, 0x19, 0x6d, 0x41, 0x85, 0xa9,
+ 0xdd, 0xf1, 0x48, 0x64, 0x10, 0x3c, 0xf8, 0xd4, 0xa0, 0x8c,
+ 0x26, 0x0a, 0x7e, 0x52, 0x96, 0xba, 0xce, 0xe2, 0x5b, 0x77,
+ 0x03, 0x2f, 0xeb, 0xc7, 0xb3, 0x9f, 0xdc, 0xf0, 0x84, 0xa8,
+ 0x6c, 0x40, 0x34, 0x18, 0xa1, 0x8d, 0xf9, 0xd5, 0x11, 0x3d,
+ 0x49, 0x65, 0x83, 0xaf, 0xdb, 0xf7, 0x33, 0x1f, 0x6b, 0x47,
+ 0xfe, 0xd2, 0xa6, 0x8a, 0x4e, 0x62, 0x16, 0x3a, 0x79, 0x55,
+ 0x21, 0x0d, 0xc9, 0xe5, 0x91, 0xbd, 0x04, 0x28, 0x5c, 0x70,
+ 0xb4, 0x98, 0xec, 0xc0, 0x6a, 0x46, 0x32, 0x1e, 0xda, 0xf6,
+ 0x82, 0xae, 0x17, 0x3b, 0x4f, 0x63, 0xa7, 0x8b, 0xff, 0xd3,
+ 0x90, 0xbc, 0xc8, 0xe4, 0x20, 0x0c, 0x78, 0x54, 0xed, 0xc1,
+ 0xb5, 0x99, 0x5d, 0x71, 0x05, 0x29, 0x4c, 0x60, 0x14, 0x38,
+ 0xfc, 0xd0, 0xa4, 0x88, 0x31, 0x1d, 0x69, 0x45, 0x81, 0xad,
+ 0xd9, 0xf5, 0xb6, 0x9a, 0xee, 0xc2, 0x06, 0x2a, 0x5e, 0x72,
+ 0xcb, 0xe7, 0x93, 0xbf, 0x7b, 0x57, 0x23, 0x0f, 0xa5, 0x89,
+ 0xfd, 0xd1, 0x15, 0x39, 0x4d, 0x61, 0xd8, 0xf4, 0x80, 0xac,
+ 0x68, 0x44, 0x30, 0x1c, 0x5f, 0x73, 0x07, 0x2b, 0xef, 0xc3,
+ 0xb7, 0x9b, 0x22, 0x0e, 0x7a, 0x56, 0x92, 0xbe, 0xca, 0xe6,
+ 0x00, 0x2d, 0x5a, 0x77, 0xb4, 0x99, 0xee, 0xc3, 0x75, 0x58,
+ 0x2f, 0x02, 0xc1, 0xec, 0x9b, 0xb6, 0xea, 0xc7, 0xb0, 0x9d,
+ 0x5e, 0x73, 0x04, 0x29, 0x9f, 0xb2, 0xc5, 0xe8, 0x2b, 0x06,
+ 0x71, 0x5c, 0xc9, 0xe4, 0x93, 0xbe, 0x7d, 0x50, 0x27, 0x0a,
+ 0xbc, 0x91, 0xe6, 0xcb, 0x08, 0x25, 0x52, 0x7f, 0x23, 0x0e,
+ 0x79, 0x54, 0x97, 0xba, 0xcd, 0xe0, 0x56, 0x7b, 0x0c, 0x21,
+ 0xe2, 0xcf, 0xb8, 0x95, 0x8f, 0xa2, 0xd5, 0xf8, 0x3b, 0x16,
+ 0x61, 0x4c, 0xfa, 0xd7, 0xa0, 0x8d, 0x4e, 0x63, 0x14, 0x39,
+ 0x65, 0x48, 0x3f, 0x12, 0xd1, 0xfc, 0x8b, 0xa6, 0x10, 0x3d,
+ 0x4a, 0x67, 0xa4, 0x89, 0xfe, 0xd3, 0x46, 0x6b, 0x1c, 0x31,
+ 0xf2, 0xdf, 0xa8, 0x85, 0x33, 0x1e, 0x69, 0x44, 0x87, 0xaa,
+ 0xdd, 0xf0, 0xac, 0x81, 0xf6, 0xdb, 0x18, 0x35, 0x42, 0x6f,
+ 0xd9, 0xf4, 0x83, 0xae, 0x6d, 0x40, 0x37, 0x1a, 0x03, 0x2e,
+ 0x59, 0x74, 0xb7, 0x9a, 0xed, 0xc0, 0x76, 0x5b, 0x2c, 0x01,
+ 0xc2, 0xef, 0x98, 0xb5, 0xe9, 0xc4, 0xb3, 0x9e, 0x5d, 0x70,
+ 0x07, 0x2a, 0x9c, 0xb1, 0xc6, 0xeb, 0x28, 0x05, 0x72, 0x5f,
+ 0xca, 0xe7, 0x90, 0xbd, 0x7e, 0x53, 0x24, 0x09, 0xbf, 0x92,
+ 0xe5, 0xc8, 0x0b, 0x26, 0x51, 0x7c, 0x20, 0x0d, 0x7a, 0x57,
+ 0x94, 0xb9, 0xce, 0xe3, 0x55, 0x78, 0x0f, 0x22, 0xe1, 0xcc,
+ 0xbb, 0x96, 0x8c, 0xa1, 0xd6, 0xfb, 0x38, 0x15, 0x62, 0x4f,
+ 0xf9, 0xd4, 0xa3, 0x8e, 0x4d, 0x60, 0x17, 0x3a, 0x66, 0x4b,
+ 0x3c, 0x11, 0xd2, 0xff, 0x88, 0xa5, 0x13, 0x3e, 0x49, 0x64,
+ 0xa7, 0x8a, 0xfd, 0xd0, 0x45, 0x68, 0x1f, 0x32, 0xf1, 0xdc,
+ 0xab, 0x86, 0x30, 0x1d, 0x6a, 0x47, 0x84, 0xa9, 0xde, 0xf3,
+ 0xaf, 0x82, 0xf5, 0xd8, 0x1b, 0x36, 0x41, 0x6c, 0xda, 0xf7,
+ 0x80, 0xad, 0x6e, 0x43, 0x34, 0x19, 0x00, 0x2e, 0x5c, 0x72,
+ 0xb8, 0x96, 0xe4, 0xca, 0x6d, 0x43, 0x31, 0x1f, 0xd5, 0xfb,
+ 0x89, 0xa7, 0xda, 0xf4, 0x86, 0xa8, 0x62, 0x4c, 0x3e, 0x10,
+ 0xb7, 0x99, 0xeb, 0xc5, 0x0f, 0x21, 0x53, 0x7d, 0xa9, 0x87,
+ 0xf5, 0xdb, 0x11, 0x3f, 0x4d, 0x63, 0xc4, 0xea, 0x98, 0xb6,
+ 0x7c, 0x52, 0x20, 0x0e, 0x73, 0x5d, 0x2f, 0x01, 0xcb, 0xe5,
+ 0x97, 0xb9, 0x1e, 0x30, 0x42, 0x6c, 0xa6, 0x88, 0xfa, 0xd4,
+ 0x4f, 0x61, 0x13, 0x3d, 0xf7, 0xd9, 0xab, 0x85, 0x22, 0x0c,
+ 0x7e, 0x50, 0x9a, 0xb4, 0xc6, 0xe8, 0x95, 0xbb, 0xc9, 0xe7,
+ 0x2d, 0x03, 0x71, 0x5f, 0xf8, 0xd6, 0xa4, 0x8a, 0x40, 0x6e,
+ 0x1c, 0x32, 0xe6, 0xc8, 0xba, 0x94, 0x5e, 0x70, 0x02, 0x2c,
+ 0x8b, 0xa5, 0xd7, 0xf9, 0x33, 0x1d, 0x6f, 0x41, 0x3c, 0x12,
+ 0x60, 0x4e, 0x84, 0xaa, 0xd8, 0xf6, 0x51, 0x7f, 0x0d, 0x23,
+ 0xe9, 0xc7, 0xb5, 0x9b, 0x9e, 0xb0, 0xc2, 0xec, 0x26, 0x08,
+ 0x7a, 0x54, 0xf3, 0xdd, 0xaf, 0x81, 0x4b, 0x65, 0x17, 0x39,
+ 0x44, 0x6a, 0x18, 0x36, 0xfc, 0xd2, 0xa0, 0x8e, 0x29, 0x07,
+ 0x75, 0x5b, 0x91, 0xbf, 0xcd, 0xe3, 0x37, 0x19, 0x6b, 0x45,
+ 0x8f, 0xa1, 0xd3, 0xfd, 0x5a, 0x74, 0x06, 0x28, 0xe2, 0xcc,
+ 0xbe, 0x90, 0xed, 0xc3, 0xb1, 0x9f, 0x55, 0x7b, 0x09, 0x27,
+ 0x80, 0xae, 0xdc, 0xf2, 0x38, 0x16, 0x64, 0x4a, 0xd1, 0xff,
+ 0x8d, 0xa3, 0x69, 0x47, 0x35, 0x1b, 0xbc, 0x92, 0xe0, 0xce,
+ 0x04, 0x2a, 0x58, 0x76, 0x0b, 0x25, 0x57, 0x79, 0xb3, 0x9d,
+ 0xef, 0xc1, 0x66, 0x48, 0x3a, 0x14, 0xde, 0xf0, 0x82, 0xac,
+ 0x78, 0x56, 0x24, 0x0a, 0xc0, 0xee, 0x9c, 0xb2, 0x15, 0x3b,
+ 0x49, 0x67, 0xad, 0x83, 0xf1, 0xdf, 0xa2, 0x8c, 0xfe, 0xd0,
+ 0x1a, 0x34, 0x46, 0x68, 0xcf, 0xe1, 0x93, 0xbd, 0x77, 0x59,
+ 0x2b, 0x05, 0x00, 0x2f, 0x5e, 0x71, 0xbc, 0x93, 0xe2, 0xcd,
+ 0x65, 0x4a, 0x3b, 0x14, 0xd9, 0xf6, 0x87, 0xa8, 0xca, 0xe5,
+ 0x94, 0xbb, 0x76, 0x59, 0x28, 0x07, 0xaf, 0x80, 0xf1, 0xde,
+ 0x13, 0x3c, 0x4d, 0x62, 0x89, 0xa6, 0xd7, 0xf8, 0x35, 0x1a,
+ 0x6b, 0x44, 0xec, 0xc3, 0xb2, 0x9d, 0x50, 0x7f, 0x0e, 0x21,
+ 0x43, 0x6c, 0x1d, 0x32, 0xff, 0xd0, 0xa1, 0x8e, 0x26, 0x09,
+ 0x78, 0x57, 0x9a, 0xb5, 0xc4, 0xeb, 0x0f, 0x20, 0x51, 0x7e,
+ 0xb3, 0x9c, 0xed, 0xc2, 0x6a, 0x45, 0x34, 0x1b, 0xd6, 0xf9,
+ 0x88, 0xa7, 0xc5, 0xea, 0x9b, 0xb4, 0x79, 0x56, 0x27, 0x08,
+ 0xa0, 0x8f, 0xfe, 0xd1, 0x1c, 0x33, 0x42, 0x6d, 0x86, 0xa9,
+ 0xd8, 0xf7, 0x3a, 0x15, 0x64, 0x4b, 0xe3, 0xcc, 0xbd, 0x92,
+ 0x5f, 0x70, 0x01, 0x2e, 0x4c, 0x63, 0x12, 0x3d, 0xf0, 0xdf,
+ 0xae, 0x81, 0x29, 0x06, 0x77, 0x58, 0x95, 0xba, 0xcb, 0xe4,
+ 0x1e, 0x31, 0x40, 0x6f, 0xa2, 0x8d, 0xfc, 0xd3, 0x7b, 0x54,
+ 0x25, 0x0a, 0xc7, 0xe8, 0x99, 0xb6, 0xd4, 0xfb, 0x8a, 0xa5,
+ 0x68, 0x47, 0x36, 0x19, 0xb1, 0x9e, 0xef, 0xc0, 0x0d, 0x22,
+ 0x53, 0x7c, 0x97, 0xb8, 0xc9, 0xe6, 0x2b, 0x04, 0x75, 0x5a,
+ 0xf2, 0xdd, 0xac, 0x83, 0x4e, 0x61, 0x10, 0x3f, 0x5d, 0x72,
+ 0x03, 0x2c, 0xe1, 0xce, 0xbf, 0x90, 0x38, 0x17, 0x66, 0x49,
+ 0x84, 0xab, 0xda, 0xf5, 0x11, 0x3e, 0x4f, 0x60, 0xad, 0x82,
+ 0xf3, 0xdc, 0x74, 0x5b, 0x2a, 0x05, 0xc8, 0xe7, 0x96, 0xb9,
+ 0xdb, 0xf4, 0x85, 0xaa, 0x67, 0x48, 0x39, 0x16, 0xbe, 0x91,
+ 0xe0, 0xcf, 0x02, 0x2d, 0x5c, 0x73, 0x98, 0xb7, 0xc6, 0xe9,
+ 0x24, 0x0b, 0x7a, 0x55, 0xfd, 0xd2, 0xa3, 0x8c, 0x41, 0x6e,
+ 0x1f, 0x30, 0x52, 0x7d, 0x0c, 0x23, 0xee, 0xc1, 0xb0, 0x9f,
+ 0x37, 0x18, 0x69, 0x46, 0x8b, 0xa4, 0xd5, 0xfa, 0x00, 0x30,
+ 0x60, 0x50, 0xc0, 0xf0, 0xa0, 0x90, 0x9d, 0xad, 0xfd, 0xcd,
+ 0x5d, 0x6d, 0x3d, 0x0d, 0x27, 0x17, 0x47, 0x77, 0xe7, 0xd7,
+ 0x87, 0xb7, 0xba, 0x8a, 0xda, 0xea, 0x7a, 0x4a, 0x1a, 0x2a,
+ 0x4e, 0x7e, 0x2e, 0x1e, 0x8e, 0xbe, 0xee, 0xde, 0xd3, 0xe3,
+ 0xb3, 0x83, 0x13, 0x23, 0x73, 0x43, 0x69, 0x59, 0x09, 0x39,
+ 0xa9, 0x99, 0xc9, 0xf9, 0xf4, 0xc4, 0x94, 0xa4, 0x34, 0x04,
+ 0x54, 0x64, 0x9c, 0xac, 0xfc, 0xcc, 0x5c, 0x6c, 0x3c, 0x0c,
+ 0x01, 0x31, 0x61, 0x51, 0xc1, 0xf1, 0xa1, 0x91, 0xbb, 0x8b,
+ 0xdb, 0xeb, 0x7b, 0x4b, 0x1b, 0x2b, 0x26, 0x16, 0x46, 0x76,
+ 0xe6, 0xd6, 0x86, 0xb6, 0xd2, 0xe2, 0xb2, 0x82, 0x12, 0x22,
+ 0x72, 0x42, 0x4f, 0x7f, 0x2f, 0x1f, 0x8f, 0xbf, 0xef, 0xdf,
+ 0xf5, 0xc5, 0x95, 0xa5, 0x35, 0x05, 0x55, 0x65, 0x68, 0x58,
+ 0x08, 0x38, 0xa8, 0x98, 0xc8, 0xf8, 0x25, 0x15, 0x45, 0x75,
+ 0xe5, 0xd5, 0x85, 0xb5, 0xb8, 0x88, 0xd8, 0xe8, 0x78, 0x48,
+ 0x18, 0x28, 0x02, 0x32, 0x62, 0x52, 0xc2, 0xf2, 0xa2, 0x92,
+ 0x9f, 0xaf, 0xff, 0xcf, 0x5f, 0x6f, 0x3f, 0x0f, 0x6b, 0x5b,
+ 0x0b, 0x3b, 0xab, 0x9b, 0xcb, 0xfb, 0xf6, 0xc6, 0x96, 0xa6,
+ 0x36, 0x06, 0x56, 0x66, 0x4c, 0x7c, 0x2c, 0x1c, 0x8c, 0xbc,
+ 0xec, 0xdc, 0xd1, 0xe1, 0xb1, 0x81, 0x11, 0x21, 0x71, 0x41,
+ 0xb9, 0x89, 0xd9, 0xe9, 0x79, 0x49, 0x19, 0x29, 0x24, 0x14,
+ 0x44, 0x74, 0xe4, 0xd4, 0x84, 0xb4, 0x9e, 0xae, 0xfe, 0xce,
+ 0x5e, 0x6e, 0x3e, 0x0e, 0x03, 0x33, 0x63, 0x53, 0xc3, 0xf3,
+ 0xa3, 0x93, 0xf7, 0xc7, 0x97, 0xa7, 0x37, 0x07, 0x57, 0x67,
+ 0x6a, 0x5a, 0x0a, 0x3a, 0xaa, 0x9a, 0xca, 0xfa, 0xd0, 0xe0,
+ 0xb0, 0x80, 0x10, 0x20, 0x70, 0x40, 0x4d, 0x7d, 0x2d, 0x1d,
+ 0x8d, 0xbd, 0xed, 0xdd, 0x00, 0x31, 0x62, 0x53, 0xc4, 0xf5,
+ 0xa6, 0x97, 0x95, 0xa4, 0xf7, 0xc6, 0x51, 0x60, 0x33, 0x02,
+ 0x37, 0x06, 0x55, 0x64, 0xf3, 0xc2, 0x91, 0xa0, 0xa2, 0x93,
+ 0xc0, 0xf1, 0x66, 0x57, 0x04, 0x35, 0x6e, 0x5f, 0x0c, 0x3d,
+ 0xaa, 0x9b, 0xc8, 0xf9, 0xfb, 0xca, 0x99, 0xa8, 0x3f, 0x0e,
+ 0x5d, 0x6c, 0x59, 0x68, 0x3b, 0x0a, 0x9d, 0xac, 0xff, 0xce,
+ 0xcc, 0xfd, 0xae, 0x9f, 0x08, 0x39, 0x6a, 0x5b, 0xdc, 0xed,
+ 0xbe, 0x8f, 0x18, 0x29, 0x7a, 0x4b, 0x49, 0x78, 0x2b, 0x1a,
+ 0x8d, 0xbc, 0xef, 0xde, 0xeb, 0xda, 0x89, 0xb8, 0x2f, 0x1e,
+ 0x4d, 0x7c, 0x7e, 0x4f, 0x1c, 0x2d, 0xba, 0x8b, 0xd8, 0xe9,
+ 0xb2, 0x83, 0xd0, 0xe1, 0x76, 0x47, 0x14, 0x25, 0x27, 0x16,
+ 0x45, 0x74, 0xe3, 0xd2, 0x81, 0xb0, 0x85, 0xb4, 0xe7, 0xd6,
+ 0x41, 0x70, 0x23, 0x12, 0x10, 0x21, 0x72, 0x43, 0xd4, 0xe5,
+ 0xb6, 0x87, 0xa5, 0x94, 0xc7, 0xf6, 0x61, 0x50, 0x03, 0x32,
+ 0x30, 0x01, 0x52, 0x63, 0xf4, 0xc5, 0x96, 0xa7, 0x92, 0xa3,
+ 0xf0, 0xc1, 0x56, 0x67, 0x34, 0x05, 0x07, 0x36, 0x65, 0x54,
+ 0xc3, 0xf2, 0xa1, 0x90, 0xcb, 0xfa, 0xa9, 0x98, 0x0f, 0x3e,
+ 0x6d, 0x5c, 0x5e, 0x6f, 0x3c, 0x0d, 0x9a, 0xab, 0xf8, 0xc9,
+ 0xfc, 0xcd, 0x9e, 0xaf, 0x38, 0x09, 0x5a, 0x6b, 0x69, 0x58,
+ 0x0b, 0x3a, 0xad, 0x9c, 0xcf, 0xfe, 0x79, 0x48, 0x1b, 0x2a,
+ 0xbd, 0x8c, 0xdf, 0xee, 0xec, 0xdd, 0x8e, 0xbf, 0x28, 0x19,
+ 0x4a, 0x7b, 0x4e, 0x7f, 0x2c, 0x1d, 0x8a, 0xbb, 0xe8, 0xd9,
+ 0xdb, 0xea, 0xb9, 0x88, 0x1f, 0x2e, 0x7d, 0x4c, 0x17, 0x26,
+ 0x75, 0x44, 0xd3, 0xe2, 0xb1, 0x80, 0x82, 0xb3, 0xe0, 0xd1,
+ 0x46, 0x77, 0x24, 0x15, 0x20, 0x11, 0x42, 0x73, 0xe4, 0xd5,
+ 0x86, 0xb7, 0xb5, 0x84, 0xd7, 0xe6, 0x71, 0x40, 0x13, 0x22,
+ 0x00, 0x32, 0x64, 0x56, 0xc8, 0xfa, 0xac, 0x9e, 0x8d, 0xbf,
+ 0xe9, 0xdb, 0x45, 0x77, 0x21, 0x13, 0x07, 0x35, 0x63, 0x51,
+ 0xcf, 0xfd, 0xab, 0x99, 0x8a, 0xb8, 0xee, 0xdc, 0x42, 0x70,
+ 0x26, 0x14, 0x0e, 0x3c, 0x6a, 0x58, 0xc6, 0xf4, 0xa2, 0x90,
+ 0x83, 0xb1, 0xe7, 0xd5, 0x4b, 0x79, 0x2f, 0x1d, 0x09, 0x3b,
+ 0x6d, 0x5f, 0xc1, 0xf3, 0xa5, 0x97, 0x84, 0xb6, 0xe0, 0xd2,
+ 0x4c, 0x7e, 0x28, 0x1a, 0x1c, 0x2e, 0x78, 0x4a, 0xd4, 0xe6,
+ 0xb0, 0x82, 0x91, 0xa3, 0xf5, 0xc7, 0x59, 0x6b, 0x3d, 0x0f,
+ 0x1b, 0x29, 0x7f, 0x4d, 0xd3, 0xe1, 0xb7, 0x85, 0x96, 0xa4,
+ 0xf2, 0xc0, 0x5e, 0x6c, 0x3a, 0x08, 0x12, 0x20, 0x76, 0x44,
+ 0xda, 0xe8, 0xbe, 0x8c, 0x9f, 0xad, 0xfb, 0xc9, 0x57, 0x65,
+ 0x33, 0x01, 0x15, 0x27, 0x71, 0x43, 0xdd, 0xef, 0xb9, 0x8b,
+ 0x98, 0xaa, 0xfc, 0xce, 0x50, 0x62, 0x34, 0x06, 0x38, 0x0a,
+ 0x5c, 0x6e, 0xf0, 0xc2, 0x94, 0xa6, 0xb5, 0x87, 0xd1, 0xe3,
+ 0x7d, 0x4f, 0x19, 0x2b, 0x3f, 0x0d, 0x5b, 0x69, 0xf7, 0xc5,
+ 0x93, 0xa1, 0xb2, 0x80, 0xd6, 0xe4, 0x7a, 0x48, 0x1e, 0x2c,
+ 0x36, 0x04, 0x52, 0x60, 0xfe, 0xcc, 0x9a, 0xa8, 0xbb, 0x89,
+ 0xdf, 0xed, 0x73, 0x41, 0x17, 0x25, 0x31, 0x03, 0x55, 0x67,
+ 0xf9, 0xcb, 0x9d, 0xaf, 0xbc, 0x8e, 0xd8, 0xea, 0x74, 0x46,
+ 0x10, 0x22, 0x24, 0x16, 0x40, 0x72, 0xec, 0xde, 0x88, 0xba,
+ 0xa9, 0x9b, 0xcd, 0xff, 0x61, 0x53, 0x05, 0x37, 0x23, 0x11,
+ 0x47, 0x75, 0xeb, 0xd9, 0x8f, 0xbd, 0xae, 0x9c, 0xca, 0xf8,
+ 0x66, 0x54, 0x02, 0x30, 0x2a, 0x18, 0x4e, 0x7c, 0xe2, 0xd0,
+ 0x86, 0xb4, 0xa7, 0x95, 0xc3, 0xf1, 0x6f, 0x5d, 0x0b, 0x39,
+ 0x2d, 0x1f, 0x49, 0x7b, 0xe5, 0xd7, 0x81, 0xb3, 0xa0, 0x92,
+ 0xc4, 0xf6, 0x68, 0x5a, 0x0c, 0x3e, 0x00, 0x33, 0x66, 0x55,
+ 0xcc, 0xff, 0xaa, 0x99, 0x85, 0xb6, 0xe3, 0xd0, 0x49, 0x7a,
+ 0x2f, 0x1c, 0x17, 0x24, 0x71, 0x42, 0xdb, 0xe8, 0xbd, 0x8e,
+ 0x92, 0xa1, 0xf4, 0xc7, 0x5e, 0x6d, 0x38, 0x0b, 0x2e, 0x1d,
+ 0x48, 0x7b, 0xe2, 0xd1, 0x84, 0xb7, 0xab, 0x98, 0xcd, 0xfe,
+ 0x67, 0x54, 0x01, 0x32, 0x39, 0x0a, 0x5f, 0x6c, 0xf5, 0xc6,
+ 0x93, 0xa0, 0xbc, 0x8f, 0xda, 0xe9, 0x70, 0x43, 0x16, 0x25,
+ 0x5c, 0x6f, 0x3a, 0x09, 0x90, 0xa3, 0xf6, 0xc5, 0xd9, 0xea,
+ 0xbf, 0x8c, 0x15, 0x26, 0x73, 0x40, 0x4b, 0x78, 0x2d, 0x1e,
+ 0x87, 0xb4, 0xe1, 0xd2, 0xce, 0xfd, 0xa8, 0x9b, 0x02, 0x31,
+ 0x64, 0x57, 0x72, 0x41, 0x14, 0x27, 0xbe, 0x8d, 0xd8, 0xeb,
+ 0xf7, 0xc4, 0x91, 0xa2, 0x3b, 0x08, 0x5d, 0x6e, 0x65, 0x56,
+ 0x03, 0x30, 0xa9, 0x9a, 0xcf, 0xfc, 0xe0, 0xd3, 0x86, 0xb5,
+ 0x2c, 0x1f, 0x4a, 0x79, 0xb8, 0x8b, 0xde, 0xed, 0x74, 0x47,
+ 0x12, 0x21, 0x3d, 0x0e, 0x5b, 0x68, 0xf1, 0xc2, 0x97, 0xa4,
+ 0xaf, 0x9c, 0xc9, 0xfa, 0x63, 0x50, 0x05, 0x36, 0x2a, 0x19,
+ 0x4c, 0x7f, 0xe6, 0xd5, 0x80, 0xb3, 0x96, 0xa5, 0xf0, 0xc3,
+ 0x5a, 0x69, 0x3c, 0x0f, 0x13, 0x20, 0x75, 0x46, 0xdf, 0xec,
+ 0xb9, 0x8a, 0x81, 0xb2, 0xe7, 0xd4, 0x4d, 0x7e, 0x2b, 0x18,
+ 0x04, 0x37, 0x62, 0x51, 0xc8, 0xfb, 0xae, 0x9d, 0xe4, 0xd7,
+ 0x82, 0xb1, 0x28, 0x1b, 0x4e, 0x7d, 0x61, 0x52, 0x07, 0x34,
+ 0xad, 0x9e, 0xcb, 0xf8, 0xf3, 0xc0, 0x95, 0xa6, 0x3f, 0x0c,
+ 0x59, 0x6a, 0x76, 0x45, 0x10, 0x23, 0xba, 0x89, 0xdc, 0xef,
+ 0xca, 0xf9, 0xac, 0x9f, 0x06, 0x35, 0x60, 0x53, 0x4f, 0x7c,
+ 0x29, 0x1a, 0x83, 0xb0, 0xe5, 0xd6, 0xdd, 0xee, 0xbb, 0x88,
+ 0x11, 0x22, 0x77, 0x44, 0x58, 0x6b, 0x3e, 0x0d, 0x94, 0xa7,
+ 0xf2, 0xc1, 0x00, 0x34, 0x68, 0x5c, 0xd0, 0xe4, 0xb8, 0x8c,
+ 0xbd, 0x89, 0xd5, 0xe1, 0x6d, 0x59, 0x05, 0x31, 0x67, 0x53,
+ 0x0f, 0x3b, 0xb7, 0x83, 0xdf, 0xeb, 0xda, 0xee, 0xb2, 0x86,
+ 0x0a, 0x3e, 0x62, 0x56, 0xce, 0xfa, 0xa6, 0x92, 0x1e, 0x2a,
+ 0x76, 0x42, 0x73, 0x47, 0x1b, 0x2f, 0xa3, 0x97, 0xcb, 0xff,
+ 0xa9, 0x9d, 0xc1, 0xf5, 0x79, 0x4d, 0x11, 0x25, 0x14, 0x20,
+ 0x7c, 0x48, 0xc4, 0xf0, 0xac, 0x98, 0x81, 0xb5, 0xe9, 0xdd,
+ 0x51, 0x65, 0x39, 0x0d, 0x3c, 0x08, 0x54, 0x60, 0xec, 0xd8,
+ 0x84, 0xb0, 0xe6, 0xd2, 0x8e, 0xba, 0x36, 0x02, 0x5e, 0x6a,
+ 0x5b, 0x6f, 0x33, 0x07, 0x8b, 0xbf, 0xe3, 0xd7, 0x4f, 0x7b,
+ 0x27, 0x13, 0x9f, 0xab, 0xf7, 0xc3, 0xf2, 0xc6, 0x9a, 0xae,
+ 0x22, 0x16, 0x4a, 0x7e, 0x28, 0x1c, 0x40, 0x74, 0xf8, 0xcc,
+ 0x90, 0xa4, 0x95, 0xa1, 0xfd, 0xc9, 0x45, 0x71, 0x2d, 0x19,
+ 0x1f, 0x2b, 0x77, 0x43, 0xcf, 0xfb, 0xa7, 0x93, 0xa2, 0x96,
+ 0xca, 0xfe, 0x72, 0x46, 0x1a, 0x2e, 0x78, 0x4c, 0x10, 0x24,
+ 0xa8, 0x9c, 0xc0, 0xf4, 0xc5, 0xf1, 0xad, 0x99, 0x15, 0x21,
+ 0x7d, 0x49, 0xd1, 0xe5, 0xb9, 0x8d, 0x01, 0x35, 0x69, 0x5d,
+ 0x6c, 0x58, 0x04, 0x30, 0xbc, 0x88, 0xd4, 0xe0, 0xb6, 0x82,
+ 0xde, 0xea, 0x66, 0x52, 0x0e, 0x3a, 0x0b, 0x3f, 0x63, 0x57,
+ 0xdb, 0xef, 0xb3, 0x87, 0x9e, 0xaa, 0xf6, 0xc2, 0x4e, 0x7a,
+ 0x26, 0x12, 0x23, 0x17, 0x4b, 0x7f, 0xf3, 0xc7, 0x9b, 0xaf,
+ 0xf9, 0xcd, 0x91, 0xa5, 0x29, 0x1d, 0x41, 0x75, 0x44, 0x70,
+ 0x2c, 0x18, 0x94, 0xa0, 0xfc, 0xc8, 0x50, 0x64, 0x38, 0x0c,
+ 0x80, 0xb4, 0xe8, 0xdc, 0xed, 0xd9, 0x85, 0xb1, 0x3d, 0x09,
+ 0x55, 0x61, 0x37, 0x03, 0x5f, 0x6b, 0xe7, 0xd3, 0x8f, 0xbb,
+ 0x8a, 0xbe, 0xe2, 0xd6, 0x5a, 0x6e, 0x32, 0x06, 0x00, 0x35,
+ 0x6a, 0x5f, 0xd4, 0xe1, 0xbe, 0x8b, 0xb5, 0x80, 0xdf, 0xea,
+ 0x61, 0x54, 0x0b, 0x3e, 0x77, 0x42, 0x1d, 0x28, 0xa3, 0x96,
+ 0xc9, 0xfc, 0xc2, 0xf7, 0xa8, 0x9d, 0x16, 0x23, 0x7c, 0x49,
+ 0xee, 0xdb, 0x84, 0xb1, 0x3a, 0x0f, 0x50, 0x65, 0x5b, 0x6e,
+ 0x31, 0x04, 0x8f, 0xba, 0xe5, 0xd0, 0x99, 0xac, 0xf3, 0xc6,
+ 0x4d, 0x78, 0x27, 0x12, 0x2c, 0x19, 0x46, 0x73, 0xf8, 0xcd,
+ 0x92, 0xa7, 0xc1, 0xf4, 0xab, 0x9e, 0x15, 0x20, 0x7f, 0x4a,
+ 0x74, 0x41, 0x1e, 0x2b, 0xa0, 0x95, 0xca, 0xff, 0xb6, 0x83,
+ 0xdc, 0xe9, 0x62, 0x57, 0x08, 0x3d, 0x03, 0x36, 0x69, 0x5c,
+ 0xd7, 0xe2, 0xbd, 0x88, 0x2f, 0x1a, 0x45, 0x70, 0xfb, 0xce,
+ 0x91, 0xa4, 0x9a, 0xaf, 0xf0, 0xc5, 0x4e, 0x7b, 0x24, 0x11,
+ 0x58, 0x6d, 0x32, 0x07, 0x8c, 0xb9, 0xe6, 0xd3, 0xed, 0xd8,
+ 0x87, 0xb2, 0x39, 0x0c, 0x53, 0x66, 0x9f, 0xaa, 0xf5, 0xc0,
+ 0x4b, 0x7e, 0x21, 0x14, 0x2a, 0x1f, 0x40, 0x75, 0xfe, 0xcb,
+ 0x94, 0xa1, 0xe8, 0xdd, 0x82, 0xb7, 0x3c, 0x09, 0x56, 0x63,
+ 0x5d, 0x68, 0x37, 0x02, 0x89, 0xbc, 0xe3, 0xd6, 0x71, 0x44,
+ 0x1b, 0x2e, 0xa5, 0x90, 0xcf, 0xfa, 0xc4, 0xf1, 0xae, 0x9b,
+ 0x10, 0x25, 0x7a, 0x4f, 0x06, 0x33, 0x6c, 0x59, 0xd2, 0xe7,
+ 0xb8, 0x8d, 0xb3, 0x86, 0xd9, 0xec, 0x67, 0x52, 0x0d, 0x38,
+ 0x5e, 0x6b, 0x34, 0x01, 0x8a, 0xbf, 0xe0, 0xd5, 0xeb, 0xde,
+ 0x81, 0xb4, 0x3f, 0x0a, 0x55, 0x60, 0x29, 0x1c, 0x43, 0x76,
+ 0xfd, 0xc8, 0x97, 0xa2, 0x9c, 0xa9, 0xf6, 0xc3, 0x48, 0x7d,
+ 0x22, 0x17, 0xb0, 0x85, 0xda, 0xef, 0x64, 0x51, 0x0e, 0x3b,
+ 0x05, 0x30, 0x6f, 0x5a, 0xd1, 0xe4, 0xbb, 0x8e, 0xc7, 0xf2,
+ 0xad, 0x98, 0x13, 0x26, 0x79, 0x4c, 0x72, 0x47, 0x18, 0x2d,
+ 0xa6, 0x93, 0xcc, 0xf9, 0x00, 0x36, 0x6c, 0x5a, 0xd8, 0xee,
+ 0xb4, 0x82, 0xad, 0x9b, 0xc1, 0xf7, 0x75, 0x43, 0x19, 0x2f,
+ 0x47, 0x71, 0x2b, 0x1d, 0x9f, 0xa9, 0xf3, 0xc5, 0xea, 0xdc,
+ 0x86, 0xb0, 0x32, 0x04, 0x5e, 0x68, 0x8e, 0xb8, 0xe2, 0xd4,
+ 0x56, 0x60, 0x3a, 0x0c, 0x23, 0x15, 0x4f, 0x79, 0xfb, 0xcd,
+ 0x97, 0xa1, 0xc9, 0xff, 0xa5, 0x93, 0x11, 0x27, 0x7d, 0x4b,
+ 0x64, 0x52, 0x08, 0x3e, 0xbc, 0x8a, 0xd0, 0xe6, 0x01, 0x37,
+ 0x6d, 0x5b, 0xd9, 0xef, 0xb5, 0x83, 0xac, 0x9a, 0xc0, 0xf6,
+ 0x74, 0x42, 0x18, 0x2e, 0x46, 0x70, 0x2a, 0x1c, 0x9e, 0xa8,
+ 0xf2, 0xc4, 0xeb, 0xdd, 0x87, 0xb1, 0x33, 0x05, 0x5f, 0x69,
+ 0x8f, 0xb9, 0xe3, 0xd5, 0x57, 0x61, 0x3b, 0x0d, 0x22, 0x14,
+ 0x4e, 0x78, 0xfa, 0xcc, 0x96, 0xa0, 0xc8, 0xfe, 0xa4, 0x92,
+ 0x10, 0x26, 0x7c, 0x4a, 0x65, 0x53, 0x09, 0x3f, 0xbd, 0x8b,
+ 0xd1, 0xe7, 0x02, 0x34, 0x6e, 0x58, 0xda, 0xec, 0xb6, 0x80,
+ 0xaf, 0x99, 0xc3, 0xf5, 0x77, 0x41, 0x1b, 0x2d, 0x45, 0x73,
+ 0x29, 0x1f, 0x9d, 0xab, 0xf1, 0xc7, 0xe8, 0xde, 0x84, 0xb2,
+ 0x30, 0x06, 0x5c, 0x6a, 0x8c, 0xba, 0xe0, 0xd6, 0x54, 0x62,
+ 0x38, 0x0e, 0x21, 0x17, 0x4d, 0x7b, 0xf9, 0xcf, 0x95, 0xa3,
+ 0xcb, 0xfd, 0xa7, 0x91, 0x13, 0x25, 0x7f, 0x49, 0x66, 0x50,
+ 0x0a, 0x3c, 0xbe, 0x88, 0xd2, 0xe4, 0x03, 0x35, 0x6f, 0x59,
+ 0xdb, 0xed, 0xb7, 0x81, 0xae, 0x98, 0xc2, 0xf4, 0x76, 0x40,
+ 0x1a, 0x2c, 0x44, 0x72, 0x28, 0x1e, 0x9c, 0xaa, 0xf0, 0xc6,
+ 0xe9, 0xdf, 0x85, 0xb3, 0x31, 0x07, 0x5d, 0x6b, 0x8d, 0xbb,
+ 0xe1, 0xd7, 0x55, 0x63, 0x39, 0x0f, 0x20, 0x16, 0x4c, 0x7a,
+ 0xf8, 0xce, 0x94, 0xa2, 0xca, 0xfc, 0xa6, 0x90, 0x12, 0x24,
+ 0x7e, 0x48, 0x67, 0x51, 0x0b, 0x3d, 0xbf, 0x89, 0xd3, 0xe5,
+ 0x00, 0x37, 0x6e, 0x59, 0xdc, 0xeb, 0xb2, 0x85, 0xa5, 0x92,
+ 0xcb, 0xfc, 0x79, 0x4e, 0x17, 0x20, 0x57, 0x60, 0x39, 0x0e,
+ 0x8b, 0xbc, 0xe5, 0xd2, 0xf2, 0xc5, 0x9c, 0xab, 0x2e, 0x19,
+ 0x40, 0x77, 0xae, 0x99, 0xc0, 0xf7, 0x72, 0x45, 0x1c, 0x2b,
+ 0x0b, 0x3c, 0x65, 0x52, 0xd7, 0xe0, 0xb9, 0x8e, 0xf9, 0xce,
+ 0x97, 0xa0, 0x25, 0x12, 0x4b, 0x7c, 0x5c, 0x6b, 0x32, 0x05,
+ 0x80, 0xb7, 0xee, 0xd9, 0x41, 0x76, 0x2f, 0x18, 0x9d, 0xaa,
+ 0xf3, 0xc4, 0xe4, 0xd3, 0x8a, 0xbd, 0x38, 0x0f, 0x56, 0x61,
+ 0x16, 0x21, 0x78, 0x4f, 0xca, 0xfd, 0xa4, 0x93, 0xb3, 0x84,
+ 0xdd, 0xea, 0x6f, 0x58, 0x01, 0x36, 0xef, 0xd8, 0x81, 0xb6,
+ 0x33, 0x04, 0x5d, 0x6a, 0x4a, 0x7d, 0x24, 0x13, 0x96, 0xa1,
+ 0xf8, 0xcf, 0xb8, 0x8f, 0xd6, 0xe1, 0x64, 0x53, 0x0a, 0x3d,
+ 0x1d, 0x2a, 0x73, 0x44, 0xc1, 0xf6, 0xaf, 0x98, 0x82, 0xb5,
+ 0xec, 0xdb, 0x5e, 0x69, 0x30, 0x07, 0x27, 0x10, 0x49, 0x7e,
+ 0xfb, 0xcc, 0x95, 0xa2, 0xd5, 0xe2, 0xbb, 0x8c, 0x09, 0x3e,
+ 0x67, 0x50, 0x70, 0x47, 0x1e, 0x29, 0xac, 0x9b, 0xc2, 0xf5,
+ 0x2c, 0x1b, 0x42, 0x75, 0xf0, 0xc7, 0x9e, 0xa9, 0x89, 0xbe,
+ 0xe7, 0xd0, 0x55, 0x62, 0x3b, 0x0c, 0x7b, 0x4c, 0x15, 0x22,
+ 0xa7, 0x90, 0xc9, 0xfe, 0xde, 0xe9, 0xb0, 0x87, 0x02, 0x35,
+ 0x6c, 0x5b, 0xc3, 0xf4, 0xad, 0x9a, 0x1f, 0x28, 0x71, 0x46,
+ 0x66, 0x51, 0x08, 0x3f, 0xba, 0x8d, 0xd4, 0xe3, 0x94, 0xa3,
+ 0xfa, 0xcd, 0x48, 0x7f, 0x26, 0x11, 0x31, 0x06, 0x5f, 0x68,
+ 0xed, 0xda, 0x83, 0xb4, 0x6d, 0x5a, 0x03, 0x34, 0xb1, 0x86,
+ 0xdf, 0xe8, 0xc8, 0xff, 0xa6, 0x91, 0x14, 0x23, 0x7a, 0x4d,
+ 0x3a, 0x0d, 0x54, 0x63, 0xe6, 0xd1, 0x88, 0xbf, 0x9f, 0xa8,
+ 0xf1, 0xc6, 0x43, 0x74, 0x2d, 0x1a, 0x00, 0x38, 0x70, 0x48,
+ 0xe0, 0xd8, 0x90, 0xa8, 0xdd, 0xe5, 0xad, 0x95, 0x3d, 0x05,
+ 0x4d, 0x75, 0xa7, 0x9f, 0xd7, 0xef, 0x47, 0x7f, 0x37, 0x0f,
+ 0x7a, 0x42, 0x0a, 0x32, 0x9a, 0xa2, 0xea, 0xd2, 0x53, 0x6b,
+ 0x23, 0x1b, 0xb3, 0x8b, 0xc3, 0xfb, 0x8e, 0xb6, 0xfe, 0xc6,
+ 0x6e, 0x56, 0x1e, 0x26, 0xf4, 0xcc, 0x84, 0xbc, 0x14, 0x2c,
+ 0x64, 0x5c, 0x29, 0x11, 0x59, 0x61, 0xc9, 0xf1, 0xb9, 0x81,
+ 0xa6, 0x9e, 0xd6, 0xee, 0x46, 0x7e, 0x36, 0x0e, 0x7b, 0x43,
+ 0x0b, 0x33, 0x9b, 0xa3, 0xeb, 0xd3, 0x01, 0x39, 0x71, 0x49,
+ 0xe1, 0xd9, 0x91, 0xa9, 0xdc, 0xe4, 0xac, 0x94, 0x3c, 0x04,
+ 0x4c, 0x74, 0xf5, 0xcd, 0x85, 0xbd, 0x15, 0x2d, 0x65, 0x5d,
+ 0x28, 0x10, 0x58, 0x60, 0xc8, 0xf0, 0xb8, 0x80, 0x52, 0x6a,
+ 0x22, 0x1a, 0xb2, 0x8a, 0xc2, 0xfa, 0x8f, 0xb7, 0xff, 0xc7,
+ 0x6f, 0x57, 0x1f, 0x27, 0x51, 0x69, 0x21, 0x19, 0xb1, 0x89,
+ 0xc1, 0xf9, 0x8c, 0xb4, 0xfc, 0xc4, 0x6c, 0x54, 0x1c, 0x24,
+ 0xf6, 0xce, 0x86, 0xbe, 0x16, 0x2e, 0x66, 0x5e, 0x2b, 0x13,
+ 0x5b, 0x63, 0xcb, 0xf3, 0xbb, 0x83, 0x02, 0x3a, 0x72, 0x4a,
+ 0xe2, 0xda, 0x92, 0xaa, 0xdf, 0xe7, 0xaf, 0x97, 0x3f, 0x07,
+ 0x4f, 0x77, 0xa5, 0x9d, 0xd5, 0xed, 0x45, 0x7d, 0x35, 0x0d,
+ 0x78, 0x40, 0x08, 0x30, 0x98, 0xa0, 0xe8, 0xd0, 0xf7, 0xcf,
+ 0x87, 0xbf, 0x17, 0x2f, 0x67, 0x5f, 0x2a, 0x12, 0x5a, 0x62,
+ 0xca, 0xf2, 0xba, 0x82, 0x50, 0x68, 0x20, 0x18, 0xb0, 0x88,
+ 0xc0, 0xf8, 0x8d, 0xb5, 0xfd, 0xc5, 0x6d, 0x55, 0x1d, 0x25,
+ 0xa4, 0x9c, 0xd4, 0xec, 0x44, 0x7c, 0x34, 0x0c, 0x79, 0x41,
+ 0x09, 0x31, 0x99, 0xa1, 0xe9, 0xd1, 0x03, 0x3b, 0x73, 0x4b,
+ 0xe3, 0xdb, 0x93, 0xab, 0xde, 0xe6, 0xae, 0x96, 0x3e, 0x06,
+ 0x4e, 0x76, 0x00, 0x39, 0x72, 0x4b, 0xe4, 0xdd, 0x96, 0xaf,
+ 0xd5, 0xec, 0xa7, 0x9e, 0x31, 0x08, 0x43, 0x7a, 0xb7, 0x8e,
+ 0xc5, 0xfc, 0x53, 0x6a, 0x21, 0x18, 0x62, 0x5b, 0x10, 0x29,
+ 0x86, 0xbf, 0xf4, 0xcd, 0x73, 0x4a, 0x01, 0x38, 0x97, 0xae,
+ 0xe5, 0xdc, 0xa6, 0x9f, 0xd4, 0xed, 0x42, 0x7b, 0x30, 0x09,
+ 0xc4, 0xfd, 0xb6, 0x8f, 0x20, 0x19, 0x52, 0x6b, 0x11, 0x28,
+ 0x63, 0x5a, 0xf5, 0xcc, 0x87, 0xbe, 0xe6, 0xdf, 0x94, 0xad,
+ 0x02, 0x3b, 0x70, 0x49, 0x33, 0x0a, 0x41, 0x78, 0xd7, 0xee,
+ 0xa5, 0x9c, 0x51, 0x68, 0x23, 0x1a, 0xb5, 0x8c, 0xc7, 0xfe,
+ 0x84, 0xbd, 0xf6, 0xcf, 0x60, 0x59, 0x12, 0x2b, 0x95, 0xac,
+ 0xe7, 0xde, 0x71, 0x48, 0x03, 0x3a, 0x40, 0x79, 0x32, 0x0b,
+ 0xa4, 0x9d, 0xd6, 0xef, 0x22, 0x1b, 0x50, 0x69, 0xc6, 0xff,
+ 0xb4, 0x8d, 0xf7, 0xce, 0x85, 0xbc, 0x13, 0x2a, 0x61, 0x58,
+ 0xd1, 0xe8, 0xa3, 0x9a, 0x35, 0x0c, 0x47, 0x7e, 0x04, 0x3d,
+ 0x76, 0x4f, 0xe0, 0xd9, 0x92, 0xab, 0x66, 0x5f, 0x14, 0x2d,
+ 0x82, 0xbb, 0xf0, 0xc9, 0xb3, 0x8a, 0xc1, 0xf8, 0x57, 0x6e,
+ 0x25, 0x1c, 0xa2, 0x9b, 0xd0, 0xe9, 0x46, 0x7f, 0x34, 0x0d,
+ 0x77, 0x4e, 0x05, 0x3c, 0x93, 0xaa, 0xe1, 0xd8, 0x15, 0x2c,
+ 0x67, 0x5e, 0xf1, 0xc8, 0x83, 0xba, 0xc0, 0xf9, 0xb2, 0x8b,
+ 0x24, 0x1d, 0x56, 0x6f, 0x37, 0x0e, 0x45, 0x7c, 0xd3, 0xea,
+ 0xa1, 0x98, 0xe2, 0xdb, 0x90, 0xa9, 0x06, 0x3f, 0x74, 0x4d,
+ 0x80, 0xb9, 0xf2, 0xcb, 0x64, 0x5d, 0x16, 0x2f, 0x55, 0x6c,
+ 0x27, 0x1e, 0xb1, 0x88, 0xc3, 0xfa, 0x44, 0x7d, 0x36, 0x0f,
+ 0xa0, 0x99, 0xd2, 0xeb, 0x91, 0xa8, 0xe3, 0xda, 0x75, 0x4c,
+ 0x07, 0x3e, 0xf3, 0xca, 0x81, 0xb8, 0x17, 0x2e, 0x65, 0x5c,
+ 0x26, 0x1f, 0x54, 0x6d, 0xc2, 0xfb, 0xb0, 0x89, 0x00, 0x3a,
+ 0x74, 0x4e, 0xe8, 0xd2, 0x9c, 0xa6, 0xcd, 0xf7, 0xb9, 0x83,
+ 0x25, 0x1f, 0x51, 0x6b, 0x87, 0xbd, 0xf3, 0xc9, 0x6f, 0x55,
+ 0x1b, 0x21, 0x4a, 0x70, 0x3e, 0x04, 0xa2, 0x98, 0xd6, 0xec,
+ 0x13, 0x29, 0x67, 0x5d, 0xfb, 0xc1, 0x8f, 0xb5, 0xde, 0xe4,
+ 0xaa, 0x90, 0x36, 0x0c, 0x42, 0x78, 0x94, 0xae, 0xe0, 0xda,
+ 0x7c, 0x46, 0x08, 0x32, 0x59, 0x63, 0x2d, 0x17, 0xb1, 0x8b,
+ 0xc5, 0xff, 0x26, 0x1c, 0x52, 0x68, 0xce, 0xf4, 0xba, 0x80,
+ 0xeb, 0xd1, 0x9f, 0xa5, 0x03, 0x39, 0x77, 0x4d, 0xa1, 0x9b,
+ 0xd5, 0xef, 0x49, 0x73, 0x3d, 0x07, 0x6c, 0x56, 0x18, 0x22,
+ 0x84, 0xbe, 0xf0, 0xca, 0x35, 0x0f, 0x41, 0x7b, 0xdd, 0xe7,
+ 0xa9, 0x93, 0xf8, 0xc2, 0x8c, 0xb6, 0x10, 0x2a, 0x64, 0x5e,
+ 0xb2, 0x88, 0xc6, 0xfc, 0x5a, 0x60, 0x2e, 0x14, 0x7f, 0x45,
+ 0x0b, 0x31, 0x97, 0xad, 0xe3, 0xd9, 0x4c, 0x76, 0x38, 0x02,
+ 0xa4, 0x9e, 0xd0, 0xea, 0x81, 0xbb, 0xf5, 0xcf, 0x69, 0x53,
+ 0x1d, 0x27, 0xcb, 0xf1, 0xbf, 0x85, 0x23, 0x19, 0x57, 0x6d,
+ 0x06, 0x3c, 0x72, 0x48, 0xee, 0xd4, 0x9a, 0xa0, 0x5f, 0x65,
+ 0x2b, 0x11, 0xb7, 0x8d, 0xc3, 0xf9, 0x92, 0xa8, 0xe6, 0xdc,
+ 0x7a, 0x40, 0x0e, 0x34, 0xd8, 0xe2, 0xac, 0x96, 0x30, 0x0a,
+ 0x44, 0x7e, 0x15, 0x2f, 0x61, 0x5b, 0xfd, 0xc7, 0x89, 0xb3,
+ 0x6a, 0x50, 0x1e, 0x24, 0x82, 0xb8, 0xf6, 0xcc, 0xa7, 0x9d,
+ 0xd3, 0xe9, 0x4f, 0x75, 0x3b, 0x01, 0xed, 0xd7, 0x99, 0xa3,
+ 0x05, 0x3f, 0x71, 0x4b, 0x20, 0x1a, 0x54, 0x6e, 0xc8, 0xf2,
+ 0xbc, 0x86, 0x79, 0x43, 0x0d, 0x37, 0x91, 0xab, 0xe5, 0xdf,
+ 0xb4, 0x8e, 0xc0, 0xfa, 0x5c, 0x66, 0x28, 0x12, 0xfe, 0xc4,
+ 0x8a, 0xb0, 0x16, 0x2c, 0x62, 0x58, 0x33, 0x09, 0x47, 0x7d,
+ 0xdb, 0xe1, 0xaf, 0x95, 0x00, 0x3b, 0x76, 0x4d, 0xec, 0xd7,
+ 0x9a, 0xa1, 0xc5, 0xfe, 0xb3, 0x88, 0x29, 0x12, 0x5f, 0x64,
+ 0x97, 0xac, 0xe1, 0xda, 0x7b, 0x40, 0x0d, 0x36, 0x52, 0x69,
+ 0x24, 0x1f, 0xbe, 0x85, 0xc8, 0xf3, 0x33, 0x08, 0x45, 0x7e,
+ 0xdf, 0xe4, 0xa9, 0x92, 0xf6, 0xcd, 0x80, 0xbb, 0x1a, 0x21,
+ 0x6c, 0x57, 0xa4, 0x9f, 0xd2, 0xe9, 0x48, 0x73, 0x3e, 0x05,
+ 0x61, 0x5a, 0x17, 0x2c, 0x8d, 0xb6, 0xfb, 0xc0, 0x66, 0x5d,
+ 0x10, 0x2b, 0x8a, 0xb1, 0xfc, 0xc7, 0xa3, 0x98, 0xd5, 0xee,
+ 0x4f, 0x74, 0x39, 0x02, 0xf1, 0xca, 0x87, 0xbc, 0x1d, 0x26,
+ 0x6b, 0x50, 0x34, 0x0f, 0x42, 0x79, 0xd8, 0xe3, 0xae, 0x95,
+ 0x55, 0x6e, 0x23, 0x18, 0xb9, 0x82, 0xcf, 0xf4, 0x90, 0xab,
+ 0xe6, 0xdd, 0x7c, 0x47, 0x0a, 0x31, 0xc2, 0xf9, 0xb4, 0x8f,
+ 0x2e, 0x15, 0x58, 0x63, 0x07, 0x3c, 0x71, 0x4a, 0xeb, 0xd0,
+ 0x9d, 0xa6, 0xcc, 0xf7, 0xba, 0x81, 0x20, 0x1b, 0x56, 0x6d,
+ 0x09, 0x32, 0x7f, 0x44, 0xe5, 0xde, 0x93, 0xa8, 0x5b, 0x60,
+ 0x2d, 0x16, 0xb7, 0x8c, 0xc1, 0xfa, 0x9e, 0xa5, 0xe8, 0xd3,
+ 0x72, 0x49, 0x04, 0x3f, 0xff, 0xc4, 0x89, 0xb2, 0x13, 0x28,
+ 0x65, 0x5e, 0x3a, 0x01, 0x4c, 0x77, 0xd6, 0xed, 0xa0, 0x9b,
+ 0x68, 0x53, 0x1e, 0x25, 0x84, 0xbf, 0xf2, 0xc9, 0xad, 0x96,
+ 0xdb, 0xe0, 0x41, 0x7a, 0x37, 0x0c, 0xaa, 0x91, 0xdc, 0xe7,
+ 0x46, 0x7d, 0x30, 0x0b, 0x6f, 0x54, 0x19, 0x22, 0x83, 0xb8,
+ 0xf5, 0xce, 0x3d, 0x06, 0x4b, 0x70, 0xd1, 0xea, 0xa7, 0x9c,
+ 0xf8, 0xc3, 0x8e, 0xb5, 0x14, 0x2f, 0x62, 0x59, 0x99, 0xa2,
+ 0xef, 0xd4, 0x75, 0x4e, 0x03, 0x38, 0x5c, 0x67, 0x2a, 0x11,
+ 0xb0, 0x8b, 0xc6, 0xfd, 0x0e, 0x35, 0x78, 0x43, 0xe2, 0xd9,
+ 0x94, 0xaf, 0xcb, 0xf0, 0xbd, 0x86, 0x27, 0x1c, 0x51, 0x6a,
+ 0x00, 0x3c, 0x78, 0x44, 0xf0, 0xcc, 0x88, 0xb4, 0xfd, 0xc1,
+ 0x85, 0xb9, 0x0d, 0x31, 0x75, 0x49, 0xe7, 0xdb, 0x9f, 0xa3,
+ 0x17, 0x2b, 0x6f, 0x53, 0x1a, 0x26, 0x62, 0x5e, 0xea, 0xd6,
+ 0x92, 0xae, 0xd3, 0xef, 0xab, 0x97, 0x23, 0x1f, 0x5b, 0x67,
+ 0x2e, 0x12, 0x56, 0x6a, 0xde, 0xe2, 0xa6, 0x9a, 0x34, 0x08,
+ 0x4c, 0x70, 0xc4, 0xf8, 0xbc, 0x80, 0xc9, 0xf5, 0xb1, 0x8d,
+ 0x39, 0x05, 0x41, 0x7d, 0xbb, 0x87, 0xc3, 0xff, 0x4b, 0x77,
+ 0x33, 0x0f, 0x46, 0x7a, 0x3e, 0x02, 0xb6, 0x8a, 0xce, 0xf2,
+ 0x5c, 0x60, 0x24, 0x18, 0xac, 0x90, 0xd4, 0xe8, 0xa1, 0x9d,
+ 0xd9, 0xe5, 0x51, 0x6d, 0x29, 0x15, 0x68, 0x54, 0x10, 0x2c,
+ 0x98, 0xa4, 0xe0, 0xdc, 0x95, 0xa9, 0xed, 0xd1, 0x65, 0x59,
+ 0x1d, 0x21, 0x8f, 0xb3, 0xf7, 0xcb, 0x7f, 0x43, 0x07, 0x3b,
+ 0x72, 0x4e, 0x0a, 0x36, 0x82, 0xbe, 0xfa, 0xc6, 0x6b, 0x57,
+ 0x13, 0x2f, 0x9b, 0xa7, 0xe3, 0xdf, 0x96, 0xaa, 0xee, 0xd2,
+ 0x66, 0x5a, 0x1e, 0x22, 0x8c, 0xb0, 0xf4, 0xc8, 0x7c, 0x40,
+ 0x04, 0x38, 0x71, 0x4d, 0x09, 0x35, 0x81, 0xbd, 0xf9, 0xc5,
+ 0xb8, 0x84, 0xc0, 0xfc, 0x48, 0x74, 0x30, 0x0c, 0x45, 0x79,
+ 0x3d, 0x01, 0xb5, 0x89, 0xcd, 0xf1, 0x5f, 0x63, 0x27, 0x1b,
+ 0xaf, 0x93, 0xd7, 0xeb, 0xa2, 0x9e, 0xda, 0xe6, 0x52, 0x6e,
+ 0x2a, 0x16, 0xd0, 0xec, 0xa8, 0x94, 0x20, 0x1c, 0x58, 0x64,
+ 0x2d, 0x11, 0x55, 0x69, 0xdd, 0xe1, 0xa5, 0x99, 0x37, 0x0b,
+ 0x4f, 0x73, 0xc7, 0xfb, 0xbf, 0x83, 0xca, 0xf6, 0xb2, 0x8e,
+ 0x3a, 0x06, 0x42, 0x7e, 0x03, 0x3f, 0x7b, 0x47, 0xf3, 0xcf,
+ 0x8b, 0xb7, 0xfe, 0xc2, 0x86, 0xba, 0x0e, 0x32, 0x76, 0x4a,
+ 0xe4, 0xd8, 0x9c, 0xa0, 0x14, 0x28, 0x6c, 0x50, 0x19, 0x25,
+ 0x61, 0x5d, 0xe9, 0xd5, 0x91, 0xad, 0x00, 0x3d, 0x7a, 0x47,
+ 0xf4, 0xc9, 0x8e, 0xb3, 0xf5, 0xc8, 0x8f, 0xb2, 0x01, 0x3c,
+ 0x7b, 0x46, 0xf7, 0xca, 0x8d, 0xb0, 0x03, 0x3e, 0x79, 0x44,
+ 0x02, 0x3f, 0x78, 0x45, 0xf6, 0xcb, 0x8c, 0xb1, 0xf3, 0xce,
+ 0x89, 0xb4, 0x07, 0x3a, 0x7d, 0x40, 0x06, 0x3b, 0x7c, 0x41,
+ 0xf2, 0xcf, 0x88, 0xb5, 0x04, 0x39, 0x7e, 0x43, 0xf0, 0xcd,
+ 0x8a, 0xb7, 0xf1, 0xcc, 0x8b, 0xb6, 0x05, 0x38, 0x7f, 0x42,
+ 0xfb, 0xc6, 0x81, 0xbc, 0x0f, 0x32, 0x75, 0x48, 0x0e, 0x33,
+ 0x74, 0x49, 0xfa, 0xc7, 0x80, 0xbd, 0x0c, 0x31, 0x76, 0x4b,
+ 0xf8, 0xc5, 0x82, 0xbf, 0xf9, 0xc4, 0x83, 0xbe, 0x0d, 0x30,
+ 0x77, 0x4a, 0x08, 0x35, 0x72, 0x4f, 0xfc, 0xc1, 0x86, 0xbb,
+ 0xfd, 0xc0, 0x87, 0xba, 0x09, 0x34, 0x73, 0x4e, 0xff, 0xc2,
+ 0x85, 0xb8, 0x0b, 0x36, 0x71, 0x4c, 0x0a, 0x37, 0x70, 0x4d,
+ 0xfe, 0xc3, 0x84, 0xb9, 0xeb, 0xd6, 0x91, 0xac, 0x1f, 0x22,
+ 0x65, 0x58, 0x1e, 0x23, 0x64, 0x59, 0xea, 0xd7, 0x90, 0xad,
+ 0x1c, 0x21, 0x66, 0x5b, 0xe8, 0xd5, 0x92, 0xaf, 0xe9, 0xd4,
+ 0x93, 0xae, 0x1d, 0x20, 0x67, 0x5a, 0x18, 0x25, 0x62, 0x5f,
+ 0xec, 0xd1, 0x96, 0xab, 0xed, 0xd0, 0x97, 0xaa, 0x19, 0x24,
+ 0x63, 0x5e, 0xef, 0xd2, 0x95, 0xa8, 0x1b, 0x26, 0x61, 0x5c,
+ 0x1a, 0x27, 0x60, 0x5d, 0xee, 0xd3, 0x94, 0xa9, 0x10, 0x2d,
+ 0x6a, 0x57, 0xe4, 0xd9, 0x9e, 0xa3, 0xe5, 0xd8, 0x9f, 0xa2,
+ 0x11, 0x2c, 0x6b, 0x56, 0xe7, 0xda, 0x9d, 0xa0, 0x13, 0x2e,
+ 0x69, 0x54, 0x12, 0x2f, 0x68, 0x55, 0xe6, 0xdb, 0x9c, 0xa1,
+ 0xe3, 0xde, 0x99, 0xa4, 0x17, 0x2a, 0x6d, 0x50, 0x16, 0x2b,
+ 0x6c, 0x51, 0xe2, 0xdf, 0x98, 0xa5, 0x14, 0x29, 0x6e, 0x53,
+ 0xe0, 0xdd, 0x9a, 0xa7, 0xe1, 0xdc, 0x9b, 0xa6, 0x15, 0x28,
+ 0x6f, 0x52, 0x00, 0x3e, 0x7c, 0x42, 0xf8, 0xc6, 0x84, 0xba,
+ 0xed, 0xd3, 0x91, 0xaf, 0x15, 0x2b, 0x69, 0x57, 0xc7, 0xf9,
+ 0xbb, 0x85, 0x3f, 0x01, 0x43, 0x7d, 0x2a, 0x14, 0x56, 0x68,
+ 0xd2, 0xec, 0xae, 0x90, 0x93, 0xad, 0xef, 0xd1, 0x6b, 0x55,
+ 0x17, 0x29, 0x7e, 0x40, 0x02, 0x3c, 0x86, 0xb8, 0xfa, 0xc4,
+ 0x54, 0x6a, 0x28, 0x16, 0xac, 0x92, 0xd0, 0xee, 0xb9, 0x87,
+ 0xc5, 0xfb, 0x41, 0x7f, 0x3d, 0x03, 0x3b, 0x05, 0x47, 0x79,
+ 0xc3, 0xfd, 0xbf, 0x81, 0xd6, 0xe8, 0xaa, 0x94, 0x2e, 0x10,
+ 0x52, 0x6c, 0xfc, 0xc2, 0x80, 0xbe, 0x04, 0x3a, 0x78, 0x46,
+ 0x11, 0x2f, 0x6d, 0x53, 0xe9, 0xd7, 0x95, 0xab, 0xa8, 0x96,
+ 0xd4, 0xea, 0x50, 0x6e, 0x2c, 0x12, 0x45, 0x7b, 0x39, 0x07,
+ 0xbd, 0x83, 0xc1, 0xff, 0x6f, 0x51, 0x13, 0x2d, 0x97, 0xa9,
+ 0xeb, 0xd5, 0x82, 0xbc, 0xfe, 0xc0, 0x7a, 0x44, 0x06, 0x38,
+ 0x76, 0x48, 0x0a, 0x34, 0x8e, 0xb0, 0xf2, 0xcc, 0x9b, 0xa5,
+ 0xe7, 0xd9, 0x63, 0x5d, 0x1f, 0x21, 0xb1, 0x8f, 0xcd, 0xf3,
+ 0x49, 0x77, 0x35, 0x0b, 0x5c, 0x62, 0x20, 0x1e, 0xa4, 0x9a,
+ 0xd8, 0xe6, 0xe5, 0xdb, 0x99, 0xa7, 0x1d, 0x23, 0x61, 0x5f,
+ 0x08, 0x36, 0x74, 0x4a, 0xf0, 0xce, 0x8c, 0xb2, 0x22, 0x1c,
+ 0x5e, 0x60, 0xda, 0xe4, 0xa6, 0x98, 0xcf, 0xf1, 0xb3, 0x8d,
+ 0x37, 0x09, 0x4b, 0x75, 0x4d, 0x73, 0x31, 0x0f, 0xb5, 0x8b,
+ 0xc9, 0xf7, 0xa0, 0x9e, 0xdc, 0xe2, 0x58, 0x66, 0x24, 0x1a,
+ 0x8a, 0xb4, 0xf6, 0xc8, 0x72, 0x4c, 0x0e, 0x30, 0x67, 0x59,
+ 0x1b, 0x25, 0x9f, 0xa1, 0xe3, 0xdd, 0xde, 0xe0, 0xa2, 0x9c,
+ 0x26, 0x18, 0x5a, 0x64, 0x33, 0x0d, 0x4f, 0x71, 0xcb, 0xf5,
+ 0xb7, 0x89, 0x19, 0x27, 0x65, 0x5b, 0xe1, 0xdf, 0x9d, 0xa3,
+ 0xf4, 0xca, 0x88, 0xb6, 0x0c, 0x32, 0x70, 0x4e, 0x00, 0x3f,
+ 0x7e, 0x41, 0xfc, 0xc3, 0x82, 0xbd, 0xe5, 0xda, 0x9b, 0xa4,
+ 0x19, 0x26, 0x67, 0x58, 0xd7, 0xe8, 0xa9, 0x96, 0x2b, 0x14,
+ 0x55, 0x6a, 0x32, 0x0d, 0x4c, 0x73, 0xce, 0xf1, 0xb0, 0x8f,
+ 0xb3, 0x8c, 0xcd, 0xf2, 0x4f, 0x70, 0x31, 0x0e, 0x56, 0x69,
+ 0x28, 0x17, 0xaa, 0x95, 0xd4, 0xeb, 0x64, 0x5b, 0x1a, 0x25,
+ 0x98, 0xa7, 0xe6, 0xd9, 0x81, 0xbe, 0xff, 0xc0, 0x7d, 0x42,
+ 0x03, 0x3c, 0x7b, 0x44, 0x05, 0x3a, 0x87, 0xb8, 0xf9, 0xc6,
+ 0x9e, 0xa1, 0xe0, 0xdf, 0x62, 0x5d, 0x1c, 0x23, 0xac, 0x93,
+ 0xd2, 0xed, 0x50, 0x6f, 0x2e, 0x11, 0x49, 0x76, 0x37, 0x08,
+ 0xb5, 0x8a, 0xcb, 0xf4, 0xc8, 0xf7, 0xb6, 0x89, 0x34, 0x0b,
+ 0x4a, 0x75, 0x2d, 0x12, 0x53, 0x6c, 0xd1, 0xee, 0xaf, 0x90,
+ 0x1f, 0x20, 0x61, 0x5e, 0xe3, 0xdc, 0x9d, 0xa2, 0xfa, 0xc5,
+ 0x84, 0xbb, 0x06, 0x39, 0x78, 0x47, 0xf6, 0xc9, 0x88, 0xb7,
+ 0x0a, 0x35, 0x74, 0x4b, 0x13, 0x2c, 0x6d, 0x52, 0xef, 0xd0,
+ 0x91, 0xae, 0x21, 0x1e, 0x5f, 0x60, 0xdd, 0xe2, 0xa3, 0x9c,
+ 0xc4, 0xfb, 0xba, 0x85, 0x38, 0x07, 0x46, 0x79, 0x45, 0x7a,
+ 0x3b, 0x04, 0xb9, 0x86, 0xc7, 0xf8, 0xa0, 0x9f, 0xde, 0xe1,
+ 0x5c, 0x63, 0x22, 0x1d, 0x92, 0xad, 0xec, 0xd3, 0x6e, 0x51,
+ 0x10, 0x2f, 0x77, 0x48, 0x09, 0x36, 0x8b, 0xb4, 0xf5, 0xca,
+ 0x8d, 0xb2, 0xf3, 0xcc, 0x71, 0x4e, 0x0f, 0x30, 0x68, 0x57,
+ 0x16, 0x29, 0x94, 0xab, 0xea, 0xd5, 0x5a, 0x65, 0x24, 0x1b,
+ 0xa6, 0x99, 0xd8, 0xe7, 0xbf, 0x80, 0xc1, 0xfe, 0x43, 0x7c,
+ 0x3d, 0x02, 0x3e, 0x01, 0x40, 0x7f, 0xc2, 0xfd, 0xbc, 0x83,
+ 0xdb, 0xe4, 0xa5, 0x9a, 0x27, 0x18, 0x59, 0x66, 0xe9, 0xd6,
+ 0x97, 0xa8, 0x15, 0x2a, 0x6b, 0x54, 0x0c, 0x33, 0x72, 0x4d,
+ 0xf0, 0xcf, 0x8e, 0xb1, 0x00, 0x40, 0x80, 0xc0, 0x1d, 0x5d,
+ 0x9d, 0xdd, 0x3a, 0x7a, 0xba, 0xfa, 0x27, 0x67, 0xa7, 0xe7,
+ 0x74, 0x34, 0xf4, 0xb4, 0x69, 0x29, 0xe9, 0xa9, 0x4e, 0x0e,
+ 0xce, 0x8e, 0x53, 0x13, 0xd3, 0x93, 0xe8, 0xa8, 0x68, 0x28,
+ 0xf5, 0xb5, 0x75, 0x35, 0xd2, 0x92, 0x52, 0x12, 0xcf, 0x8f,
+ 0x4f, 0x0f, 0x9c, 0xdc, 0x1c, 0x5c, 0x81, 0xc1, 0x01, 0x41,
+ 0xa6, 0xe6, 0x26, 0x66, 0xbb, 0xfb, 0x3b, 0x7b, 0xcd, 0x8d,
+ 0x4d, 0x0d, 0xd0, 0x90, 0x50, 0x10, 0xf7, 0xb7, 0x77, 0x37,
+ 0xea, 0xaa, 0x6a, 0x2a, 0xb9, 0xf9, 0x39, 0x79, 0xa4, 0xe4,
+ 0x24, 0x64, 0x83, 0xc3, 0x03, 0x43, 0x9e, 0xde, 0x1e, 0x5e,
+ 0x25, 0x65, 0xa5, 0xe5, 0x38, 0x78, 0xb8, 0xf8, 0x1f, 0x5f,
+ 0x9f, 0xdf, 0x02, 0x42, 0x82, 0xc2, 0x51, 0x11, 0xd1, 0x91,
+ 0x4c, 0x0c, 0xcc, 0x8c, 0x6b, 0x2b, 0xeb, 0xab, 0x76, 0x36,
+ 0xf6, 0xb6, 0x87, 0xc7, 0x07, 0x47, 0x9a, 0xda, 0x1a, 0x5a,
+ 0xbd, 0xfd, 0x3d, 0x7d, 0xa0, 0xe0, 0x20, 0x60, 0xf3, 0xb3,
+ 0x73, 0x33, 0xee, 0xae, 0x6e, 0x2e, 0xc9, 0x89, 0x49, 0x09,
+ 0xd4, 0x94, 0x54, 0x14, 0x6f, 0x2f, 0xef, 0xaf, 0x72, 0x32,
+ 0xf2, 0xb2, 0x55, 0x15, 0xd5, 0x95, 0x48, 0x08, 0xc8, 0x88,
+ 0x1b, 0x5b, 0x9b, 0xdb, 0x06, 0x46, 0x86, 0xc6, 0x21, 0x61,
+ 0xa1, 0xe1, 0x3c, 0x7c, 0xbc, 0xfc, 0x4a, 0x0a, 0xca, 0x8a,
+ 0x57, 0x17, 0xd7, 0x97, 0x70, 0x30, 0xf0, 0xb0, 0x6d, 0x2d,
+ 0xed, 0xad, 0x3e, 0x7e, 0xbe, 0xfe, 0x23, 0x63, 0xa3, 0xe3,
+ 0x04, 0x44, 0x84, 0xc4, 0x19, 0x59, 0x99, 0xd9, 0xa2, 0xe2,
+ 0x22, 0x62, 0xbf, 0xff, 0x3f, 0x7f, 0x98, 0xd8, 0x18, 0x58,
+ 0x85, 0xc5, 0x05, 0x45, 0xd6, 0x96, 0x56, 0x16, 0xcb, 0x8b,
+ 0x4b, 0x0b, 0xec, 0xac, 0x6c, 0x2c, 0xf1, 0xb1, 0x71, 0x31,
+ 0x00, 0x41, 0x82, 0xc3, 0x19, 0x58, 0x9b, 0xda, 0x32, 0x73,
+ 0xb0, 0xf1, 0x2b, 0x6a, 0xa9, 0xe8, 0x64, 0x25, 0xe6, 0xa7,
+ 0x7d, 0x3c, 0xff, 0xbe, 0x56, 0x17, 0xd4, 0x95, 0x4f, 0x0e,
+ 0xcd, 0x8c, 0xc8, 0x89, 0x4a, 0x0b, 0xd1, 0x90, 0x53, 0x12,
+ 0xfa, 0xbb, 0x78, 0x39, 0xe3, 0xa2, 0x61, 0x20, 0xac, 0xed,
+ 0x2e, 0x6f, 0xb5, 0xf4, 0x37, 0x76, 0x9e, 0xdf, 0x1c, 0x5d,
+ 0x87, 0xc6, 0x05, 0x44, 0x8d, 0xcc, 0x0f, 0x4e, 0x94, 0xd5,
+ 0x16, 0x57, 0xbf, 0xfe, 0x3d, 0x7c, 0xa6, 0xe7, 0x24, 0x65,
+ 0xe9, 0xa8, 0x6b, 0x2a, 0xf0, 0xb1, 0x72, 0x33, 0xdb, 0x9a,
+ 0x59, 0x18, 0xc2, 0x83, 0x40, 0x01, 0x45, 0x04, 0xc7, 0x86,
+ 0x5c, 0x1d, 0xde, 0x9f, 0x77, 0x36, 0xf5, 0xb4, 0x6e, 0x2f,
+ 0xec, 0xad, 0x21, 0x60, 0xa3, 0xe2, 0x38, 0x79, 0xba, 0xfb,
+ 0x13, 0x52, 0x91, 0xd0, 0x0a, 0x4b, 0x88, 0xc9, 0x07, 0x46,
+ 0x85, 0xc4, 0x1e, 0x5f, 0x9c, 0xdd, 0x35, 0x74, 0xb7, 0xf6,
+ 0x2c, 0x6d, 0xae, 0xef, 0x63, 0x22, 0xe1, 0xa0, 0x7a, 0x3b,
+ 0xf8, 0xb9, 0x51, 0x10, 0xd3, 0x92, 0x48, 0x09, 0xca, 0x8b,
+ 0xcf, 0x8e, 0x4d, 0x0c, 0xd6, 0x97, 0x54, 0x15, 0xfd, 0xbc,
+ 0x7f, 0x3e, 0xe4, 0xa5, 0x66, 0x27, 0xab, 0xea, 0x29, 0x68,
+ 0xb2, 0xf3, 0x30, 0x71, 0x99, 0xd8, 0x1b, 0x5a, 0x80, 0xc1,
+ 0x02, 0x43, 0x8a, 0xcb, 0x08, 0x49, 0x93, 0xd2, 0x11, 0x50,
+ 0xb8, 0xf9, 0x3a, 0x7b, 0xa1, 0xe0, 0x23, 0x62, 0xee, 0xaf,
+ 0x6c, 0x2d, 0xf7, 0xb6, 0x75, 0x34, 0xdc, 0x9d, 0x5e, 0x1f,
+ 0xc5, 0x84, 0x47, 0x06, 0x42, 0x03, 0xc0, 0x81, 0x5b, 0x1a,
+ 0xd9, 0x98, 0x70, 0x31, 0xf2, 0xb3, 0x69, 0x28, 0xeb, 0xaa,
+ 0x26, 0x67, 0xa4, 0xe5, 0x3f, 0x7e, 0xbd, 0xfc, 0x14, 0x55,
+ 0x96, 0xd7, 0x0d, 0x4c, 0x8f, 0xce, 0x00, 0x42, 0x84, 0xc6,
+ 0x15, 0x57, 0x91, 0xd3, 0x2a, 0x68, 0xae, 0xec, 0x3f, 0x7d,
+ 0xbb, 0xf9, 0x54, 0x16, 0xd0, 0x92, 0x41, 0x03, 0xc5, 0x87,
+ 0x7e, 0x3c, 0xfa, 0xb8, 0x6b, 0x29, 0xef, 0xad, 0xa8, 0xea,
+ 0x2c, 0x6e, 0xbd, 0xff, 0x39, 0x7b, 0x82, 0xc0, 0x06, 0x44,
+ 0x97, 0xd5, 0x13, 0x51, 0xfc, 0xbe, 0x78, 0x3a, 0xe9, 0xab,
+ 0x6d, 0x2f, 0xd6, 0x94, 0x52, 0x10, 0xc3, 0x81, 0x47, 0x05,
+ 0x4d, 0x0f, 0xc9, 0x8b, 0x58, 0x1a, 0xdc, 0x9e, 0x67, 0x25,
+ 0xe3, 0xa1, 0x72, 0x30, 0xf6, 0xb4, 0x19, 0x5b, 0x9d, 0xdf,
+ 0x0c, 0x4e, 0x88, 0xca, 0x33, 0x71, 0xb7, 0xf5, 0x26, 0x64,
+ 0xa2, 0xe0, 0xe5, 0xa7, 0x61, 0x23, 0xf0, 0xb2, 0x74, 0x36,
+ 0xcf, 0x8d, 0x4b, 0x09, 0xda, 0x98, 0x5e, 0x1c, 0xb1, 0xf3,
+ 0x35, 0x77, 0xa4, 0xe6, 0x20, 0x62, 0x9b, 0xd9, 0x1f, 0x5d,
+ 0x8e, 0xcc, 0x0a, 0x48, 0x9a, 0xd8, 0x1e, 0x5c, 0x8f, 0xcd,
+ 0x0b, 0x49, 0xb0, 0xf2, 0x34, 0x76, 0xa5, 0xe7, 0x21, 0x63,
+ 0xce, 0x8c, 0x4a, 0x08, 0xdb, 0x99, 0x5f, 0x1d, 0xe4, 0xa6,
+ 0x60, 0x22, 0xf1, 0xb3, 0x75, 0x37, 0x32, 0x70, 0xb6, 0xf4,
+ 0x27, 0x65, 0xa3, 0xe1, 0x18, 0x5a, 0x9c, 0xde, 0x0d, 0x4f,
+ 0x89, 0xcb, 0x66, 0x24, 0xe2, 0xa0, 0x73, 0x31, 0xf7, 0xb5,
+ 0x4c, 0x0e, 0xc8, 0x8a, 0x59, 0x1b, 0xdd, 0x9f, 0xd7, 0x95,
+ 0x53, 0x11, 0xc2, 0x80, 0x46, 0x04, 0xfd, 0xbf, 0x79, 0x3b,
+ 0xe8, 0xaa, 0x6c, 0x2e, 0x83, 0xc1, 0x07, 0x45, 0x96, 0xd4,
+ 0x12, 0x50, 0xa9, 0xeb, 0x2d, 0x6f, 0xbc, 0xfe, 0x38, 0x7a,
+ 0x7f, 0x3d, 0xfb, 0xb9, 0x6a, 0x28, 0xee, 0xac, 0x55, 0x17,
+ 0xd1, 0x93, 0x40, 0x02, 0xc4, 0x86, 0x2b, 0x69, 0xaf, 0xed,
+ 0x3e, 0x7c, 0xba, 0xf8, 0x01, 0x43, 0x85, 0xc7, 0x14, 0x56,
+ 0x90, 0xd2, 0x00, 0x43, 0x86, 0xc5, 0x11, 0x52, 0x97, 0xd4,
+ 0x22, 0x61, 0xa4, 0xe7, 0x33, 0x70, 0xb5, 0xf6, 0x44, 0x07,
+ 0xc2, 0x81, 0x55, 0x16, 0xd3, 0x90, 0x66, 0x25, 0xe0, 0xa3,
+ 0x77, 0x34, 0xf1, 0xb2, 0x88, 0xcb, 0x0e, 0x4d, 0x99, 0xda,
+ 0x1f, 0x5c, 0xaa, 0xe9, 0x2c, 0x6f, 0xbb, 0xf8, 0x3d, 0x7e,
+ 0xcc, 0x8f, 0x4a, 0x09, 0xdd, 0x9e, 0x5b, 0x18, 0xee, 0xad,
+ 0x68, 0x2b, 0xff, 0xbc, 0x79, 0x3a, 0x0d, 0x4e, 0x8b, 0xc8,
+ 0x1c, 0x5f, 0x9a, 0xd9, 0x2f, 0x6c, 0xa9, 0xea, 0x3e, 0x7d,
+ 0xb8, 0xfb, 0x49, 0x0a, 0xcf, 0x8c, 0x58, 0x1b, 0xde, 0x9d,
+ 0x6b, 0x28, 0xed, 0xae, 0x7a, 0x39, 0xfc, 0xbf, 0x85, 0xc6,
+ 0x03, 0x40, 0x94, 0xd7, 0x12, 0x51, 0xa7, 0xe4, 0x21, 0x62,
+ 0xb6, 0xf5, 0x30, 0x73, 0xc1, 0x82, 0x47, 0x04, 0xd0, 0x93,
+ 0x56, 0x15, 0xe3, 0xa0, 0x65, 0x26, 0xf2, 0xb1, 0x74, 0x37,
+ 0x1a, 0x59, 0x9c, 0xdf, 0x0b, 0x48, 0x8d, 0xce, 0x38, 0x7b,
+ 0xbe, 0xfd, 0x29, 0x6a, 0xaf, 0xec, 0x5e, 0x1d, 0xd8, 0x9b,
+ 0x4f, 0x0c, 0xc9, 0x8a, 0x7c, 0x3f, 0xfa, 0xb9, 0x6d, 0x2e,
+ 0xeb, 0xa8, 0x92, 0xd1, 0x14, 0x57, 0x83, 0xc0, 0x05, 0x46,
+ 0xb0, 0xf3, 0x36, 0x75, 0xa1, 0xe2, 0x27, 0x64, 0xd6, 0x95,
+ 0x50, 0x13, 0xc7, 0x84, 0x41, 0x02, 0xf4, 0xb7, 0x72, 0x31,
+ 0xe5, 0xa6, 0x63, 0x20, 0x17, 0x54, 0x91, 0xd2, 0x06, 0x45,
+ 0x80, 0xc3, 0x35, 0x76, 0xb3, 0xf0, 0x24, 0x67, 0xa2, 0xe1,
+ 0x53, 0x10, 0xd5, 0x96, 0x42, 0x01, 0xc4, 0x87, 0x71, 0x32,
+ 0xf7, 0xb4, 0x60, 0x23, 0xe6, 0xa5, 0x9f, 0xdc, 0x19, 0x5a,
+ 0x8e, 0xcd, 0x08, 0x4b, 0xbd, 0xfe, 0x3b, 0x78, 0xac, 0xef,
+ 0x2a, 0x69, 0xdb, 0x98, 0x5d, 0x1e, 0xca, 0x89, 0x4c, 0x0f,
+ 0xf9, 0xba, 0x7f, 0x3c, 0xe8, 0xab, 0x6e, 0x2d, 0x00, 0x44,
+ 0x88, 0xcc, 0x0d, 0x49, 0x85, 0xc1, 0x1a, 0x5e, 0x92, 0xd6,
+ 0x17, 0x53, 0x9f, 0xdb, 0x34, 0x70, 0xbc, 0xf8, 0x39, 0x7d,
+ 0xb1, 0xf5, 0x2e, 0x6a, 0xa6, 0xe2, 0x23, 0x67, 0xab, 0xef,
+ 0x68, 0x2c, 0xe0, 0xa4, 0x65, 0x21, 0xed, 0xa9, 0x72, 0x36,
+ 0xfa, 0xbe, 0x7f, 0x3b, 0xf7, 0xb3, 0x5c, 0x18, 0xd4, 0x90,
+ 0x51, 0x15, 0xd9, 0x9d, 0x46, 0x02, 0xce, 0x8a, 0x4b, 0x0f,
+ 0xc3, 0x87, 0xd0, 0x94, 0x58, 0x1c, 0xdd, 0x99, 0x55, 0x11,
+ 0xca, 0x8e, 0x42, 0x06, 0xc7, 0x83, 0x4f, 0x0b, 0xe4, 0xa0,
+ 0x6c, 0x28, 0xe9, 0xad, 0x61, 0x25, 0xfe, 0xba, 0x76, 0x32,
+ 0xf3, 0xb7, 0x7b, 0x3f, 0xb8, 0xfc, 0x30, 0x74, 0xb5, 0xf1,
+ 0x3d, 0x79, 0xa2, 0xe6, 0x2a, 0x6e, 0xaf, 0xeb, 0x27, 0x63,
+ 0x8c, 0xc8, 0x04, 0x40, 0x81, 0xc5, 0x09, 0x4d, 0x96, 0xd2,
+ 0x1e, 0x5a, 0x9b, 0xdf, 0x13, 0x57, 0xbd, 0xf9, 0x35, 0x71,
+ 0xb0, 0xf4, 0x38, 0x7c, 0xa7, 0xe3, 0x2f, 0x6b, 0xaa, 0xee,
+ 0x22, 0x66, 0x89, 0xcd, 0x01, 0x45, 0x84, 0xc0, 0x0c, 0x48,
+ 0x93, 0xd7, 0x1b, 0x5f, 0x9e, 0xda, 0x16, 0x52, 0xd5, 0x91,
+ 0x5d, 0x19, 0xd8, 0x9c, 0x50, 0x14, 0xcf, 0x8b, 0x47, 0x03,
+ 0xc2, 0x86, 0x4a, 0x0e, 0xe1, 0xa5, 0x69, 0x2d, 0xec, 0xa8,
+ 0x64, 0x20, 0xfb, 0xbf, 0x73, 0x37, 0xf6, 0xb2, 0x7e, 0x3a,
+ 0x6d, 0x29, 0xe5, 0xa1, 0x60, 0x24, 0xe8, 0xac, 0x77, 0x33,
+ 0xff, 0xbb, 0x7a, 0x3e, 0xf2, 0xb6, 0x59, 0x1d, 0xd1, 0x95,
+ 0x54, 0x10, 0xdc, 0x98, 0x43, 0x07, 0xcb, 0x8f, 0x4e, 0x0a,
+ 0xc6, 0x82, 0x05, 0x41, 0x8d, 0xc9, 0x08, 0x4c, 0x80, 0xc4,
+ 0x1f, 0x5b, 0x97, 0xd3, 0x12, 0x56, 0x9a, 0xde, 0x31, 0x75,
+ 0xb9, 0xfd, 0x3c, 0x78, 0xb4, 0xf0, 0x2b, 0x6f, 0xa3, 0xe7,
+ 0x26, 0x62, 0xae, 0xea, 0x00, 0x45, 0x8a, 0xcf, 0x09, 0x4c,
+ 0x83, 0xc6, 0x12, 0x57, 0x98, 0xdd, 0x1b, 0x5e, 0x91, 0xd4,
+ 0x24, 0x61, 0xae, 0xeb, 0x2d, 0x68, 0xa7, 0xe2, 0x36, 0x73,
+ 0xbc, 0xf9, 0x3f, 0x7a, 0xb5, 0xf0, 0x48, 0x0d, 0xc2, 0x87,
+ 0x41, 0x04, 0xcb, 0x8e, 0x5a, 0x1f, 0xd0, 0x95, 0x53, 0x16,
+ 0xd9, 0x9c, 0x6c, 0x29, 0xe6, 0xa3, 0x65, 0x20, 0xef, 0xaa,
+ 0x7e, 0x3b, 0xf4, 0xb1, 0x77, 0x32, 0xfd, 0xb8, 0x90, 0xd5,
+ 0x1a, 0x5f, 0x99, 0xdc, 0x13, 0x56, 0x82, 0xc7, 0x08, 0x4d,
+ 0x8b, 0xce, 0x01, 0x44, 0xb4, 0xf1, 0x3e, 0x7b, 0xbd, 0xf8,
+ 0x37, 0x72, 0xa6, 0xe3, 0x2c, 0x69, 0xaf, 0xea, 0x25, 0x60,
+ 0xd8, 0x9d, 0x52, 0x17, 0xd1, 0x94, 0x5b, 0x1e, 0xca, 0x8f,
+ 0x40, 0x05, 0xc3, 0x86, 0x49, 0x0c, 0xfc, 0xb9, 0x76, 0x33,
+ 0xf5, 0xb0, 0x7f, 0x3a, 0xee, 0xab, 0x64, 0x21, 0xe7, 0xa2,
+ 0x6d, 0x28, 0x3d, 0x78, 0xb7, 0xf2, 0x34, 0x71, 0xbe, 0xfb,
+ 0x2f, 0x6a, 0xa5, 0xe0, 0x26, 0x63, 0xac, 0xe9, 0x19, 0x5c,
+ 0x93, 0xd6, 0x10, 0x55, 0x9a, 0xdf, 0x0b, 0x4e, 0x81, 0xc4,
+ 0x02, 0x47, 0x88, 0xcd, 0x75, 0x30, 0xff, 0xba, 0x7c, 0x39,
+ 0xf6, 0xb3, 0x67, 0x22, 0xed, 0xa8, 0x6e, 0x2b, 0xe4, 0xa1,
+ 0x51, 0x14, 0xdb, 0x9e, 0x58, 0x1d, 0xd2, 0x97, 0x43, 0x06,
+ 0xc9, 0x8c, 0x4a, 0x0f, 0xc0, 0x85, 0xad, 0xe8, 0x27, 0x62,
+ 0xa4, 0xe1, 0x2e, 0x6b, 0xbf, 0xfa, 0x35, 0x70, 0xb6, 0xf3,
+ 0x3c, 0x79, 0x89, 0xcc, 0x03, 0x46, 0x80, 0xc5, 0x0a, 0x4f,
+ 0x9b, 0xde, 0x11, 0x54, 0x92, 0xd7, 0x18, 0x5d, 0xe5, 0xa0,
+ 0x6f, 0x2a, 0xec, 0xa9, 0x66, 0x23, 0xf7, 0xb2, 0x7d, 0x38,
+ 0xfe, 0xbb, 0x74, 0x31, 0xc1, 0x84, 0x4b, 0x0e, 0xc8, 0x8d,
+ 0x42, 0x07, 0xd3, 0x96, 0x59, 0x1c, 0xda, 0x9f, 0x50, 0x15,
+ 0x00, 0x46, 0x8c, 0xca, 0x05, 0x43, 0x89, 0xcf, 0x0a, 0x4c,
+ 0x86, 0xc0, 0x0f, 0x49, 0x83, 0xc5, 0x14, 0x52, 0x98, 0xde,
+ 0x11, 0x57, 0x9d, 0xdb, 0x1e, 0x58, 0x92, 0xd4, 0x1b, 0x5d,
+ 0x97, 0xd1, 0x28, 0x6e, 0xa4, 0xe2, 0x2d, 0x6b, 0xa1, 0xe7,
+ 0x22, 0x64, 0xae, 0xe8, 0x27, 0x61, 0xab, 0xed, 0x3c, 0x7a,
+ 0xb0, 0xf6, 0x39, 0x7f, 0xb5, 0xf3, 0x36, 0x70, 0xba, 0xfc,
+ 0x33, 0x75, 0xbf, 0xf9, 0x50, 0x16, 0xdc, 0x9a, 0x55, 0x13,
+ 0xd9, 0x9f, 0x5a, 0x1c, 0xd6, 0x90, 0x5f, 0x19, 0xd3, 0x95,
+ 0x44, 0x02, 0xc8, 0x8e, 0x41, 0x07, 0xcd, 0x8b, 0x4e, 0x08,
+ 0xc2, 0x84, 0x4b, 0x0d, 0xc7, 0x81, 0x78, 0x3e, 0xf4, 0xb2,
+ 0x7d, 0x3b, 0xf1, 0xb7, 0x72, 0x34, 0xfe, 0xb8, 0x77, 0x31,
+ 0xfb, 0xbd, 0x6c, 0x2a, 0xe0, 0xa6, 0x69, 0x2f, 0xe5, 0xa3,
+ 0x66, 0x20, 0xea, 0xac, 0x63, 0x25, 0xef, 0xa9, 0xa0, 0xe6,
+ 0x2c, 0x6a, 0xa5, 0xe3, 0x29, 0x6f, 0xaa, 0xec, 0x26, 0x60,
+ 0xaf, 0xe9, 0x23, 0x65, 0xb4, 0xf2, 0x38, 0x7e, 0xb1, 0xf7,
+ 0x3d, 0x7b, 0xbe, 0xf8, 0x32, 0x74, 0xbb, 0xfd, 0x37, 0x71,
+ 0x88, 0xce, 0x04, 0x42, 0x8d, 0xcb, 0x01, 0x47, 0x82, 0xc4,
+ 0x0e, 0x48, 0x87, 0xc1, 0x0b, 0x4d, 0x9c, 0xda, 0x10, 0x56,
+ 0x99, 0xdf, 0x15, 0x53, 0x96, 0xd0, 0x1a, 0x5c, 0x93, 0xd5,
+ 0x1f, 0x59, 0xf0, 0xb6, 0x7c, 0x3a, 0xf5, 0xb3, 0x79, 0x3f,
+ 0xfa, 0xbc, 0x76, 0x30, 0xff, 0xb9, 0x73, 0x35, 0xe4, 0xa2,
+ 0x68, 0x2e, 0xe1, 0xa7, 0x6d, 0x2b, 0xee, 0xa8, 0x62, 0x24,
+ 0xeb, 0xad, 0x67, 0x21, 0xd8, 0x9e, 0x54, 0x12, 0xdd, 0x9b,
+ 0x51, 0x17, 0xd2, 0x94, 0x5e, 0x18, 0xd7, 0x91, 0x5b, 0x1d,
+ 0xcc, 0x8a, 0x40, 0x06, 0xc9, 0x8f, 0x45, 0x03, 0xc6, 0x80,
+ 0x4a, 0x0c, 0xc3, 0x85, 0x4f, 0x09, 0x00, 0x47, 0x8e, 0xc9,
+ 0x01, 0x46, 0x8f, 0xc8, 0x02, 0x45, 0x8c, 0xcb, 0x03, 0x44,
+ 0x8d, 0xca, 0x04, 0x43, 0x8a, 0xcd, 0x05, 0x42, 0x8b, 0xcc,
+ 0x06, 0x41, 0x88, 0xcf, 0x07, 0x40, 0x89, 0xce, 0x08, 0x4f,
+ 0x86, 0xc1, 0x09, 0x4e, 0x87, 0xc0, 0x0a, 0x4d, 0x84, 0xc3,
+ 0x0b, 0x4c, 0x85, 0xc2, 0x0c, 0x4b, 0x82, 0xc5, 0x0d, 0x4a,
+ 0x83, 0xc4, 0x0e, 0x49, 0x80, 0xc7, 0x0f, 0x48, 0x81, 0xc6,
+ 0x10, 0x57, 0x9e, 0xd9, 0x11, 0x56, 0x9f, 0xd8, 0x12, 0x55,
+ 0x9c, 0xdb, 0x13, 0x54, 0x9d, 0xda, 0x14, 0x53, 0x9a, 0xdd,
+ 0x15, 0x52, 0x9b, 0xdc, 0x16, 0x51, 0x98, 0xdf, 0x17, 0x50,
+ 0x99, 0xde, 0x18, 0x5f, 0x96, 0xd1, 0x19, 0x5e, 0x97, 0xd0,
+ 0x1a, 0x5d, 0x94, 0xd3, 0x1b, 0x5c, 0x95, 0xd2, 0x1c, 0x5b,
+ 0x92, 0xd5, 0x1d, 0x5a, 0x93, 0xd4, 0x1e, 0x59, 0x90, 0xd7,
+ 0x1f, 0x58, 0x91, 0xd6, 0x20, 0x67, 0xae, 0xe9, 0x21, 0x66,
+ 0xaf, 0xe8, 0x22, 0x65, 0xac, 0xeb, 0x23, 0x64, 0xad, 0xea,
+ 0x24, 0x63, 0xaa, 0xed, 0x25, 0x62, 0xab, 0xec, 0x26, 0x61,
+ 0xa8, 0xef, 0x27, 0x60, 0xa9, 0xee, 0x28, 0x6f, 0xa6, 0xe1,
+ 0x29, 0x6e, 0xa7, 0xe0, 0x2a, 0x6d, 0xa4, 0xe3, 0x2b, 0x6c,
+ 0xa5, 0xe2, 0x2c, 0x6b, 0xa2, 0xe5, 0x2d, 0x6a, 0xa3, 0xe4,
+ 0x2e, 0x69, 0xa0, 0xe7, 0x2f, 0x68, 0xa1, 0xe6, 0x30, 0x77,
+ 0xbe, 0xf9, 0x31, 0x76, 0xbf, 0xf8, 0x32, 0x75, 0xbc, 0xfb,
+ 0x33, 0x74, 0xbd, 0xfa, 0x34, 0x73, 0xba, 0xfd, 0x35, 0x72,
+ 0xbb, 0xfc, 0x36, 0x71, 0xb8, 0xff, 0x37, 0x70, 0xb9, 0xfe,
+ 0x38, 0x7f, 0xb6, 0xf1, 0x39, 0x7e, 0xb7, 0xf0, 0x3a, 0x7d,
+ 0xb4, 0xf3, 0x3b, 0x7c, 0xb5, 0xf2, 0x3c, 0x7b, 0xb2, 0xf5,
+ 0x3d, 0x7a, 0xb3, 0xf4, 0x3e, 0x79, 0xb0, 0xf7, 0x3f, 0x78,
+ 0xb1, 0xf6, 0x00, 0x48, 0x90, 0xd8, 0x3d, 0x75, 0xad, 0xe5,
+ 0x7a, 0x32, 0xea, 0xa2, 0x47, 0x0f, 0xd7, 0x9f, 0xf4, 0xbc,
+ 0x64, 0x2c, 0xc9, 0x81, 0x59, 0x11, 0x8e, 0xc6, 0x1e, 0x56,
+ 0xb3, 0xfb, 0x23, 0x6b, 0xf5, 0xbd, 0x65, 0x2d, 0xc8, 0x80,
+ 0x58, 0x10, 0x8f, 0xc7, 0x1f, 0x57, 0xb2, 0xfa, 0x22, 0x6a,
+ 0x01, 0x49, 0x91, 0xd9, 0x3c, 0x74, 0xac, 0xe4, 0x7b, 0x33,
+ 0xeb, 0xa3, 0x46, 0x0e, 0xd6, 0x9e, 0xf7, 0xbf, 0x67, 0x2f,
+ 0xca, 0x82, 0x5a, 0x12, 0x8d, 0xc5, 0x1d, 0x55, 0xb0, 0xf8,
+ 0x20, 0x68, 0x03, 0x4b, 0x93, 0xdb, 0x3e, 0x76, 0xae, 0xe6,
+ 0x79, 0x31, 0xe9, 0xa1, 0x44, 0x0c, 0xd4, 0x9c, 0x02, 0x4a,
+ 0x92, 0xda, 0x3f, 0x77, 0xaf, 0xe7, 0x78, 0x30, 0xe8, 0xa0,
+ 0x45, 0x0d, 0xd5, 0x9d, 0xf6, 0xbe, 0x66, 0x2e, 0xcb, 0x83,
+ 0x5b, 0x13, 0x8c, 0xc4, 0x1c, 0x54, 0xb1, 0xf9, 0x21, 0x69,
+ 0xf3, 0xbb, 0x63, 0x2b, 0xce, 0x86, 0x5e, 0x16, 0x89, 0xc1,
+ 0x19, 0x51, 0xb4, 0xfc, 0x24, 0x6c, 0x07, 0x4f, 0x97, 0xdf,
+ 0x3a, 0x72, 0xaa, 0xe2, 0x7d, 0x35, 0xed, 0xa5, 0x40, 0x08,
+ 0xd0, 0x98, 0x06, 0x4e, 0x96, 0xde, 0x3b, 0x73, 0xab, 0xe3,
+ 0x7c, 0x34, 0xec, 0xa4, 0x41, 0x09, 0xd1, 0x99, 0xf2, 0xba,
+ 0x62, 0x2a, 0xcf, 0x87, 0x5f, 0x17, 0x88, 0xc0, 0x18, 0x50,
+ 0xb5, 0xfd, 0x25, 0x6d, 0x04, 0x4c, 0x94, 0xdc, 0x39, 0x71,
+ 0xa9, 0xe1, 0x7e, 0x36, 0xee, 0xa6, 0x43, 0x0b, 0xd3, 0x9b,
+ 0xf0, 0xb8, 0x60, 0x28, 0xcd, 0x85, 0x5d, 0x15, 0x8a, 0xc2,
+ 0x1a, 0x52, 0xb7, 0xff, 0x27, 0x6f, 0xf1, 0xb9, 0x61, 0x29,
+ 0xcc, 0x84, 0x5c, 0x14, 0x8b, 0xc3, 0x1b, 0x53, 0xb6, 0xfe,
+ 0x26, 0x6e, 0x05, 0x4d, 0x95, 0xdd, 0x38, 0x70, 0xa8, 0xe0,
+ 0x7f, 0x37, 0xef, 0xa7, 0x42, 0x0a, 0xd2, 0x9a, 0x00, 0x49,
+ 0x92, 0xdb, 0x39, 0x70, 0xab, 0xe2, 0x72, 0x3b, 0xe0, 0xa9,
+ 0x4b, 0x02, 0xd9, 0x90, 0xe4, 0xad, 0x76, 0x3f, 0xdd, 0x94,
+ 0x4f, 0x06, 0x96, 0xdf, 0x04, 0x4d, 0xaf, 0xe6, 0x3d, 0x74,
+ 0xd5, 0x9c, 0x47, 0x0e, 0xec, 0xa5, 0x7e, 0x37, 0xa7, 0xee,
+ 0x35, 0x7c, 0x9e, 0xd7, 0x0c, 0x45, 0x31, 0x78, 0xa3, 0xea,
+ 0x08, 0x41, 0x9a, 0xd3, 0x43, 0x0a, 0xd1, 0x98, 0x7a, 0x33,
+ 0xe8, 0xa1, 0xb7, 0xfe, 0x25, 0x6c, 0x8e, 0xc7, 0x1c, 0x55,
+ 0xc5, 0x8c, 0x57, 0x1e, 0xfc, 0xb5, 0x6e, 0x27, 0x53, 0x1a,
+ 0xc1, 0x88, 0x6a, 0x23, 0xf8, 0xb1, 0x21, 0x68, 0xb3, 0xfa,
+ 0x18, 0x51, 0x8a, 0xc3, 0x62, 0x2b, 0xf0, 0xb9, 0x5b, 0x12,
+ 0xc9, 0x80, 0x10, 0x59, 0x82, 0xcb, 0x29, 0x60, 0xbb, 0xf2,
+ 0x86, 0xcf, 0x14, 0x5d, 0xbf, 0xf6, 0x2d, 0x64, 0xf4, 0xbd,
+ 0x66, 0x2f, 0xcd, 0x84, 0x5f, 0x16, 0x73, 0x3a, 0xe1, 0xa8,
+ 0x4a, 0x03, 0xd8, 0x91, 0x01, 0x48, 0x93, 0xda, 0x38, 0x71,
+ 0xaa, 0xe3, 0x97, 0xde, 0x05, 0x4c, 0xae, 0xe7, 0x3c, 0x75,
+ 0xe5, 0xac, 0x77, 0x3e, 0xdc, 0x95, 0x4e, 0x07, 0xa6, 0xef,
+ 0x34, 0x7d, 0x9f, 0xd6, 0x0d, 0x44, 0xd4, 0x9d, 0x46, 0x0f,
+ 0xed, 0xa4, 0x7f, 0x36, 0x42, 0x0b, 0xd0, 0x99, 0x7b, 0x32,
+ 0xe9, 0xa0, 0x30, 0x79, 0xa2, 0xeb, 0x09, 0x40, 0x9b, 0xd2,
+ 0xc4, 0x8d, 0x56, 0x1f, 0xfd, 0xb4, 0x6f, 0x26, 0xb6, 0xff,
+ 0x24, 0x6d, 0x8f, 0xc6, 0x1d, 0x54, 0x20, 0x69, 0xb2, 0xfb,
+ 0x19, 0x50, 0x8b, 0xc2, 0x52, 0x1b, 0xc0, 0x89, 0x6b, 0x22,
+ 0xf9, 0xb0, 0x11, 0x58, 0x83, 0xca, 0x28, 0x61, 0xba, 0xf3,
+ 0x63, 0x2a, 0xf1, 0xb8, 0x5a, 0x13, 0xc8, 0x81, 0xf5, 0xbc,
+ 0x67, 0x2e, 0xcc, 0x85, 0x5e, 0x17, 0x87, 0xce, 0x15, 0x5c,
+ 0xbe, 0xf7, 0x2c, 0x65, 0x00, 0x4a, 0x94, 0xde, 0x35, 0x7f,
+ 0xa1, 0xeb, 0x6a, 0x20, 0xfe, 0xb4, 0x5f, 0x15, 0xcb, 0x81,
+ 0xd4, 0x9e, 0x40, 0x0a, 0xe1, 0xab, 0x75, 0x3f, 0xbe, 0xf4,
+ 0x2a, 0x60, 0x8b, 0xc1, 0x1f, 0x55, 0xb5, 0xff, 0x21, 0x6b,
+ 0x80, 0xca, 0x14, 0x5e, 0xdf, 0x95, 0x4b, 0x01, 0xea, 0xa0,
+ 0x7e, 0x34, 0x61, 0x2b, 0xf5, 0xbf, 0x54, 0x1e, 0xc0, 0x8a,
+ 0x0b, 0x41, 0x9f, 0xd5, 0x3e, 0x74, 0xaa, 0xe0, 0x77, 0x3d,
+ 0xe3, 0xa9, 0x42, 0x08, 0xd6, 0x9c, 0x1d, 0x57, 0x89, 0xc3,
+ 0x28, 0x62, 0xbc, 0xf6, 0xa3, 0xe9, 0x37, 0x7d, 0x96, 0xdc,
+ 0x02, 0x48, 0xc9, 0x83, 0x5d, 0x17, 0xfc, 0xb6, 0x68, 0x22,
+ 0xc2, 0x88, 0x56, 0x1c, 0xf7, 0xbd, 0x63, 0x29, 0xa8, 0xe2,
+ 0x3c, 0x76, 0x9d, 0xd7, 0x09, 0x43, 0x16, 0x5c, 0x82, 0xc8,
+ 0x23, 0x69, 0xb7, 0xfd, 0x7c, 0x36, 0xe8, 0xa2, 0x49, 0x03,
+ 0xdd, 0x97, 0xee, 0xa4, 0x7a, 0x30, 0xdb, 0x91, 0x4f, 0x05,
+ 0x84, 0xce, 0x10, 0x5a, 0xb1, 0xfb, 0x25, 0x6f, 0x3a, 0x70,
+ 0xae, 0xe4, 0x0f, 0x45, 0x9b, 0xd1, 0x50, 0x1a, 0xc4, 0x8e,
+ 0x65, 0x2f, 0xf1, 0xbb, 0x5b, 0x11, 0xcf, 0x85, 0x6e, 0x24,
+ 0xfa, 0xb0, 0x31, 0x7b, 0xa5, 0xef, 0x04, 0x4e, 0x90, 0xda,
+ 0x8f, 0xc5, 0x1b, 0x51, 0xba, 0xf0, 0x2e, 0x64, 0xe5, 0xaf,
+ 0x71, 0x3b, 0xd0, 0x9a, 0x44, 0x0e, 0x99, 0xd3, 0x0d, 0x47,
+ 0xac, 0xe6, 0x38, 0x72, 0xf3, 0xb9, 0x67, 0x2d, 0xc6, 0x8c,
+ 0x52, 0x18, 0x4d, 0x07, 0xd9, 0x93, 0x78, 0x32, 0xec, 0xa6,
+ 0x27, 0x6d, 0xb3, 0xf9, 0x12, 0x58, 0x86, 0xcc, 0x2c, 0x66,
+ 0xb8, 0xf2, 0x19, 0x53, 0x8d, 0xc7, 0x46, 0x0c, 0xd2, 0x98,
+ 0x73, 0x39, 0xe7, 0xad, 0xf8, 0xb2, 0x6c, 0x26, 0xcd, 0x87,
+ 0x59, 0x13, 0x92, 0xd8, 0x06, 0x4c, 0xa7, 0xed, 0x33, 0x79,
+ 0x00, 0x4b, 0x96, 0xdd, 0x31, 0x7a, 0xa7, 0xec, 0x62, 0x29,
+ 0xf4, 0xbf, 0x53, 0x18, 0xc5, 0x8e, 0xc4, 0x8f, 0x52, 0x19,
+ 0xf5, 0xbe, 0x63, 0x28, 0xa6, 0xed, 0x30, 0x7b, 0x97, 0xdc,
+ 0x01, 0x4a, 0x95, 0xde, 0x03, 0x48, 0xa4, 0xef, 0x32, 0x79,
+ 0xf7, 0xbc, 0x61, 0x2a, 0xc6, 0x8d, 0x50, 0x1b, 0x51, 0x1a,
+ 0xc7, 0x8c, 0x60, 0x2b, 0xf6, 0xbd, 0x33, 0x78, 0xa5, 0xee,
+ 0x02, 0x49, 0x94, 0xdf, 0x37, 0x7c, 0xa1, 0xea, 0x06, 0x4d,
+ 0x90, 0xdb, 0x55, 0x1e, 0xc3, 0x88, 0x64, 0x2f, 0xf2, 0xb9,
+ 0xf3, 0xb8, 0x65, 0x2e, 0xc2, 0x89, 0x54, 0x1f, 0x91, 0xda,
+ 0x07, 0x4c, 0xa0, 0xeb, 0x36, 0x7d, 0xa2, 0xe9, 0x34, 0x7f,
+ 0x93, 0xd8, 0x05, 0x4e, 0xc0, 0x8b, 0x56, 0x1d, 0xf1, 0xba,
+ 0x67, 0x2c, 0x66, 0x2d, 0xf0, 0xbb, 0x57, 0x1c, 0xc1, 0x8a,
+ 0x04, 0x4f, 0x92, 0xd9, 0x35, 0x7e, 0xa3, 0xe8, 0x6e, 0x25,
+ 0xf8, 0xb3, 0x5f, 0x14, 0xc9, 0x82, 0x0c, 0x47, 0x9a, 0xd1,
+ 0x3d, 0x76, 0xab, 0xe0, 0xaa, 0xe1, 0x3c, 0x77, 0x9b, 0xd0,
+ 0x0d, 0x46, 0xc8, 0x83, 0x5e, 0x15, 0xf9, 0xb2, 0x6f, 0x24,
+ 0xfb, 0xb0, 0x6d, 0x26, 0xca, 0x81, 0x5c, 0x17, 0x99, 0xd2,
+ 0x0f, 0x44, 0xa8, 0xe3, 0x3e, 0x75, 0x3f, 0x74, 0xa9, 0xe2,
+ 0x0e, 0x45, 0x98, 0xd3, 0x5d, 0x16, 0xcb, 0x80, 0x6c, 0x27,
+ 0xfa, 0xb1, 0x59, 0x12, 0xcf, 0x84, 0x68, 0x23, 0xfe, 0xb5,
+ 0x3b, 0x70, 0xad, 0xe6, 0x0a, 0x41, 0x9c, 0xd7, 0x9d, 0xd6,
+ 0x0b, 0x40, 0xac, 0xe7, 0x3a, 0x71, 0xff, 0xb4, 0x69, 0x22,
+ 0xce, 0x85, 0x58, 0x13, 0xcc, 0x87, 0x5a, 0x11, 0xfd, 0xb6,
+ 0x6b, 0x20, 0xae, 0xe5, 0x38, 0x73, 0x9f, 0xd4, 0x09, 0x42,
+ 0x08, 0x43, 0x9e, 0xd5, 0x39, 0x72, 0xaf, 0xe4, 0x6a, 0x21,
+ 0xfc, 0xb7, 0x5b, 0x10, 0xcd, 0x86, 0x00, 0x4c, 0x98, 0xd4,
+ 0x2d, 0x61, 0xb5, 0xf9, 0x5a, 0x16, 0xc2, 0x8e, 0x77, 0x3b,
+ 0xef, 0xa3, 0xb4, 0xf8, 0x2c, 0x60, 0x99, 0xd5, 0x01, 0x4d,
+ 0xee, 0xa2, 0x76, 0x3a, 0xc3, 0x8f, 0x5b, 0x17, 0x75, 0x39,
+ 0xed, 0xa1, 0x58, 0x14, 0xc0, 0x8c, 0x2f, 0x63, 0xb7, 0xfb,
+ 0x02, 0x4e, 0x9a, 0xd6, 0xc1, 0x8d, 0x59, 0x15, 0xec, 0xa0,
+ 0x74, 0x38, 0x9b, 0xd7, 0x03, 0x4f, 0xb6, 0xfa, 0x2e, 0x62,
+ 0xea, 0xa6, 0x72, 0x3e, 0xc7, 0x8b, 0x5f, 0x13, 0xb0, 0xfc,
+ 0x28, 0x64, 0x9d, 0xd1, 0x05, 0x49, 0x5e, 0x12, 0xc6, 0x8a,
+ 0x73, 0x3f, 0xeb, 0xa7, 0x04, 0x48, 0x9c, 0xd0, 0x29, 0x65,
+ 0xb1, 0xfd, 0x9f, 0xd3, 0x07, 0x4b, 0xb2, 0xfe, 0x2a, 0x66,
+ 0xc5, 0x89, 0x5d, 0x11, 0xe8, 0xa4, 0x70, 0x3c, 0x2b, 0x67,
+ 0xb3, 0xff, 0x06, 0x4a, 0x9e, 0xd2, 0x71, 0x3d, 0xe9, 0xa5,
+ 0x5c, 0x10, 0xc4, 0x88, 0xc9, 0x85, 0x51, 0x1d, 0xe4, 0xa8,
+ 0x7c, 0x30, 0x93, 0xdf, 0x0b, 0x47, 0xbe, 0xf2, 0x26, 0x6a,
+ 0x7d, 0x31, 0xe5, 0xa9, 0x50, 0x1c, 0xc8, 0x84, 0x27, 0x6b,
+ 0xbf, 0xf3, 0x0a, 0x46, 0x92, 0xde, 0xbc, 0xf0, 0x24, 0x68,
+ 0x91, 0xdd, 0x09, 0x45, 0xe6, 0xaa, 0x7e, 0x32, 0xcb, 0x87,
+ 0x53, 0x1f, 0x08, 0x44, 0x90, 0xdc, 0x25, 0x69, 0xbd, 0xf1,
+ 0x52, 0x1e, 0xca, 0x86, 0x7f, 0x33, 0xe7, 0xab, 0x23, 0x6f,
+ 0xbb, 0xf7, 0x0e, 0x42, 0x96, 0xda, 0x79, 0x35, 0xe1, 0xad,
+ 0x54, 0x18, 0xcc, 0x80, 0x97, 0xdb, 0x0f, 0x43, 0xba, 0xf6,
+ 0x22, 0x6e, 0xcd, 0x81, 0x55, 0x19, 0xe0, 0xac, 0x78, 0x34,
+ 0x56, 0x1a, 0xce, 0x82, 0x7b, 0x37, 0xe3, 0xaf, 0x0c, 0x40,
+ 0x94, 0xd8, 0x21, 0x6d, 0xb9, 0xf5, 0xe2, 0xae, 0x7a, 0x36,
+ 0xcf, 0x83, 0x57, 0x1b, 0xb8, 0xf4, 0x20, 0x6c, 0x95, 0xd9,
+ 0x0d, 0x41, 0x00, 0x4d, 0x9a, 0xd7, 0x29, 0x64, 0xb3, 0xfe,
+ 0x52, 0x1f, 0xc8, 0x85, 0x7b, 0x36, 0xe1, 0xac, 0xa4, 0xe9,
+ 0x3e, 0x73, 0x8d, 0xc0, 0x17, 0x5a, 0xf6, 0xbb, 0x6c, 0x21,
+ 0xdf, 0x92, 0x45, 0x08, 0x55, 0x18, 0xcf, 0x82, 0x7c, 0x31,
+ 0xe6, 0xab, 0x07, 0x4a, 0x9d, 0xd0, 0x2e, 0x63, 0xb4, 0xf9,
+ 0xf1, 0xbc, 0x6b, 0x26, 0xd8, 0x95, 0x42, 0x0f, 0xa3, 0xee,
+ 0x39, 0x74, 0x8a, 0xc7, 0x10, 0x5d, 0xaa, 0xe7, 0x30, 0x7d,
+ 0x83, 0xce, 0x19, 0x54, 0xf8, 0xb5, 0x62, 0x2f, 0xd1, 0x9c,
+ 0x4b, 0x06, 0x0e, 0x43, 0x94, 0xd9, 0x27, 0x6a, 0xbd, 0xf0,
+ 0x5c, 0x11, 0xc6, 0x8b, 0x75, 0x38, 0xef, 0xa2, 0xff, 0xb2,
+ 0x65, 0x28, 0xd6, 0x9b, 0x4c, 0x01, 0xad, 0xe0, 0x37, 0x7a,
+ 0x84, 0xc9, 0x1e, 0x53, 0x5b, 0x16, 0xc1, 0x8c, 0x72, 0x3f,
+ 0xe8, 0xa5, 0x09, 0x44, 0x93, 0xde, 0x20, 0x6d, 0xba, 0xf7,
+ 0x49, 0x04, 0xd3, 0x9e, 0x60, 0x2d, 0xfa, 0xb7, 0x1b, 0x56,
+ 0x81, 0xcc, 0x32, 0x7f, 0xa8, 0xe5, 0xed, 0xa0, 0x77, 0x3a,
+ 0xc4, 0x89, 0x5e, 0x13, 0xbf, 0xf2, 0x25, 0x68, 0x96, 0xdb,
+ 0x0c, 0x41, 0x1c, 0x51, 0x86, 0xcb, 0x35, 0x78, 0xaf, 0xe2,
+ 0x4e, 0x03, 0xd4, 0x99, 0x67, 0x2a, 0xfd, 0xb0, 0xb8, 0xf5,
+ 0x22, 0x6f, 0x91, 0xdc, 0x0b, 0x46, 0xea, 0xa7, 0x70, 0x3d,
+ 0xc3, 0x8e, 0x59, 0x14, 0xe3, 0xae, 0x79, 0x34, 0xca, 0x87,
+ 0x50, 0x1d, 0xb1, 0xfc, 0x2b, 0x66, 0x98, 0xd5, 0x02, 0x4f,
+ 0x47, 0x0a, 0xdd, 0x90, 0x6e, 0x23, 0xf4, 0xb9, 0x15, 0x58,
+ 0x8f, 0xc2, 0x3c, 0x71, 0xa6, 0xeb, 0xb6, 0xfb, 0x2c, 0x61,
+ 0x9f, 0xd2, 0x05, 0x48, 0xe4, 0xa9, 0x7e, 0x33, 0xcd, 0x80,
+ 0x57, 0x1a, 0x12, 0x5f, 0x88, 0xc5, 0x3b, 0x76, 0xa1, 0xec,
+ 0x40, 0x0d, 0xda, 0x97, 0x69, 0x24, 0xf3, 0xbe, 0x00, 0x4e,
+ 0x9c, 0xd2, 0x25, 0x6b, 0xb9, 0xf7, 0x4a, 0x04, 0xd6, 0x98,
+ 0x6f, 0x21, 0xf3, 0xbd, 0x94, 0xda, 0x08, 0x46, 0xb1, 0xff,
+ 0x2d, 0x63, 0xde, 0x90, 0x42, 0x0c, 0xfb, 0xb5, 0x67, 0x29,
+ 0x35, 0x7b, 0xa9, 0xe7, 0x10, 0x5e, 0x8c, 0xc2, 0x7f, 0x31,
+ 0xe3, 0xad, 0x5a, 0x14, 0xc6, 0x88, 0xa1, 0xef, 0x3d, 0x73,
+ 0x84, 0xca, 0x18, 0x56, 0xeb, 0xa5, 0x77, 0x39, 0xce, 0x80,
+ 0x52, 0x1c, 0x6a, 0x24, 0xf6, 0xb8, 0x4f, 0x01, 0xd3, 0x9d,
+ 0x20, 0x6e, 0xbc, 0xf2, 0x05, 0x4b, 0x99, 0xd7, 0xfe, 0xb0,
+ 0x62, 0x2c, 0xdb, 0x95, 0x47, 0x09, 0xb4, 0xfa, 0x28, 0x66,
+ 0x91, 0xdf, 0x0d, 0x43, 0x5f, 0x11, 0xc3, 0x8d, 0x7a, 0x34,
+ 0xe6, 0xa8, 0x15, 0x5b, 0x89, 0xc7, 0x30, 0x7e, 0xac, 0xe2,
+ 0xcb, 0x85, 0x57, 0x19, 0xee, 0xa0, 0x72, 0x3c, 0x81, 0xcf,
+ 0x1d, 0x53, 0xa4, 0xea, 0x38, 0x76, 0xd4, 0x9a, 0x48, 0x06,
+ 0xf1, 0xbf, 0x6d, 0x23, 0x9e, 0xd0, 0x02, 0x4c, 0xbb, 0xf5,
+ 0x27, 0x69, 0x40, 0x0e, 0xdc, 0x92, 0x65, 0x2b, 0xf9, 0xb7,
+ 0x0a, 0x44, 0x96, 0xd8, 0x2f, 0x61, 0xb3, 0xfd, 0xe1, 0xaf,
+ 0x7d, 0x33, 0xc4, 0x8a, 0x58, 0x16, 0xab, 0xe5, 0x37, 0x79,
+ 0x8e, 0xc0, 0x12, 0x5c, 0x75, 0x3b, 0xe9, 0xa7, 0x50, 0x1e,
+ 0xcc, 0x82, 0x3f, 0x71, 0xa3, 0xed, 0x1a, 0x54, 0x86, 0xc8,
+ 0xbe, 0xf0, 0x22, 0x6c, 0x9b, 0xd5, 0x07, 0x49, 0xf4, 0xba,
+ 0x68, 0x26, 0xd1, 0x9f, 0x4d, 0x03, 0x2a, 0x64, 0xb6, 0xf8,
+ 0x0f, 0x41, 0x93, 0xdd, 0x60, 0x2e, 0xfc, 0xb2, 0x45, 0x0b,
+ 0xd9, 0x97, 0x8b, 0xc5, 0x17, 0x59, 0xae, 0xe0, 0x32, 0x7c,
+ 0xc1, 0x8f, 0x5d, 0x13, 0xe4, 0xaa, 0x78, 0x36, 0x1f, 0x51,
+ 0x83, 0xcd, 0x3a, 0x74, 0xa6, 0xe8, 0x55, 0x1b, 0xc9, 0x87,
+ 0x70, 0x3e, 0xec, 0xa2, 0x00, 0x4f, 0x9e, 0xd1, 0x21, 0x6e,
+ 0xbf, 0xf0, 0x42, 0x0d, 0xdc, 0x93, 0x63, 0x2c, 0xfd, 0xb2,
+ 0x84, 0xcb, 0x1a, 0x55, 0xa5, 0xea, 0x3b, 0x74, 0xc6, 0x89,
+ 0x58, 0x17, 0xe7, 0xa8, 0x79, 0x36, 0x15, 0x5a, 0x8b, 0xc4,
+ 0x34, 0x7b, 0xaa, 0xe5, 0x57, 0x18, 0xc9, 0x86, 0x76, 0x39,
+ 0xe8, 0xa7, 0x91, 0xde, 0x0f, 0x40, 0xb0, 0xff, 0x2e, 0x61,
+ 0xd3, 0x9c, 0x4d, 0x02, 0xf2, 0xbd, 0x6c, 0x23, 0x2a, 0x65,
+ 0xb4, 0xfb, 0x0b, 0x44, 0x95, 0xda, 0x68, 0x27, 0xf6, 0xb9,
+ 0x49, 0x06, 0xd7, 0x98, 0xae, 0xe1, 0x30, 0x7f, 0x8f, 0xc0,
+ 0x11, 0x5e, 0xec, 0xa3, 0x72, 0x3d, 0xcd, 0x82, 0x53, 0x1c,
+ 0x3f, 0x70, 0xa1, 0xee, 0x1e, 0x51, 0x80, 0xcf, 0x7d, 0x32,
+ 0xe3, 0xac, 0x5c, 0x13, 0xc2, 0x8d, 0xbb, 0xf4, 0x25, 0x6a,
+ 0x9a, 0xd5, 0x04, 0x4b, 0xf9, 0xb6, 0x67, 0x28, 0xd8, 0x97,
+ 0x46, 0x09, 0x54, 0x1b, 0xca, 0x85, 0x75, 0x3a, 0xeb, 0xa4,
+ 0x16, 0x59, 0x88, 0xc7, 0x37, 0x78, 0xa9, 0xe6, 0xd0, 0x9f,
+ 0x4e, 0x01, 0xf1, 0xbe, 0x6f, 0x20, 0x92, 0xdd, 0x0c, 0x43,
+ 0xb3, 0xfc, 0x2d, 0x62, 0x41, 0x0e, 0xdf, 0x90, 0x60, 0x2f,
+ 0xfe, 0xb1, 0x03, 0x4c, 0x9d, 0xd2, 0x22, 0x6d, 0xbc, 0xf3,
+ 0xc5, 0x8a, 0x5b, 0x14, 0xe4, 0xab, 0x7a, 0x35, 0x87, 0xc8,
+ 0x19, 0x56, 0xa6, 0xe9, 0x38, 0x77, 0x7e, 0x31, 0xe0, 0xaf,
+ 0x5f, 0x10, 0xc1, 0x8e, 0x3c, 0x73, 0xa2, 0xed, 0x1d, 0x52,
+ 0x83, 0xcc, 0xfa, 0xb5, 0x64, 0x2b, 0xdb, 0x94, 0x45, 0x0a,
+ 0xb8, 0xf7, 0x26, 0x69, 0x99, 0xd6, 0x07, 0x48, 0x6b, 0x24,
+ 0xf5, 0xba, 0x4a, 0x05, 0xd4, 0x9b, 0x29, 0x66, 0xb7, 0xf8,
+ 0x08, 0x47, 0x96, 0xd9, 0xef, 0xa0, 0x71, 0x3e, 0xce, 0x81,
+ 0x50, 0x1f, 0xad, 0xe2, 0x33, 0x7c, 0x8c, 0xc3, 0x12, 0x5d,
+ 0x00, 0x50, 0xa0, 0xf0, 0x5d, 0x0d, 0xfd, 0xad, 0xba, 0xea,
+ 0x1a, 0x4a, 0xe7, 0xb7, 0x47, 0x17, 0x69, 0x39, 0xc9, 0x99,
+ 0x34, 0x64, 0x94, 0xc4, 0xd3, 0x83, 0x73, 0x23, 0x8e, 0xde,
+ 0x2e, 0x7e, 0xd2, 0x82, 0x72, 0x22, 0x8f, 0xdf, 0x2f, 0x7f,
+ 0x68, 0x38, 0xc8, 0x98, 0x35, 0x65, 0x95, 0xc5, 0xbb, 0xeb,
+ 0x1b, 0x4b, 0xe6, 0xb6, 0x46, 0x16, 0x01, 0x51, 0xa1, 0xf1,
+ 0x5c, 0x0c, 0xfc, 0xac, 0xb9, 0xe9, 0x19, 0x49, 0xe4, 0xb4,
+ 0x44, 0x14, 0x03, 0x53, 0xa3, 0xf3, 0x5e, 0x0e, 0xfe, 0xae,
+ 0xd0, 0x80, 0x70, 0x20, 0x8d, 0xdd, 0x2d, 0x7d, 0x6a, 0x3a,
+ 0xca, 0x9a, 0x37, 0x67, 0x97, 0xc7, 0x6b, 0x3b, 0xcb, 0x9b,
+ 0x36, 0x66, 0x96, 0xc6, 0xd1, 0x81, 0x71, 0x21, 0x8c, 0xdc,
+ 0x2c, 0x7c, 0x02, 0x52, 0xa2, 0xf2, 0x5f, 0x0f, 0xff, 0xaf,
+ 0xb8, 0xe8, 0x18, 0x48, 0xe5, 0xb5, 0x45, 0x15, 0x6f, 0x3f,
+ 0xcf, 0x9f, 0x32, 0x62, 0x92, 0xc2, 0xd5, 0x85, 0x75, 0x25,
+ 0x88, 0xd8, 0x28, 0x78, 0x06, 0x56, 0xa6, 0xf6, 0x5b, 0x0b,
+ 0xfb, 0xab, 0xbc, 0xec, 0x1c, 0x4c, 0xe1, 0xb1, 0x41, 0x11,
+ 0xbd, 0xed, 0x1d, 0x4d, 0xe0, 0xb0, 0x40, 0x10, 0x07, 0x57,
+ 0xa7, 0xf7, 0x5a, 0x0a, 0xfa, 0xaa, 0xd4, 0x84, 0x74, 0x24,
+ 0x89, 0xd9, 0x29, 0x79, 0x6e, 0x3e, 0xce, 0x9e, 0x33, 0x63,
+ 0x93, 0xc3, 0xd6, 0x86, 0x76, 0x26, 0x8b, 0xdb, 0x2b, 0x7b,
+ 0x6c, 0x3c, 0xcc, 0x9c, 0x31, 0x61, 0x91, 0xc1, 0xbf, 0xef,
+ 0x1f, 0x4f, 0xe2, 0xb2, 0x42, 0x12, 0x05, 0x55, 0xa5, 0xf5,
+ 0x58, 0x08, 0xf8, 0xa8, 0x04, 0x54, 0xa4, 0xf4, 0x59, 0x09,
+ 0xf9, 0xa9, 0xbe, 0xee, 0x1e, 0x4e, 0xe3, 0xb3, 0x43, 0x13,
+ 0x6d, 0x3d, 0xcd, 0x9d, 0x30, 0x60, 0x90, 0xc0, 0xd7, 0x87,
+ 0x77, 0x27, 0x8a, 0xda, 0x2a, 0x7a, 0x00, 0x51, 0xa2, 0xf3,
+ 0x59, 0x08, 0xfb, 0xaa, 0xb2, 0xe3, 0x10, 0x41, 0xeb, 0xba,
+ 0x49, 0x18, 0x79, 0x28, 0xdb, 0x8a, 0x20, 0x71, 0x82, 0xd3,
+ 0xcb, 0x9a, 0x69, 0x38, 0x92, 0xc3, 0x30, 0x61, 0xf2, 0xa3,
+ 0x50, 0x01, 0xab, 0xfa, 0x09, 0x58, 0x40, 0x11, 0xe2, 0xb3,
+ 0x19, 0x48, 0xbb, 0xea, 0x8b, 0xda, 0x29, 0x78, 0xd2, 0x83,
+ 0x70, 0x21, 0x39, 0x68, 0x9b, 0xca, 0x60, 0x31, 0xc2, 0x93,
+ 0xf9, 0xa8, 0x5b, 0x0a, 0xa0, 0xf1, 0x02, 0x53, 0x4b, 0x1a,
+ 0xe9, 0xb8, 0x12, 0x43, 0xb0, 0xe1, 0x80, 0xd1, 0x22, 0x73,
+ 0xd9, 0x88, 0x7b, 0x2a, 0x32, 0x63, 0x90, 0xc1, 0x6b, 0x3a,
+ 0xc9, 0x98, 0x0b, 0x5a, 0xa9, 0xf8, 0x52, 0x03, 0xf0, 0xa1,
+ 0xb9, 0xe8, 0x1b, 0x4a, 0xe0, 0xb1, 0x42, 0x13, 0x72, 0x23,
+ 0xd0, 0x81, 0x2b, 0x7a, 0x89, 0xd8, 0xc0, 0x91, 0x62, 0x33,
+ 0x99, 0xc8, 0x3b, 0x6a, 0xef, 0xbe, 0x4d, 0x1c, 0xb6, 0xe7,
+ 0x14, 0x45, 0x5d, 0x0c, 0xff, 0xae, 0x04, 0x55, 0xa6, 0xf7,
+ 0x96, 0xc7, 0x34, 0x65, 0xcf, 0x9e, 0x6d, 0x3c, 0x24, 0x75,
+ 0x86, 0xd7, 0x7d, 0x2c, 0xdf, 0x8e, 0x1d, 0x4c, 0xbf, 0xee,
+ 0x44, 0x15, 0xe6, 0xb7, 0xaf, 0xfe, 0x0d, 0x5c, 0xf6, 0xa7,
+ 0x54, 0x05, 0x64, 0x35, 0xc6, 0x97, 0x3d, 0x6c, 0x9f, 0xce,
+ 0xd6, 0x87, 0x74, 0x25, 0x8f, 0xde, 0x2d, 0x7c, 0x16, 0x47,
+ 0xb4, 0xe5, 0x4f, 0x1e, 0xed, 0xbc, 0xa4, 0xf5, 0x06, 0x57,
+ 0xfd, 0xac, 0x5f, 0x0e, 0x6f, 0x3e, 0xcd, 0x9c, 0x36, 0x67,
+ 0x94, 0xc5, 0xdd, 0x8c, 0x7f, 0x2e, 0x84, 0xd5, 0x26, 0x77,
+ 0xe4, 0xb5, 0x46, 0x17, 0xbd, 0xec, 0x1f, 0x4e, 0x56, 0x07,
+ 0xf4, 0xa5, 0x0f, 0x5e, 0xad, 0xfc, 0x9d, 0xcc, 0x3f, 0x6e,
+ 0xc4, 0x95, 0x66, 0x37, 0x2f, 0x7e, 0x8d, 0xdc, 0x76, 0x27,
+ 0xd4, 0x85, 0x00, 0x52, 0xa4, 0xf6, 0x55, 0x07, 0xf1, 0xa3,
+ 0xaa, 0xf8, 0x0e, 0x5c, 0xff, 0xad, 0x5b, 0x09, 0x49, 0x1b,
+ 0xed, 0xbf, 0x1c, 0x4e, 0xb8, 0xea, 0xe3, 0xb1, 0x47, 0x15,
+ 0xb6, 0xe4, 0x12, 0x40, 0x92, 0xc0, 0x36, 0x64, 0xc7, 0x95,
+ 0x63, 0x31, 0x38, 0x6a, 0x9c, 0xce, 0x6d, 0x3f, 0xc9, 0x9b,
+ 0xdb, 0x89, 0x7f, 0x2d, 0x8e, 0xdc, 0x2a, 0x78, 0x71, 0x23,
+ 0xd5, 0x87, 0x24, 0x76, 0x80, 0xd2, 0x39, 0x6b, 0x9d, 0xcf,
+ 0x6c, 0x3e, 0xc8, 0x9a, 0x93, 0xc1, 0x37, 0x65, 0xc6, 0x94,
+ 0x62, 0x30, 0x70, 0x22, 0xd4, 0x86, 0x25, 0x77, 0x81, 0xd3,
+ 0xda, 0x88, 0x7e, 0x2c, 0x8f, 0xdd, 0x2b, 0x79, 0xab, 0xf9,
+ 0x0f, 0x5d, 0xfe, 0xac, 0x5a, 0x08, 0x01, 0x53, 0xa5, 0xf7,
+ 0x54, 0x06, 0xf0, 0xa2, 0xe2, 0xb0, 0x46, 0x14, 0xb7, 0xe5,
+ 0x13, 0x41, 0x48, 0x1a, 0xec, 0xbe, 0x1d, 0x4f, 0xb9, 0xeb,
+ 0x72, 0x20, 0xd6, 0x84, 0x27, 0x75, 0x83, 0xd1, 0xd8, 0x8a,
+ 0x7c, 0x2e, 0x8d, 0xdf, 0x29, 0x7b, 0x3b, 0x69, 0x9f, 0xcd,
+ 0x6e, 0x3c, 0xca, 0x98, 0x91, 0xc3, 0x35, 0x67, 0xc4, 0x96,
+ 0x60, 0x32, 0xe0, 0xb2, 0x44, 0x16, 0xb5, 0xe7, 0x11, 0x43,
+ 0x4a, 0x18, 0xee, 0xbc, 0x1f, 0x4d, 0xbb, 0xe9, 0xa9, 0xfb,
+ 0x0d, 0x5f, 0xfc, 0xae, 0x58, 0x0a, 0x03, 0x51, 0xa7, 0xf5,
+ 0x56, 0x04, 0xf2, 0xa0, 0x4b, 0x19, 0xef, 0xbd, 0x1e, 0x4c,
+ 0xba, 0xe8, 0xe1, 0xb3, 0x45, 0x17, 0xb4, 0xe6, 0x10, 0x42,
+ 0x02, 0x50, 0xa6, 0xf4, 0x57, 0x05, 0xf3, 0xa1, 0xa8, 0xfa,
+ 0x0c, 0x5e, 0xfd, 0xaf, 0x59, 0x0b, 0xd9, 0x8b, 0x7d, 0x2f,
+ 0x8c, 0xde, 0x28, 0x7a, 0x73, 0x21, 0xd7, 0x85, 0x26, 0x74,
+ 0x82, 0xd0, 0x90, 0xc2, 0x34, 0x66, 0xc5, 0x97, 0x61, 0x33,
+ 0x3a, 0x68, 0x9e, 0xcc, 0x6f, 0x3d, 0xcb, 0x99, 0x00, 0x53,
+ 0xa6, 0xf5, 0x51, 0x02, 0xf7, 0xa4, 0xa2, 0xf1, 0x04, 0x57,
+ 0xf3, 0xa0, 0x55, 0x06, 0x59, 0x0a, 0xff, 0xac, 0x08, 0x5b,
+ 0xae, 0xfd, 0xfb, 0xa8, 0x5d, 0x0e, 0xaa, 0xf9, 0x0c, 0x5f,
+ 0xb2, 0xe1, 0x14, 0x47, 0xe3, 0xb0, 0x45, 0x16, 0x10, 0x43,
+ 0xb6, 0xe5, 0x41, 0x12, 0xe7, 0xb4, 0xeb, 0xb8, 0x4d, 0x1e,
+ 0xba, 0xe9, 0x1c, 0x4f, 0x49, 0x1a, 0xef, 0xbc, 0x18, 0x4b,
+ 0xbe, 0xed, 0x79, 0x2a, 0xdf, 0x8c, 0x28, 0x7b, 0x8e, 0xdd,
+ 0xdb, 0x88, 0x7d, 0x2e, 0x8a, 0xd9, 0x2c, 0x7f, 0x20, 0x73,
+ 0x86, 0xd5, 0x71, 0x22, 0xd7, 0x84, 0x82, 0xd1, 0x24, 0x77,
+ 0xd3, 0x80, 0x75, 0x26, 0xcb, 0x98, 0x6d, 0x3e, 0x9a, 0xc9,
+ 0x3c, 0x6f, 0x69, 0x3a, 0xcf, 0x9c, 0x38, 0x6b, 0x9e, 0xcd,
+ 0x92, 0xc1, 0x34, 0x67, 0xc3, 0x90, 0x65, 0x36, 0x30, 0x63,
+ 0x96, 0xc5, 0x61, 0x32, 0xc7, 0x94, 0xf2, 0xa1, 0x54, 0x07,
+ 0xa3, 0xf0, 0x05, 0x56, 0x50, 0x03, 0xf6, 0xa5, 0x01, 0x52,
+ 0xa7, 0xf4, 0xab, 0xf8, 0x0d, 0x5e, 0xfa, 0xa9, 0x5c, 0x0f,
+ 0x09, 0x5a, 0xaf, 0xfc, 0x58, 0x0b, 0xfe, 0xad, 0x40, 0x13,
+ 0xe6, 0xb5, 0x11, 0x42, 0xb7, 0xe4, 0xe2, 0xb1, 0x44, 0x17,
+ 0xb3, 0xe0, 0x15, 0x46, 0x19, 0x4a, 0xbf, 0xec, 0x48, 0x1b,
+ 0xee, 0xbd, 0xbb, 0xe8, 0x1d, 0x4e, 0xea, 0xb9, 0x4c, 0x1f,
+ 0x8b, 0xd8, 0x2d, 0x7e, 0xda, 0x89, 0x7c, 0x2f, 0x29, 0x7a,
+ 0x8f, 0xdc, 0x78, 0x2b, 0xde, 0x8d, 0xd2, 0x81, 0x74, 0x27,
+ 0x83, 0xd0, 0x25, 0x76, 0x70, 0x23, 0xd6, 0x85, 0x21, 0x72,
+ 0x87, 0xd4, 0x39, 0x6a, 0x9f, 0xcc, 0x68, 0x3b, 0xce, 0x9d,
+ 0x9b, 0xc8, 0x3d, 0x6e, 0xca, 0x99, 0x6c, 0x3f, 0x60, 0x33,
+ 0xc6, 0x95, 0x31, 0x62, 0x97, 0xc4, 0xc2, 0x91, 0x64, 0x37,
+ 0x93, 0xc0, 0x35, 0x66, 0x00, 0x54, 0xa8, 0xfc, 0x4d, 0x19,
+ 0xe5, 0xb1, 0x9a, 0xce, 0x32, 0x66, 0xd7, 0x83, 0x7f, 0x2b,
+ 0x29, 0x7d, 0x81, 0xd5, 0x64, 0x30, 0xcc, 0x98, 0xb3, 0xe7,
+ 0x1b, 0x4f, 0xfe, 0xaa, 0x56, 0x02, 0x52, 0x06, 0xfa, 0xae,
+ 0x1f, 0x4b, 0xb7, 0xe3, 0xc8, 0x9c, 0x60, 0x34, 0x85, 0xd1,
+ 0x2d, 0x79, 0x7b, 0x2f, 0xd3, 0x87, 0x36, 0x62, 0x9e, 0xca,
+ 0xe1, 0xb5, 0x49, 0x1d, 0xac, 0xf8, 0x04, 0x50, 0xa4, 0xf0,
+ 0x0c, 0x58, 0xe9, 0xbd, 0x41, 0x15, 0x3e, 0x6a, 0x96, 0xc2,
+ 0x73, 0x27, 0xdb, 0x8f, 0x8d, 0xd9, 0x25, 0x71, 0xc0, 0x94,
+ 0x68, 0x3c, 0x17, 0x43, 0xbf, 0xeb, 0x5a, 0x0e, 0xf2, 0xa6,
+ 0xf6, 0xa2, 0x5e, 0x0a, 0xbb, 0xef, 0x13, 0x47, 0x6c, 0x38,
+ 0xc4, 0x90, 0x21, 0x75, 0x89, 0xdd, 0xdf, 0x8b, 0x77, 0x23,
+ 0x92, 0xc6, 0x3a, 0x6e, 0x45, 0x11, 0xed, 0xb9, 0x08, 0x5c,
+ 0xa0, 0xf4, 0x55, 0x01, 0xfd, 0xa9, 0x18, 0x4c, 0xb0, 0xe4,
+ 0xcf, 0x9b, 0x67, 0x33, 0x82, 0xd6, 0x2a, 0x7e, 0x7c, 0x28,
+ 0xd4, 0x80, 0x31, 0x65, 0x99, 0xcd, 0xe6, 0xb2, 0x4e, 0x1a,
+ 0xab, 0xff, 0x03, 0x57, 0x07, 0x53, 0xaf, 0xfb, 0x4a, 0x1e,
+ 0xe2, 0xb6, 0x9d, 0xc9, 0x35, 0x61, 0xd0, 0x84, 0x78, 0x2c,
+ 0x2e, 0x7a, 0x86, 0xd2, 0x63, 0x37, 0xcb, 0x9f, 0xb4, 0xe0,
+ 0x1c, 0x48, 0xf9, 0xad, 0x51, 0x05, 0xf1, 0xa5, 0x59, 0x0d,
+ 0xbc, 0xe8, 0x14, 0x40, 0x6b, 0x3f, 0xc3, 0x97, 0x26, 0x72,
+ 0x8e, 0xda, 0xd8, 0x8c, 0x70, 0x24, 0x95, 0xc1, 0x3d, 0x69,
+ 0x42, 0x16, 0xea, 0xbe, 0x0f, 0x5b, 0xa7, 0xf3, 0xa3, 0xf7,
+ 0x0b, 0x5f, 0xee, 0xba, 0x46, 0x12, 0x39, 0x6d, 0x91, 0xc5,
+ 0x74, 0x20, 0xdc, 0x88, 0x8a, 0xde, 0x22, 0x76, 0xc7, 0x93,
+ 0x6f, 0x3b, 0x10, 0x44, 0xb8, 0xec, 0x5d, 0x09, 0xf5, 0xa1,
+ 0x00, 0x55, 0xaa, 0xff, 0x49, 0x1c, 0xe3, 0xb6, 0x92, 0xc7,
+ 0x38, 0x6d, 0xdb, 0x8e, 0x71, 0x24, 0x39, 0x6c, 0x93, 0xc6,
+ 0x70, 0x25, 0xda, 0x8f, 0xab, 0xfe, 0x01, 0x54, 0xe2, 0xb7,
+ 0x48, 0x1d, 0x72, 0x27, 0xd8, 0x8d, 0x3b, 0x6e, 0x91, 0xc4,
+ 0xe0, 0xb5, 0x4a, 0x1f, 0xa9, 0xfc, 0x03, 0x56, 0x4b, 0x1e,
+ 0xe1, 0xb4, 0x02, 0x57, 0xa8, 0xfd, 0xd9, 0x8c, 0x73, 0x26,
+ 0x90, 0xc5, 0x3a, 0x6f, 0xe4, 0xb1, 0x4e, 0x1b, 0xad, 0xf8,
+ 0x07, 0x52, 0x76, 0x23, 0xdc, 0x89, 0x3f, 0x6a, 0x95, 0xc0,
+ 0xdd, 0x88, 0x77, 0x22, 0x94, 0xc1, 0x3e, 0x6b, 0x4f, 0x1a,
+ 0xe5, 0xb0, 0x06, 0x53, 0xac, 0xf9, 0x96, 0xc3, 0x3c, 0x69,
+ 0xdf, 0x8a, 0x75, 0x20, 0x04, 0x51, 0xae, 0xfb, 0x4d, 0x18,
+ 0xe7, 0xb2, 0xaf, 0xfa, 0x05, 0x50, 0xe6, 0xb3, 0x4c, 0x19,
+ 0x3d, 0x68, 0x97, 0xc2, 0x74, 0x21, 0xde, 0x8b, 0xd5, 0x80,
+ 0x7f, 0x2a, 0x9c, 0xc9, 0x36, 0x63, 0x47, 0x12, 0xed, 0xb8,
+ 0x0e, 0x5b, 0xa4, 0xf1, 0xec, 0xb9, 0x46, 0x13, 0xa5, 0xf0,
+ 0x0f, 0x5a, 0x7e, 0x2b, 0xd4, 0x81, 0x37, 0x62, 0x9d, 0xc8,
+ 0xa7, 0xf2, 0x0d, 0x58, 0xee, 0xbb, 0x44, 0x11, 0x35, 0x60,
+ 0x9f, 0xca, 0x7c, 0x29, 0xd6, 0x83, 0x9e, 0xcb, 0x34, 0x61,
+ 0xd7, 0x82, 0x7d, 0x28, 0x0c, 0x59, 0xa6, 0xf3, 0x45, 0x10,
+ 0xef, 0xba, 0x31, 0x64, 0x9b, 0xce, 0x78, 0x2d, 0xd2, 0x87,
+ 0xa3, 0xf6, 0x09, 0x5c, 0xea, 0xbf, 0x40, 0x15, 0x08, 0x5d,
+ 0xa2, 0xf7, 0x41, 0x14, 0xeb, 0xbe, 0x9a, 0xcf, 0x30, 0x65,
+ 0xd3, 0x86, 0x79, 0x2c, 0x43, 0x16, 0xe9, 0xbc, 0x0a, 0x5f,
+ 0xa0, 0xf5, 0xd1, 0x84, 0x7b, 0x2e, 0x98, 0xcd, 0x32, 0x67,
+ 0x7a, 0x2f, 0xd0, 0x85, 0x33, 0x66, 0x99, 0xcc, 0xe8, 0xbd,
+ 0x42, 0x17, 0xa1, 0xf4, 0x0b, 0x5e, 0x00, 0x56, 0xac, 0xfa,
+ 0x45, 0x13, 0xe9, 0xbf, 0x8a, 0xdc, 0x26, 0x70, 0xcf, 0x99,
+ 0x63, 0x35, 0x09, 0x5f, 0xa5, 0xf3, 0x4c, 0x1a, 0xe0, 0xb6,
+ 0x83, 0xd5, 0x2f, 0x79, 0xc6, 0x90, 0x6a, 0x3c, 0x12, 0x44,
+ 0xbe, 0xe8, 0x57, 0x01, 0xfb, 0xad, 0x98, 0xce, 0x34, 0x62,
+ 0xdd, 0x8b, 0x71, 0x27, 0x1b, 0x4d, 0xb7, 0xe1, 0x5e, 0x08,
+ 0xf2, 0xa4, 0x91, 0xc7, 0x3d, 0x6b, 0xd4, 0x82, 0x78, 0x2e,
+ 0x24, 0x72, 0x88, 0xde, 0x61, 0x37, 0xcd, 0x9b, 0xae, 0xf8,
+ 0x02, 0x54, 0xeb, 0xbd, 0x47, 0x11, 0x2d, 0x7b, 0x81, 0xd7,
+ 0x68, 0x3e, 0xc4, 0x92, 0xa7, 0xf1, 0x0b, 0x5d, 0xe2, 0xb4,
+ 0x4e, 0x18, 0x36, 0x60, 0x9a, 0xcc, 0x73, 0x25, 0xdf, 0x89,
+ 0xbc, 0xea, 0x10, 0x46, 0xf9, 0xaf, 0x55, 0x03, 0x3f, 0x69,
+ 0x93, 0xc5, 0x7a, 0x2c, 0xd6, 0x80, 0xb5, 0xe3, 0x19, 0x4f,
+ 0xf0, 0xa6, 0x5c, 0x0a, 0x48, 0x1e, 0xe4, 0xb2, 0x0d, 0x5b,
+ 0xa1, 0xf7, 0xc2, 0x94, 0x6e, 0x38, 0x87, 0xd1, 0x2b, 0x7d,
+ 0x41, 0x17, 0xed, 0xbb, 0x04, 0x52, 0xa8, 0xfe, 0xcb, 0x9d,
+ 0x67, 0x31, 0x8e, 0xd8, 0x22, 0x74, 0x5a, 0x0c, 0xf6, 0xa0,
+ 0x1f, 0x49, 0xb3, 0xe5, 0xd0, 0x86, 0x7c, 0x2a, 0x95, 0xc3,
+ 0x39, 0x6f, 0x53, 0x05, 0xff, 0xa9, 0x16, 0x40, 0xba, 0xec,
+ 0xd9, 0x8f, 0x75, 0x23, 0x9c, 0xca, 0x30, 0x66, 0x6c, 0x3a,
+ 0xc0, 0x96, 0x29, 0x7f, 0x85, 0xd3, 0xe6, 0xb0, 0x4a, 0x1c,
+ 0xa3, 0xf5, 0x0f, 0x59, 0x65, 0x33, 0xc9, 0x9f, 0x20, 0x76,
+ 0x8c, 0xda, 0xef, 0xb9, 0x43, 0x15, 0xaa, 0xfc, 0x06, 0x50,
+ 0x7e, 0x28, 0xd2, 0x84, 0x3b, 0x6d, 0x97, 0xc1, 0xf4, 0xa2,
+ 0x58, 0x0e, 0xb1, 0xe7, 0x1d, 0x4b, 0x77, 0x21, 0xdb, 0x8d,
+ 0x32, 0x64, 0x9e, 0xc8, 0xfd, 0xab, 0x51, 0x07, 0xb8, 0xee,
+ 0x14, 0x42, 0x00, 0x57, 0xae, 0xf9, 0x41, 0x16, 0xef, 0xb8,
+ 0x82, 0xd5, 0x2c, 0x7b, 0xc3, 0x94, 0x6d, 0x3a, 0x19, 0x4e,
+ 0xb7, 0xe0, 0x58, 0x0f, 0xf6, 0xa1, 0x9b, 0xcc, 0x35, 0x62,
+ 0xda, 0x8d, 0x74, 0x23, 0x32, 0x65, 0x9c, 0xcb, 0x73, 0x24,
+ 0xdd, 0x8a, 0xb0, 0xe7, 0x1e, 0x49, 0xf1, 0xa6, 0x5f, 0x08,
+ 0x2b, 0x7c, 0x85, 0xd2, 0x6a, 0x3d, 0xc4, 0x93, 0xa9, 0xfe,
+ 0x07, 0x50, 0xe8, 0xbf, 0x46, 0x11, 0x64, 0x33, 0xca, 0x9d,
+ 0x25, 0x72, 0x8b, 0xdc, 0xe6, 0xb1, 0x48, 0x1f, 0xa7, 0xf0,
+ 0x09, 0x5e, 0x7d, 0x2a, 0xd3, 0x84, 0x3c, 0x6b, 0x92, 0xc5,
+ 0xff, 0xa8, 0x51, 0x06, 0xbe, 0xe9, 0x10, 0x47, 0x56, 0x01,
+ 0xf8, 0xaf, 0x17, 0x40, 0xb9, 0xee, 0xd4, 0x83, 0x7a, 0x2d,
+ 0x95, 0xc2, 0x3b, 0x6c, 0x4f, 0x18, 0xe1, 0xb6, 0x0e, 0x59,
+ 0xa0, 0xf7, 0xcd, 0x9a, 0x63, 0x34, 0x8c, 0xdb, 0x22, 0x75,
+ 0xc8, 0x9f, 0x66, 0x31, 0x89, 0xde, 0x27, 0x70, 0x4a, 0x1d,
+ 0xe4, 0xb3, 0x0b, 0x5c, 0xa5, 0xf2, 0xd1, 0x86, 0x7f, 0x28,
+ 0x90, 0xc7, 0x3e, 0x69, 0x53, 0x04, 0xfd, 0xaa, 0x12, 0x45,
+ 0xbc, 0xeb, 0xfa, 0xad, 0x54, 0x03, 0xbb, 0xec, 0x15, 0x42,
+ 0x78, 0x2f, 0xd6, 0x81, 0x39, 0x6e, 0x97, 0xc0, 0xe3, 0xb4,
+ 0x4d, 0x1a, 0xa2, 0xf5, 0x0c, 0x5b, 0x61, 0x36, 0xcf, 0x98,
+ 0x20, 0x77, 0x8e, 0xd9, 0xac, 0xfb, 0x02, 0x55, 0xed, 0xba,
+ 0x43, 0x14, 0x2e, 0x79, 0x80, 0xd7, 0x6f, 0x38, 0xc1, 0x96,
+ 0xb5, 0xe2, 0x1b, 0x4c, 0xf4, 0xa3, 0x5a, 0x0d, 0x37, 0x60,
+ 0x99, 0xce, 0x76, 0x21, 0xd8, 0x8f, 0x9e, 0xc9, 0x30, 0x67,
+ 0xdf, 0x88, 0x71, 0x26, 0x1c, 0x4b, 0xb2, 0xe5, 0x5d, 0x0a,
+ 0xf3, 0xa4, 0x87, 0xd0, 0x29, 0x7e, 0xc6, 0x91, 0x68, 0x3f,
+ 0x05, 0x52, 0xab, 0xfc, 0x44, 0x13, 0xea, 0xbd, 0x00, 0x58,
+ 0xb0, 0xe8, 0x7d, 0x25, 0xcd, 0x95, 0xfa, 0xa2, 0x4a, 0x12,
+ 0x87, 0xdf, 0x37, 0x6f, 0xe9, 0xb1, 0x59, 0x01, 0x94, 0xcc,
+ 0x24, 0x7c, 0x13, 0x4b, 0xa3, 0xfb, 0x6e, 0x36, 0xde, 0x86,
+ 0xcf, 0x97, 0x7f, 0x27, 0xb2, 0xea, 0x02, 0x5a, 0x35, 0x6d,
+ 0x85, 0xdd, 0x48, 0x10, 0xf8, 0xa0, 0x26, 0x7e, 0x96, 0xce,
+ 0x5b, 0x03, 0xeb, 0xb3, 0xdc, 0x84, 0x6c, 0x34, 0xa1, 0xf9,
+ 0x11, 0x49, 0x83, 0xdb, 0x33, 0x6b, 0xfe, 0xa6, 0x4e, 0x16,
+ 0x79, 0x21, 0xc9, 0x91, 0x04, 0x5c, 0xb4, 0xec, 0x6a, 0x32,
+ 0xda, 0x82, 0x17, 0x4f, 0xa7, 0xff, 0x90, 0xc8, 0x20, 0x78,
+ 0xed, 0xb5, 0x5d, 0x05, 0x4c, 0x14, 0xfc, 0xa4, 0x31, 0x69,
+ 0x81, 0xd9, 0xb6, 0xee, 0x06, 0x5e, 0xcb, 0x93, 0x7b, 0x23,
+ 0xa5, 0xfd, 0x15, 0x4d, 0xd8, 0x80, 0x68, 0x30, 0x5f, 0x07,
+ 0xef, 0xb7, 0x22, 0x7a, 0x92, 0xca, 0x1b, 0x43, 0xab, 0xf3,
+ 0x66, 0x3e, 0xd6, 0x8e, 0xe1, 0xb9, 0x51, 0x09, 0x9c, 0xc4,
+ 0x2c, 0x74, 0xf2, 0xaa, 0x42, 0x1a, 0x8f, 0xd7, 0x3f, 0x67,
+ 0x08, 0x50, 0xb8, 0xe0, 0x75, 0x2d, 0xc5, 0x9d, 0xd4, 0x8c,
+ 0x64, 0x3c, 0xa9, 0xf1, 0x19, 0x41, 0x2e, 0x76, 0x9e, 0xc6,
+ 0x53, 0x0b, 0xe3, 0xbb, 0x3d, 0x65, 0x8d, 0xd5, 0x40, 0x18,
+ 0xf0, 0xa8, 0xc7, 0x9f, 0x77, 0x2f, 0xba, 0xe2, 0x0a, 0x52,
+ 0x98, 0xc0, 0x28, 0x70, 0xe5, 0xbd, 0x55, 0x0d, 0x62, 0x3a,
+ 0xd2, 0x8a, 0x1f, 0x47, 0xaf, 0xf7, 0x71, 0x29, 0xc1, 0x99,
+ 0x0c, 0x54, 0xbc, 0xe4, 0x8b, 0xd3, 0x3b, 0x63, 0xf6, 0xae,
+ 0x46, 0x1e, 0x57, 0x0f, 0xe7, 0xbf, 0x2a, 0x72, 0x9a, 0xc2,
+ 0xad, 0xf5, 0x1d, 0x45, 0xd0, 0x88, 0x60, 0x38, 0xbe, 0xe6,
+ 0x0e, 0x56, 0xc3, 0x9b, 0x73, 0x2b, 0x44, 0x1c, 0xf4, 0xac,
+ 0x39, 0x61, 0x89, 0xd1, 0x00, 0x59, 0xb2, 0xeb, 0x79, 0x20,
+ 0xcb, 0x92, 0xf2, 0xab, 0x40, 0x19, 0x8b, 0xd2, 0x39, 0x60,
+ 0xf9, 0xa0, 0x4b, 0x12, 0x80, 0xd9, 0x32, 0x6b, 0x0b, 0x52,
+ 0xb9, 0xe0, 0x72, 0x2b, 0xc0, 0x99, 0xef, 0xb6, 0x5d, 0x04,
+ 0x96, 0xcf, 0x24, 0x7d, 0x1d, 0x44, 0xaf, 0xf6, 0x64, 0x3d,
+ 0xd6, 0x8f, 0x16, 0x4f, 0xa4, 0xfd, 0x6f, 0x36, 0xdd, 0x84,
+ 0xe4, 0xbd, 0x56, 0x0f, 0x9d, 0xc4, 0x2f, 0x76, 0xc3, 0x9a,
+ 0x71, 0x28, 0xba, 0xe3, 0x08, 0x51, 0x31, 0x68, 0x83, 0xda,
+ 0x48, 0x11, 0xfa, 0xa3, 0x3a, 0x63, 0x88, 0xd1, 0x43, 0x1a,
+ 0xf1, 0xa8, 0xc8, 0x91, 0x7a, 0x23, 0xb1, 0xe8, 0x03, 0x5a,
+ 0x2c, 0x75, 0x9e, 0xc7, 0x55, 0x0c, 0xe7, 0xbe, 0xde, 0x87,
+ 0x6c, 0x35, 0xa7, 0xfe, 0x15, 0x4c, 0xd5, 0x8c, 0x67, 0x3e,
+ 0xac, 0xf5, 0x1e, 0x47, 0x27, 0x7e, 0x95, 0xcc, 0x5e, 0x07,
+ 0xec, 0xb5, 0x9b, 0xc2, 0x29, 0x70, 0xe2, 0xbb, 0x50, 0x09,
+ 0x69, 0x30, 0xdb, 0x82, 0x10, 0x49, 0xa2, 0xfb, 0x62, 0x3b,
+ 0xd0, 0x89, 0x1b, 0x42, 0xa9, 0xf0, 0x90, 0xc9, 0x22, 0x7b,
+ 0xe9, 0xb0, 0x5b, 0x02, 0x74, 0x2d, 0xc6, 0x9f, 0x0d, 0x54,
+ 0xbf, 0xe6, 0x86, 0xdf, 0x34, 0x6d, 0xff, 0xa6, 0x4d, 0x14,
+ 0x8d, 0xd4, 0x3f, 0x66, 0xf4, 0xad, 0x46, 0x1f, 0x7f, 0x26,
+ 0xcd, 0x94, 0x06, 0x5f, 0xb4, 0xed, 0x58, 0x01, 0xea, 0xb3,
+ 0x21, 0x78, 0x93, 0xca, 0xaa, 0xf3, 0x18, 0x41, 0xd3, 0x8a,
+ 0x61, 0x38, 0xa1, 0xf8, 0x13, 0x4a, 0xd8, 0x81, 0x6a, 0x33,
+ 0x53, 0x0a, 0xe1, 0xb8, 0x2a, 0x73, 0x98, 0xc1, 0xb7, 0xee,
+ 0x05, 0x5c, 0xce, 0x97, 0x7c, 0x25, 0x45, 0x1c, 0xf7, 0xae,
+ 0x3c, 0x65, 0x8e, 0xd7, 0x4e, 0x17, 0xfc, 0xa5, 0x37, 0x6e,
+ 0x85, 0xdc, 0xbc, 0xe5, 0x0e, 0x57, 0xc5, 0x9c, 0x77, 0x2e,
+ 0x00, 0x5a, 0xb4, 0xee, 0x75, 0x2f, 0xc1, 0x9b, 0xea, 0xb0,
+ 0x5e, 0x04, 0x9f, 0xc5, 0x2b, 0x71, 0xc9, 0x93, 0x7d, 0x27,
+ 0xbc, 0xe6, 0x08, 0x52, 0x23, 0x79, 0x97, 0xcd, 0x56, 0x0c,
+ 0xe2, 0xb8, 0x8f, 0xd5, 0x3b, 0x61, 0xfa, 0xa0, 0x4e, 0x14,
+ 0x65, 0x3f, 0xd1, 0x8b, 0x10, 0x4a, 0xa4, 0xfe, 0x46, 0x1c,
+ 0xf2, 0xa8, 0x33, 0x69, 0x87, 0xdd, 0xac, 0xf6, 0x18, 0x42,
+ 0xd9, 0x83, 0x6d, 0x37, 0x03, 0x59, 0xb7, 0xed, 0x76, 0x2c,
+ 0xc2, 0x98, 0xe9, 0xb3, 0x5d, 0x07, 0x9c, 0xc6, 0x28, 0x72,
+ 0xca, 0x90, 0x7e, 0x24, 0xbf, 0xe5, 0x0b, 0x51, 0x20, 0x7a,
+ 0x94, 0xce, 0x55, 0x0f, 0xe1, 0xbb, 0x8c, 0xd6, 0x38, 0x62,
+ 0xf9, 0xa3, 0x4d, 0x17, 0x66, 0x3c, 0xd2, 0x88, 0x13, 0x49,
+ 0xa7, 0xfd, 0x45, 0x1f, 0xf1, 0xab, 0x30, 0x6a, 0x84, 0xde,
+ 0xaf, 0xf5, 0x1b, 0x41, 0xda, 0x80, 0x6e, 0x34, 0x06, 0x5c,
+ 0xb2, 0xe8, 0x73, 0x29, 0xc7, 0x9d, 0xec, 0xb6, 0x58, 0x02,
+ 0x99, 0xc3, 0x2d, 0x77, 0xcf, 0x95, 0x7b, 0x21, 0xba, 0xe0,
+ 0x0e, 0x54, 0x25, 0x7f, 0x91, 0xcb, 0x50, 0x0a, 0xe4, 0xbe,
+ 0x89, 0xd3, 0x3d, 0x67, 0xfc, 0xa6, 0x48, 0x12, 0x63, 0x39,
+ 0xd7, 0x8d, 0x16, 0x4c, 0xa2, 0xf8, 0x40, 0x1a, 0xf4, 0xae,
+ 0x35, 0x6f, 0x81, 0xdb, 0xaa, 0xf0, 0x1e, 0x44, 0xdf, 0x85,
+ 0x6b, 0x31, 0x05, 0x5f, 0xb1, 0xeb, 0x70, 0x2a, 0xc4, 0x9e,
+ 0xef, 0xb5, 0x5b, 0x01, 0x9a, 0xc0, 0x2e, 0x74, 0xcc, 0x96,
+ 0x78, 0x22, 0xb9, 0xe3, 0x0d, 0x57, 0x26, 0x7c, 0x92, 0xc8,
+ 0x53, 0x09, 0xe7, 0xbd, 0x8a, 0xd0, 0x3e, 0x64, 0xff, 0xa5,
+ 0x4b, 0x11, 0x60, 0x3a, 0xd4, 0x8e, 0x15, 0x4f, 0xa1, 0xfb,
+ 0x43, 0x19, 0xf7, 0xad, 0x36, 0x6c, 0x82, 0xd8, 0xa9, 0xf3,
+ 0x1d, 0x47, 0xdc, 0x86, 0x68, 0x32, 0x00, 0x5b, 0xb6, 0xed,
+ 0x71, 0x2a, 0xc7, 0x9c, 0xe2, 0xb9, 0x54, 0x0f, 0x93, 0xc8,
+ 0x25, 0x7e, 0xd9, 0x82, 0x6f, 0x34, 0xa8, 0xf3, 0x1e, 0x45,
+ 0x3b, 0x60, 0x8d, 0xd6, 0x4a, 0x11, 0xfc, 0xa7, 0xaf, 0xf4,
+ 0x19, 0x42, 0xde, 0x85, 0x68, 0x33, 0x4d, 0x16, 0xfb, 0xa0,
+ 0x3c, 0x67, 0x8a, 0xd1, 0x76, 0x2d, 0xc0, 0x9b, 0x07, 0x5c,
+ 0xb1, 0xea, 0x94, 0xcf, 0x22, 0x79, 0xe5, 0xbe, 0x53, 0x08,
+ 0x43, 0x18, 0xf5, 0xae, 0x32, 0x69, 0x84, 0xdf, 0xa1, 0xfa,
+ 0x17, 0x4c, 0xd0, 0x8b, 0x66, 0x3d, 0x9a, 0xc1, 0x2c, 0x77,
+ 0xeb, 0xb0, 0x5d, 0x06, 0x78, 0x23, 0xce, 0x95, 0x09, 0x52,
+ 0xbf, 0xe4, 0xec, 0xb7, 0x5a, 0x01, 0x9d, 0xc6, 0x2b, 0x70,
+ 0x0e, 0x55, 0xb8, 0xe3, 0x7f, 0x24, 0xc9, 0x92, 0x35, 0x6e,
+ 0x83, 0xd8, 0x44, 0x1f, 0xf2, 0xa9, 0xd7, 0x8c, 0x61, 0x3a,
+ 0xa6, 0xfd, 0x10, 0x4b, 0x86, 0xdd, 0x30, 0x6b, 0xf7, 0xac,
+ 0x41, 0x1a, 0x64, 0x3f, 0xd2, 0x89, 0x15, 0x4e, 0xa3, 0xf8,
+ 0x5f, 0x04, 0xe9, 0xb2, 0x2e, 0x75, 0x98, 0xc3, 0xbd, 0xe6,
+ 0x0b, 0x50, 0xcc, 0x97, 0x7a, 0x21, 0x29, 0x72, 0x9f, 0xc4,
+ 0x58, 0x03, 0xee, 0xb5, 0xcb, 0x90, 0x7d, 0x26, 0xba, 0xe1,
+ 0x0c, 0x57, 0xf0, 0xab, 0x46, 0x1d, 0x81, 0xda, 0x37, 0x6c,
+ 0x12, 0x49, 0xa4, 0xff, 0x63, 0x38, 0xd5, 0x8e, 0xc5, 0x9e,
+ 0x73, 0x28, 0xb4, 0xef, 0x02, 0x59, 0x27, 0x7c, 0x91, 0xca,
+ 0x56, 0x0d, 0xe0, 0xbb, 0x1c, 0x47, 0xaa, 0xf1, 0x6d, 0x36,
+ 0xdb, 0x80, 0xfe, 0xa5, 0x48, 0x13, 0x8f, 0xd4, 0x39, 0x62,
+ 0x6a, 0x31, 0xdc, 0x87, 0x1b, 0x40, 0xad, 0xf6, 0x88, 0xd3,
+ 0x3e, 0x65, 0xf9, 0xa2, 0x4f, 0x14, 0xb3, 0xe8, 0x05, 0x5e,
+ 0xc2, 0x99, 0x74, 0x2f, 0x51, 0x0a, 0xe7, 0xbc, 0x20, 0x7b,
+ 0x96, 0xcd, 0x00, 0x5c, 0xb8, 0xe4, 0x6d, 0x31, 0xd5, 0x89,
+ 0xda, 0x86, 0x62, 0x3e, 0xb7, 0xeb, 0x0f, 0x53, 0xa9, 0xf5,
+ 0x11, 0x4d, 0xc4, 0x98, 0x7c, 0x20, 0x73, 0x2f, 0xcb, 0x97,
+ 0x1e, 0x42, 0xa6, 0xfa, 0x4f, 0x13, 0xf7, 0xab, 0x22, 0x7e,
+ 0x9a, 0xc6, 0x95, 0xc9, 0x2d, 0x71, 0xf8, 0xa4, 0x40, 0x1c,
+ 0xe6, 0xba, 0x5e, 0x02, 0x8b, 0xd7, 0x33, 0x6f, 0x3c, 0x60,
+ 0x84, 0xd8, 0x51, 0x0d, 0xe9, 0xb5, 0x9e, 0xc2, 0x26, 0x7a,
+ 0xf3, 0xaf, 0x4b, 0x17, 0x44, 0x18, 0xfc, 0xa0, 0x29, 0x75,
+ 0x91, 0xcd, 0x37, 0x6b, 0x8f, 0xd3, 0x5a, 0x06, 0xe2, 0xbe,
+ 0xed, 0xb1, 0x55, 0x09, 0x80, 0xdc, 0x38, 0x64, 0xd1, 0x8d,
+ 0x69, 0x35, 0xbc, 0xe0, 0x04, 0x58, 0x0b, 0x57, 0xb3, 0xef,
+ 0x66, 0x3a, 0xde, 0x82, 0x78, 0x24, 0xc0, 0x9c, 0x15, 0x49,
+ 0xad, 0xf1, 0xa2, 0xfe, 0x1a, 0x46, 0xcf, 0x93, 0x77, 0x2b,
+ 0x21, 0x7d, 0x99, 0xc5, 0x4c, 0x10, 0xf4, 0xa8, 0xfb, 0xa7,
+ 0x43, 0x1f, 0x96, 0xca, 0x2e, 0x72, 0x88, 0xd4, 0x30, 0x6c,
+ 0xe5, 0xb9, 0x5d, 0x01, 0x52, 0x0e, 0xea, 0xb6, 0x3f, 0x63,
+ 0x87, 0xdb, 0x6e, 0x32, 0xd6, 0x8a, 0x03, 0x5f, 0xbb, 0xe7,
+ 0xb4, 0xe8, 0x0c, 0x50, 0xd9, 0x85, 0x61, 0x3d, 0xc7, 0x9b,
+ 0x7f, 0x23, 0xaa, 0xf6, 0x12, 0x4e, 0x1d, 0x41, 0xa5, 0xf9,
+ 0x70, 0x2c, 0xc8, 0x94, 0xbf, 0xe3, 0x07, 0x5b, 0xd2, 0x8e,
+ 0x6a, 0x36, 0x65, 0x39, 0xdd, 0x81, 0x08, 0x54, 0xb0, 0xec,
+ 0x16, 0x4a, 0xae, 0xf2, 0x7b, 0x27, 0xc3, 0x9f, 0xcc, 0x90,
+ 0x74, 0x28, 0xa1, 0xfd, 0x19, 0x45, 0xf0, 0xac, 0x48, 0x14,
+ 0x9d, 0xc1, 0x25, 0x79, 0x2a, 0x76, 0x92, 0xce, 0x47, 0x1b,
+ 0xff, 0xa3, 0x59, 0x05, 0xe1, 0xbd, 0x34, 0x68, 0x8c, 0xd0,
+ 0x83, 0xdf, 0x3b, 0x67, 0xee, 0xb2, 0x56, 0x0a, 0x00, 0x5d,
+ 0xba, 0xe7, 0x69, 0x34, 0xd3, 0x8e, 0xd2, 0x8f, 0x68, 0x35,
+ 0xbb, 0xe6, 0x01, 0x5c, 0xb9, 0xe4, 0x03, 0x5e, 0xd0, 0x8d,
+ 0x6a, 0x37, 0x6b, 0x36, 0xd1, 0x8c, 0x02, 0x5f, 0xb8, 0xe5,
+ 0x6f, 0x32, 0xd5, 0x88, 0x06, 0x5b, 0xbc, 0xe1, 0xbd, 0xe0,
+ 0x07, 0x5a, 0xd4, 0x89, 0x6e, 0x33, 0xd6, 0x8b, 0x6c, 0x31,
+ 0xbf, 0xe2, 0x05, 0x58, 0x04, 0x59, 0xbe, 0xe3, 0x6d, 0x30,
+ 0xd7, 0x8a, 0xde, 0x83, 0x64, 0x39, 0xb7, 0xea, 0x0d, 0x50,
+ 0x0c, 0x51, 0xb6, 0xeb, 0x65, 0x38, 0xdf, 0x82, 0x67, 0x3a,
+ 0xdd, 0x80, 0x0e, 0x53, 0xb4, 0xe9, 0xb5, 0xe8, 0x0f, 0x52,
+ 0xdc, 0x81, 0x66, 0x3b, 0xb1, 0xec, 0x0b, 0x56, 0xd8, 0x85,
+ 0x62, 0x3f, 0x63, 0x3e, 0xd9, 0x84, 0x0a, 0x57, 0xb0, 0xed,
+ 0x08, 0x55, 0xb2, 0xef, 0x61, 0x3c, 0xdb, 0x86, 0xda, 0x87,
+ 0x60, 0x3d, 0xb3, 0xee, 0x09, 0x54, 0xa1, 0xfc, 0x1b, 0x46,
+ 0xc8, 0x95, 0x72, 0x2f, 0x73, 0x2e, 0xc9, 0x94, 0x1a, 0x47,
+ 0xa0, 0xfd, 0x18, 0x45, 0xa2, 0xff, 0x71, 0x2c, 0xcb, 0x96,
+ 0xca, 0x97, 0x70, 0x2d, 0xa3, 0xfe, 0x19, 0x44, 0xce, 0x93,
+ 0x74, 0x29, 0xa7, 0xfa, 0x1d, 0x40, 0x1c, 0x41, 0xa6, 0xfb,
+ 0x75, 0x28, 0xcf, 0x92, 0x77, 0x2a, 0xcd, 0x90, 0x1e, 0x43,
+ 0xa4, 0xf9, 0xa5, 0xf8, 0x1f, 0x42, 0xcc, 0x91, 0x76, 0x2b,
+ 0x7f, 0x22, 0xc5, 0x98, 0x16, 0x4b, 0xac, 0xf1, 0xad, 0xf0,
+ 0x17, 0x4a, 0xc4, 0x99, 0x7e, 0x23, 0xc6, 0x9b, 0x7c, 0x21,
+ 0xaf, 0xf2, 0x15, 0x48, 0x14, 0x49, 0xae, 0xf3, 0x7d, 0x20,
+ 0xc7, 0x9a, 0x10, 0x4d, 0xaa, 0xf7, 0x79, 0x24, 0xc3, 0x9e,
+ 0xc2, 0x9f, 0x78, 0x25, 0xab, 0xf6, 0x11, 0x4c, 0xa9, 0xf4,
+ 0x13, 0x4e, 0xc0, 0x9d, 0x7a, 0x27, 0x7b, 0x26, 0xc1, 0x9c,
+ 0x12, 0x4f, 0xa8, 0xf5, 0x00, 0x5e, 0xbc, 0xe2, 0x65, 0x3b,
+ 0xd9, 0x87, 0xca, 0x94, 0x76, 0x28, 0xaf, 0xf1, 0x13, 0x4d,
+ 0x89, 0xd7, 0x35, 0x6b, 0xec, 0xb2, 0x50, 0x0e, 0x43, 0x1d,
+ 0xff, 0xa1, 0x26, 0x78, 0x9a, 0xc4, 0x0f, 0x51, 0xb3, 0xed,
+ 0x6a, 0x34, 0xd6, 0x88, 0xc5, 0x9b, 0x79, 0x27, 0xa0, 0xfe,
+ 0x1c, 0x42, 0x86, 0xd8, 0x3a, 0x64, 0xe3, 0xbd, 0x5f, 0x01,
+ 0x4c, 0x12, 0xf0, 0xae, 0x29, 0x77, 0x95, 0xcb, 0x1e, 0x40,
+ 0xa2, 0xfc, 0x7b, 0x25, 0xc7, 0x99, 0xd4, 0x8a, 0x68, 0x36,
+ 0xb1, 0xef, 0x0d, 0x53, 0x97, 0xc9, 0x2b, 0x75, 0xf2, 0xac,
+ 0x4e, 0x10, 0x5d, 0x03, 0xe1, 0xbf, 0x38, 0x66, 0x84, 0xda,
+ 0x11, 0x4f, 0xad, 0xf3, 0x74, 0x2a, 0xc8, 0x96, 0xdb, 0x85,
+ 0x67, 0x39, 0xbe, 0xe0, 0x02, 0x5c, 0x98, 0xc6, 0x24, 0x7a,
+ 0xfd, 0xa3, 0x41, 0x1f, 0x52, 0x0c, 0xee, 0xb0, 0x37, 0x69,
+ 0x8b, 0xd5, 0x3c, 0x62, 0x80, 0xde, 0x59, 0x07, 0xe5, 0xbb,
+ 0xf6, 0xa8, 0x4a, 0x14, 0x93, 0xcd, 0x2f, 0x71, 0xb5, 0xeb,
+ 0x09, 0x57, 0xd0, 0x8e, 0x6c, 0x32, 0x7f, 0x21, 0xc3, 0x9d,
+ 0x1a, 0x44, 0xa6, 0xf8, 0x33, 0x6d, 0x8f, 0xd1, 0x56, 0x08,
+ 0xea, 0xb4, 0xf9, 0xa7, 0x45, 0x1b, 0x9c, 0xc2, 0x20, 0x7e,
+ 0xba, 0xe4, 0x06, 0x58, 0xdf, 0x81, 0x63, 0x3d, 0x70, 0x2e,
+ 0xcc, 0x92, 0x15, 0x4b, 0xa9, 0xf7, 0x22, 0x7c, 0x9e, 0xc0,
+ 0x47, 0x19, 0xfb, 0xa5, 0xe8, 0xb6, 0x54, 0x0a, 0x8d, 0xd3,
+ 0x31, 0x6f, 0xab, 0xf5, 0x17, 0x49, 0xce, 0x90, 0x72, 0x2c,
+ 0x61, 0x3f, 0xdd, 0x83, 0x04, 0x5a, 0xb8, 0xe6, 0x2d, 0x73,
+ 0x91, 0xcf, 0x48, 0x16, 0xf4, 0xaa, 0xe7, 0xb9, 0x5b, 0x05,
+ 0x82, 0xdc, 0x3e, 0x60, 0xa4, 0xfa, 0x18, 0x46, 0xc1, 0x9f,
+ 0x7d, 0x23, 0x6e, 0x30, 0xd2, 0x8c, 0x0b, 0x55, 0xb7, 0xe9,
+ 0x00, 0x5f, 0xbe, 0xe1, 0x61, 0x3e, 0xdf, 0x80, 0xc2, 0x9d,
+ 0x7c, 0x23, 0xa3, 0xfc, 0x1d, 0x42, 0x99, 0xc6, 0x27, 0x78,
+ 0xf8, 0xa7, 0x46, 0x19, 0x5b, 0x04, 0xe5, 0xba, 0x3a, 0x65,
+ 0x84, 0xdb, 0x2f, 0x70, 0x91, 0xce, 0x4e, 0x11, 0xf0, 0xaf,
+ 0xed, 0xb2, 0x53, 0x0c, 0x8c, 0xd3, 0x32, 0x6d, 0xb6, 0xe9,
+ 0x08, 0x57, 0xd7, 0x88, 0x69, 0x36, 0x74, 0x2b, 0xca, 0x95,
+ 0x15, 0x4a, 0xab, 0xf4, 0x5e, 0x01, 0xe0, 0xbf, 0x3f, 0x60,
+ 0x81, 0xde, 0x9c, 0xc3, 0x22, 0x7d, 0xfd, 0xa2, 0x43, 0x1c,
+ 0xc7, 0x98, 0x79, 0x26, 0xa6, 0xf9, 0x18, 0x47, 0x05, 0x5a,
+ 0xbb, 0xe4, 0x64, 0x3b, 0xda, 0x85, 0x71, 0x2e, 0xcf, 0x90,
+ 0x10, 0x4f, 0xae, 0xf1, 0xb3, 0xec, 0x0d, 0x52, 0xd2, 0x8d,
+ 0x6c, 0x33, 0xe8, 0xb7, 0x56, 0x09, 0x89, 0xd6, 0x37, 0x68,
+ 0x2a, 0x75, 0x94, 0xcb, 0x4b, 0x14, 0xf5, 0xaa, 0xbc, 0xe3,
+ 0x02, 0x5d, 0xdd, 0x82, 0x63, 0x3c, 0x7e, 0x21, 0xc0, 0x9f,
+ 0x1f, 0x40, 0xa1, 0xfe, 0x25, 0x7a, 0x9b, 0xc4, 0x44, 0x1b,
+ 0xfa, 0xa5, 0xe7, 0xb8, 0x59, 0x06, 0x86, 0xd9, 0x38, 0x67,
+ 0x93, 0xcc, 0x2d, 0x72, 0xf2, 0xad, 0x4c, 0x13, 0x51, 0x0e,
+ 0xef, 0xb0, 0x30, 0x6f, 0x8e, 0xd1, 0x0a, 0x55, 0xb4, 0xeb,
+ 0x6b, 0x34, 0xd5, 0x8a, 0xc8, 0x97, 0x76, 0x29, 0xa9, 0xf6,
+ 0x17, 0x48, 0xe2, 0xbd, 0x5c, 0x03, 0x83, 0xdc, 0x3d, 0x62,
+ 0x20, 0x7f, 0x9e, 0xc1, 0x41, 0x1e, 0xff, 0xa0, 0x7b, 0x24,
+ 0xc5, 0x9a, 0x1a, 0x45, 0xa4, 0xfb, 0xb9, 0xe6, 0x07, 0x58,
+ 0xd8, 0x87, 0x66, 0x39, 0xcd, 0x92, 0x73, 0x2c, 0xac, 0xf3,
+ 0x12, 0x4d, 0x0f, 0x50, 0xb1, 0xee, 0x6e, 0x31, 0xd0, 0x8f,
+ 0x54, 0x0b, 0xea, 0xb5, 0x35, 0x6a, 0x8b, 0xd4, 0x96, 0xc9,
+ 0x28, 0x77, 0xf7, 0xa8, 0x49, 0x16, 0x00, 0x60, 0xc0, 0xa0,
+ 0x9d, 0xfd, 0x5d, 0x3d, 0x27, 0x47, 0xe7, 0x87, 0xba, 0xda,
+ 0x7a, 0x1a, 0x4e, 0x2e, 0x8e, 0xee, 0xd3, 0xb3, 0x13, 0x73,
+ 0x69, 0x09, 0xa9, 0xc9, 0xf4, 0x94, 0x34, 0x54, 0x9c, 0xfc,
+ 0x5c, 0x3c, 0x01, 0x61, 0xc1, 0xa1, 0xbb, 0xdb, 0x7b, 0x1b,
+ 0x26, 0x46, 0xe6, 0x86, 0xd2, 0xb2, 0x12, 0x72, 0x4f, 0x2f,
+ 0x8f, 0xef, 0xf5, 0x95, 0x35, 0x55, 0x68, 0x08, 0xa8, 0xc8,
+ 0x25, 0x45, 0xe5, 0x85, 0xb8, 0xd8, 0x78, 0x18, 0x02, 0x62,
+ 0xc2, 0xa2, 0x9f, 0xff, 0x5f, 0x3f, 0x6b, 0x0b, 0xab, 0xcb,
+ 0xf6, 0x96, 0x36, 0x56, 0x4c, 0x2c, 0x8c, 0xec, 0xd1, 0xb1,
+ 0x11, 0x71, 0xb9, 0xd9, 0x79, 0x19, 0x24, 0x44, 0xe4, 0x84,
+ 0x9e, 0xfe, 0x5e, 0x3e, 0x03, 0x63, 0xc3, 0xa3, 0xf7, 0x97,
+ 0x37, 0x57, 0x6a, 0x0a, 0xaa, 0xca, 0xd0, 0xb0, 0x10, 0x70,
+ 0x4d, 0x2d, 0x8d, 0xed, 0x4a, 0x2a, 0x8a, 0xea, 0xd7, 0xb7,
+ 0x17, 0x77, 0x6d, 0x0d, 0xad, 0xcd, 0xf0, 0x90, 0x30, 0x50,
+ 0x04, 0x64, 0xc4, 0xa4, 0x99, 0xf9, 0x59, 0x39, 0x23, 0x43,
+ 0xe3, 0x83, 0xbe, 0xde, 0x7e, 0x1e, 0xd6, 0xb6, 0x16, 0x76,
+ 0x4b, 0x2b, 0x8b, 0xeb, 0xf1, 0x91, 0x31, 0x51, 0x6c, 0x0c,
+ 0xac, 0xcc, 0x98, 0xf8, 0x58, 0x38, 0x05, 0x65, 0xc5, 0xa5,
+ 0xbf, 0xdf, 0x7f, 0x1f, 0x22, 0x42, 0xe2, 0x82, 0x6f, 0x0f,
+ 0xaf, 0xcf, 0xf2, 0x92, 0x32, 0x52, 0x48, 0x28, 0x88, 0xe8,
+ 0xd5, 0xb5, 0x15, 0x75, 0x21, 0x41, 0xe1, 0x81, 0xbc, 0xdc,
+ 0x7c, 0x1c, 0x06, 0x66, 0xc6, 0xa6, 0x9b, 0xfb, 0x5b, 0x3b,
+ 0xf3, 0x93, 0x33, 0x53, 0x6e, 0x0e, 0xae, 0xce, 0xd4, 0xb4,
+ 0x14, 0x74, 0x49, 0x29, 0x89, 0xe9, 0xbd, 0xdd, 0x7d, 0x1d,
+ 0x20, 0x40, 0xe0, 0x80, 0x9a, 0xfa, 0x5a, 0x3a, 0x07, 0x67,
+ 0xc7, 0xa7, 0x00, 0x61, 0xc2, 0xa3, 0x99, 0xf8, 0x5b, 0x3a,
+ 0x2f, 0x4e, 0xed, 0x8c, 0xb6, 0xd7, 0x74, 0x15, 0x5e, 0x3f,
+ 0x9c, 0xfd, 0xc7, 0xa6, 0x05, 0x64, 0x71, 0x10, 0xb3, 0xd2,
+ 0xe8, 0x89, 0x2a, 0x4b, 0xbc, 0xdd, 0x7e, 0x1f, 0x25, 0x44,
+ 0xe7, 0x86, 0x93, 0xf2, 0x51, 0x30, 0x0a, 0x6b, 0xc8, 0xa9,
+ 0xe2, 0x83, 0x20, 0x41, 0x7b, 0x1a, 0xb9, 0xd8, 0xcd, 0xac,
+ 0x0f, 0x6e, 0x54, 0x35, 0x96, 0xf7, 0x65, 0x04, 0xa7, 0xc6,
+ 0xfc, 0x9d, 0x3e, 0x5f, 0x4a, 0x2b, 0x88, 0xe9, 0xd3, 0xb2,
+ 0x11, 0x70, 0x3b, 0x5a, 0xf9, 0x98, 0xa2, 0xc3, 0x60, 0x01,
+ 0x14, 0x75, 0xd6, 0xb7, 0x8d, 0xec, 0x4f, 0x2e, 0xd9, 0xb8,
+ 0x1b, 0x7a, 0x40, 0x21, 0x82, 0xe3, 0xf6, 0x97, 0x34, 0x55,
+ 0x6f, 0x0e, 0xad, 0xcc, 0x87, 0xe6, 0x45, 0x24, 0x1e, 0x7f,
+ 0xdc, 0xbd, 0xa8, 0xc9, 0x6a, 0x0b, 0x31, 0x50, 0xf3, 0x92,
+ 0xca, 0xab, 0x08, 0x69, 0x53, 0x32, 0x91, 0xf0, 0xe5, 0x84,
+ 0x27, 0x46, 0x7c, 0x1d, 0xbe, 0xdf, 0x94, 0xf5, 0x56, 0x37,
+ 0x0d, 0x6c, 0xcf, 0xae, 0xbb, 0xda, 0x79, 0x18, 0x22, 0x43,
+ 0xe0, 0x81, 0x76, 0x17, 0xb4, 0xd5, 0xef, 0x8e, 0x2d, 0x4c,
+ 0x59, 0x38, 0x9b, 0xfa, 0xc0, 0xa1, 0x02, 0x63, 0x28, 0x49,
+ 0xea, 0x8b, 0xb1, 0xd0, 0x73, 0x12, 0x07, 0x66, 0xc5, 0xa4,
+ 0x9e, 0xff, 0x5c, 0x3d, 0xaf, 0xce, 0x6d, 0x0c, 0x36, 0x57,
+ 0xf4, 0x95, 0x80, 0xe1, 0x42, 0x23, 0x19, 0x78, 0xdb, 0xba,
+ 0xf1, 0x90, 0x33, 0x52, 0x68, 0x09, 0xaa, 0xcb, 0xde, 0xbf,
+ 0x1c, 0x7d, 0x47, 0x26, 0x85, 0xe4, 0x13, 0x72, 0xd1, 0xb0,
+ 0x8a, 0xeb, 0x48, 0x29, 0x3c, 0x5d, 0xfe, 0x9f, 0xa5, 0xc4,
+ 0x67, 0x06, 0x4d, 0x2c, 0x8f, 0xee, 0xd4, 0xb5, 0x16, 0x77,
+ 0x62, 0x03, 0xa0, 0xc1, 0xfb, 0x9a, 0x39, 0x58, 0x00, 0x62,
+ 0xc4, 0xa6, 0x95, 0xf7, 0x51, 0x33, 0x37, 0x55, 0xf3, 0x91,
+ 0xa2, 0xc0, 0x66, 0x04, 0x6e, 0x0c, 0xaa, 0xc8, 0xfb, 0x99,
+ 0x3f, 0x5d, 0x59, 0x3b, 0x9d, 0xff, 0xcc, 0xae, 0x08, 0x6a,
+ 0xdc, 0xbe, 0x18, 0x7a, 0x49, 0x2b, 0x8d, 0xef, 0xeb, 0x89,
+ 0x2f, 0x4d, 0x7e, 0x1c, 0xba, 0xd8, 0xb2, 0xd0, 0x76, 0x14,
+ 0x27, 0x45, 0xe3, 0x81, 0x85, 0xe7, 0x41, 0x23, 0x10, 0x72,
+ 0xd4, 0xb6, 0xa5, 0xc7, 0x61, 0x03, 0x30, 0x52, 0xf4, 0x96,
+ 0x92, 0xf0, 0x56, 0x34, 0x07, 0x65, 0xc3, 0xa1, 0xcb, 0xa9,
+ 0x0f, 0x6d, 0x5e, 0x3c, 0x9a, 0xf8, 0xfc, 0x9e, 0x38, 0x5a,
+ 0x69, 0x0b, 0xad, 0xcf, 0x79, 0x1b, 0xbd, 0xdf, 0xec, 0x8e,
+ 0x28, 0x4a, 0x4e, 0x2c, 0x8a, 0xe8, 0xdb, 0xb9, 0x1f, 0x7d,
+ 0x17, 0x75, 0xd3, 0xb1, 0x82, 0xe0, 0x46, 0x24, 0x20, 0x42,
+ 0xe4, 0x86, 0xb5, 0xd7, 0x71, 0x13, 0x57, 0x35, 0x93, 0xf1,
+ 0xc2, 0xa0, 0x06, 0x64, 0x60, 0x02, 0xa4, 0xc6, 0xf5, 0x97,
+ 0x31, 0x53, 0x39, 0x5b, 0xfd, 0x9f, 0xac, 0xce, 0x68, 0x0a,
+ 0x0e, 0x6c, 0xca, 0xa8, 0x9b, 0xf9, 0x5f, 0x3d, 0x8b, 0xe9,
+ 0x4f, 0x2d, 0x1e, 0x7c, 0xda, 0xb8, 0xbc, 0xde, 0x78, 0x1a,
+ 0x29, 0x4b, 0xed, 0x8f, 0xe5, 0x87, 0x21, 0x43, 0x70, 0x12,
+ 0xb4, 0xd6, 0xd2, 0xb0, 0x16, 0x74, 0x47, 0x25, 0x83, 0xe1,
+ 0xf2, 0x90, 0x36, 0x54, 0x67, 0x05, 0xa3, 0xc1, 0xc5, 0xa7,
+ 0x01, 0x63, 0x50, 0x32, 0x94, 0xf6, 0x9c, 0xfe, 0x58, 0x3a,
+ 0x09, 0x6b, 0xcd, 0xaf, 0xab, 0xc9, 0x6f, 0x0d, 0x3e, 0x5c,
+ 0xfa, 0x98, 0x2e, 0x4c, 0xea, 0x88, 0xbb, 0xd9, 0x7f, 0x1d,
+ 0x19, 0x7b, 0xdd, 0xbf, 0x8c, 0xee, 0x48, 0x2a, 0x40, 0x22,
+ 0x84, 0xe6, 0xd5, 0xb7, 0x11, 0x73, 0x77, 0x15, 0xb3, 0xd1,
+ 0xe2, 0x80, 0x26, 0x44, 0x00, 0x63, 0xc6, 0xa5, 0x91, 0xf2,
+ 0x57, 0x34, 0x3f, 0x5c, 0xf9, 0x9a, 0xae, 0xcd, 0x68, 0x0b,
+ 0x7e, 0x1d, 0xb8, 0xdb, 0xef, 0x8c, 0x29, 0x4a, 0x41, 0x22,
+ 0x87, 0xe4, 0xd0, 0xb3, 0x16, 0x75, 0xfc, 0x9f, 0x3a, 0x59,
+ 0x6d, 0x0e, 0xab, 0xc8, 0xc3, 0xa0, 0x05, 0x66, 0x52, 0x31,
+ 0x94, 0xf7, 0x82, 0xe1, 0x44, 0x27, 0x13, 0x70, 0xd5, 0xb6,
+ 0xbd, 0xde, 0x7b, 0x18, 0x2c, 0x4f, 0xea, 0x89, 0xe5, 0x86,
+ 0x23, 0x40, 0x74, 0x17, 0xb2, 0xd1, 0xda, 0xb9, 0x1c, 0x7f,
+ 0x4b, 0x28, 0x8d, 0xee, 0x9b, 0xf8, 0x5d, 0x3e, 0x0a, 0x69,
+ 0xcc, 0xaf, 0xa4, 0xc7, 0x62, 0x01, 0x35, 0x56, 0xf3, 0x90,
+ 0x19, 0x7a, 0xdf, 0xbc, 0x88, 0xeb, 0x4e, 0x2d, 0x26, 0x45,
+ 0xe0, 0x83, 0xb7, 0xd4, 0x71, 0x12, 0x67, 0x04, 0xa1, 0xc2,
+ 0xf6, 0x95, 0x30, 0x53, 0x58, 0x3b, 0x9e, 0xfd, 0xc9, 0xaa,
+ 0x0f, 0x6c, 0xd7, 0xb4, 0x11, 0x72, 0x46, 0x25, 0x80, 0xe3,
+ 0xe8, 0x8b, 0x2e, 0x4d, 0x79, 0x1a, 0xbf, 0xdc, 0xa9, 0xca,
+ 0x6f, 0x0c, 0x38, 0x5b, 0xfe, 0x9d, 0x96, 0xf5, 0x50, 0x33,
+ 0x07, 0x64, 0xc1, 0xa2, 0x2b, 0x48, 0xed, 0x8e, 0xba, 0xd9,
+ 0x7c, 0x1f, 0x14, 0x77, 0xd2, 0xb1, 0x85, 0xe6, 0x43, 0x20,
+ 0x55, 0x36, 0x93, 0xf0, 0xc4, 0xa7, 0x02, 0x61, 0x6a, 0x09,
+ 0xac, 0xcf, 0xfb, 0x98, 0x3d, 0x5e, 0x32, 0x51, 0xf4, 0x97,
+ 0xa3, 0xc0, 0x65, 0x06, 0x0d, 0x6e, 0xcb, 0xa8, 0x9c, 0xff,
+ 0x5a, 0x39, 0x4c, 0x2f, 0x8a, 0xe9, 0xdd, 0xbe, 0x1b, 0x78,
+ 0x73, 0x10, 0xb5, 0xd6, 0xe2, 0x81, 0x24, 0x47, 0xce, 0xad,
+ 0x08, 0x6b, 0x5f, 0x3c, 0x99, 0xfa, 0xf1, 0x92, 0x37, 0x54,
+ 0x60, 0x03, 0xa6, 0xc5, 0xb0, 0xd3, 0x76, 0x15, 0x21, 0x42,
+ 0xe7, 0x84, 0x8f, 0xec, 0x49, 0x2a, 0x1e, 0x7d, 0xd8, 0xbb,
+ 0x00, 0x64, 0xc8, 0xac, 0x8d, 0xe9, 0x45, 0x21, 0x07, 0x63,
+ 0xcf, 0xab, 0x8a, 0xee, 0x42, 0x26, 0x0e, 0x6a, 0xc6, 0xa2,
+ 0x83, 0xe7, 0x4b, 0x2f, 0x09, 0x6d, 0xc1, 0xa5, 0x84, 0xe0,
+ 0x4c, 0x28, 0x1c, 0x78, 0xd4, 0xb0, 0x91, 0xf5, 0x59, 0x3d,
+ 0x1b, 0x7f, 0xd3, 0xb7, 0x96, 0xf2, 0x5e, 0x3a, 0x12, 0x76,
+ 0xda, 0xbe, 0x9f, 0xfb, 0x57, 0x33, 0x15, 0x71, 0xdd, 0xb9,
+ 0x98, 0xfc, 0x50, 0x34, 0x38, 0x5c, 0xf0, 0x94, 0xb5, 0xd1,
+ 0x7d, 0x19, 0x3f, 0x5b, 0xf7, 0x93, 0xb2, 0xd6, 0x7a, 0x1e,
+ 0x36, 0x52, 0xfe, 0x9a, 0xbb, 0xdf, 0x73, 0x17, 0x31, 0x55,
+ 0xf9, 0x9d, 0xbc, 0xd8, 0x74, 0x10, 0x24, 0x40, 0xec, 0x88,
+ 0xa9, 0xcd, 0x61, 0x05, 0x23, 0x47, 0xeb, 0x8f, 0xae, 0xca,
+ 0x66, 0x02, 0x2a, 0x4e, 0xe2, 0x86, 0xa7, 0xc3, 0x6f, 0x0b,
+ 0x2d, 0x49, 0xe5, 0x81, 0xa0, 0xc4, 0x68, 0x0c, 0x70, 0x14,
+ 0xb8, 0xdc, 0xfd, 0x99, 0x35, 0x51, 0x77, 0x13, 0xbf, 0xdb,
+ 0xfa, 0x9e, 0x32, 0x56, 0x7e, 0x1a, 0xb6, 0xd2, 0xf3, 0x97,
+ 0x3b, 0x5f, 0x79, 0x1d, 0xb1, 0xd5, 0xf4, 0x90, 0x3c, 0x58,
+ 0x6c, 0x08, 0xa4, 0xc0, 0xe1, 0x85, 0x29, 0x4d, 0x6b, 0x0f,
+ 0xa3, 0xc7, 0xe6, 0x82, 0x2e, 0x4a, 0x62, 0x06, 0xaa, 0xce,
+ 0xef, 0x8b, 0x27, 0x43, 0x65, 0x01, 0xad, 0xc9, 0xe8, 0x8c,
+ 0x20, 0x44, 0x48, 0x2c, 0x80, 0xe4, 0xc5, 0xa1, 0x0d, 0x69,
+ 0x4f, 0x2b, 0x87, 0xe3, 0xc2, 0xa6, 0x0a, 0x6e, 0x46, 0x22,
+ 0x8e, 0xea, 0xcb, 0xaf, 0x03, 0x67, 0x41, 0x25, 0x89, 0xed,
+ 0xcc, 0xa8, 0x04, 0x60, 0x54, 0x30, 0x9c, 0xf8, 0xd9, 0xbd,
+ 0x11, 0x75, 0x53, 0x37, 0x9b, 0xff, 0xde, 0xba, 0x16, 0x72,
+ 0x5a, 0x3e, 0x92, 0xf6, 0xd7, 0xb3, 0x1f, 0x7b, 0x5d, 0x39,
+ 0x95, 0xf1, 0xd0, 0xb4, 0x18, 0x7c, 0x00, 0x65, 0xca, 0xaf,
+ 0x89, 0xec, 0x43, 0x26, 0x0f, 0x6a, 0xc5, 0xa0, 0x86, 0xe3,
+ 0x4c, 0x29, 0x1e, 0x7b, 0xd4, 0xb1, 0x97, 0xf2, 0x5d, 0x38,
+ 0x11, 0x74, 0xdb, 0xbe, 0x98, 0xfd, 0x52, 0x37, 0x3c, 0x59,
+ 0xf6, 0x93, 0xb5, 0xd0, 0x7f, 0x1a, 0x33, 0x56, 0xf9, 0x9c,
+ 0xba, 0xdf, 0x70, 0x15, 0x22, 0x47, 0xe8, 0x8d, 0xab, 0xce,
+ 0x61, 0x04, 0x2d, 0x48, 0xe7, 0x82, 0xa4, 0xc1, 0x6e, 0x0b,
+ 0x78, 0x1d, 0xb2, 0xd7, 0xf1, 0x94, 0x3b, 0x5e, 0x77, 0x12,
+ 0xbd, 0xd8, 0xfe, 0x9b, 0x34, 0x51, 0x66, 0x03, 0xac, 0xc9,
+ 0xef, 0x8a, 0x25, 0x40, 0x69, 0x0c, 0xa3, 0xc6, 0xe0, 0x85,
+ 0x2a, 0x4f, 0x44, 0x21, 0x8e, 0xeb, 0xcd, 0xa8, 0x07, 0x62,
+ 0x4b, 0x2e, 0x81, 0xe4, 0xc2, 0xa7, 0x08, 0x6d, 0x5a, 0x3f,
+ 0x90, 0xf5, 0xd3, 0xb6, 0x19, 0x7c, 0x55, 0x30, 0x9f, 0xfa,
+ 0xdc, 0xb9, 0x16, 0x73, 0xf0, 0x95, 0x3a, 0x5f, 0x79, 0x1c,
+ 0xb3, 0xd6, 0xff, 0x9a, 0x35, 0x50, 0x76, 0x13, 0xbc, 0xd9,
+ 0xee, 0x8b, 0x24, 0x41, 0x67, 0x02, 0xad, 0xc8, 0xe1, 0x84,
+ 0x2b, 0x4e, 0x68, 0x0d, 0xa2, 0xc7, 0xcc, 0xa9, 0x06, 0x63,
+ 0x45, 0x20, 0x8f, 0xea, 0xc3, 0xa6, 0x09, 0x6c, 0x4a, 0x2f,
+ 0x80, 0xe5, 0xd2, 0xb7, 0x18, 0x7d, 0x5b, 0x3e, 0x91, 0xf4,
+ 0xdd, 0xb8, 0x17, 0x72, 0x54, 0x31, 0x9e, 0xfb, 0x88, 0xed,
+ 0x42, 0x27, 0x01, 0x64, 0xcb, 0xae, 0x87, 0xe2, 0x4d, 0x28,
+ 0x0e, 0x6b, 0xc4, 0xa1, 0x96, 0xf3, 0x5c, 0x39, 0x1f, 0x7a,
+ 0xd5, 0xb0, 0x99, 0xfc, 0x53, 0x36, 0x10, 0x75, 0xda, 0xbf,
+ 0xb4, 0xd1, 0x7e, 0x1b, 0x3d, 0x58, 0xf7, 0x92, 0xbb, 0xde,
+ 0x71, 0x14, 0x32, 0x57, 0xf8, 0x9d, 0xaa, 0xcf, 0x60, 0x05,
+ 0x23, 0x46, 0xe9, 0x8c, 0xa5, 0xc0, 0x6f, 0x0a, 0x2c, 0x49,
+ 0xe6, 0x83, 0x00, 0x66, 0xcc, 0xaa, 0x85, 0xe3, 0x49, 0x2f,
+ 0x17, 0x71, 0xdb, 0xbd, 0x92, 0xf4, 0x5e, 0x38, 0x2e, 0x48,
+ 0xe2, 0x84, 0xab, 0xcd, 0x67, 0x01, 0x39, 0x5f, 0xf5, 0x93,
+ 0xbc, 0xda, 0x70, 0x16, 0x5c, 0x3a, 0x90, 0xf6, 0xd9, 0xbf,
+ 0x15, 0x73, 0x4b, 0x2d, 0x87, 0xe1, 0xce, 0xa8, 0x02, 0x64,
+ 0x72, 0x14, 0xbe, 0xd8, 0xf7, 0x91, 0x3b, 0x5d, 0x65, 0x03,
+ 0xa9, 0xcf, 0xe0, 0x86, 0x2c, 0x4a, 0xb8, 0xde, 0x74, 0x12,
+ 0x3d, 0x5b, 0xf1, 0x97, 0xaf, 0xc9, 0x63, 0x05, 0x2a, 0x4c,
+ 0xe6, 0x80, 0x96, 0xf0, 0x5a, 0x3c, 0x13, 0x75, 0xdf, 0xb9,
+ 0x81, 0xe7, 0x4d, 0x2b, 0x04, 0x62, 0xc8, 0xae, 0xe4, 0x82,
+ 0x28, 0x4e, 0x61, 0x07, 0xad, 0xcb, 0xf3, 0x95, 0x3f, 0x59,
+ 0x76, 0x10, 0xba, 0xdc, 0xca, 0xac, 0x06, 0x60, 0x4f, 0x29,
+ 0x83, 0xe5, 0xdd, 0xbb, 0x11, 0x77, 0x58, 0x3e, 0x94, 0xf2,
+ 0x6d, 0x0b, 0xa1, 0xc7, 0xe8, 0x8e, 0x24, 0x42, 0x7a, 0x1c,
+ 0xb6, 0xd0, 0xff, 0x99, 0x33, 0x55, 0x43, 0x25, 0x8f, 0xe9,
+ 0xc6, 0xa0, 0x0a, 0x6c, 0x54, 0x32, 0x98, 0xfe, 0xd1, 0xb7,
+ 0x1d, 0x7b, 0x31, 0x57, 0xfd, 0x9b, 0xb4, 0xd2, 0x78, 0x1e,
+ 0x26, 0x40, 0xea, 0x8c, 0xa3, 0xc5, 0x6f, 0x09, 0x1f, 0x79,
+ 0xd3, 0xb5, 0x9a, 0xfc, 0x56, 0x30, 0x08, 0x6e, 0xc4, 0xa2,
+ 0x8d, 0xeb, 0x41, 0x27, 0xd5, 0xb3, 0x19, 0x7f, 0x50, 0x36,
+ 0x9c, 0xfa, 0xc2, 0xa4, 0x0e, 0x68, 0x47, 0x21, 0x8b, 0xed,
+ 0xfb, 0x9d, 0x37, 0x51, 0x7e, 0x18, 0xb2, 0xd4, 0xec, 0x8a,
+ 0x20, 0x46, 0x69, 0x0f, 0xa5, 0xc3, 0x89, 0xef, 0x45, 0x23,
+ 0x0c, 0x6a, 0xc0, 0xa6, 0x9e, 0xf8, 0x52, 0x34, 0x1b, 0x7d,
+ 0xd7, 0xb1, 0xa7, 0xc1, 0x6b, 0x0d, 0x22, 0x44, 0xee, 0x88,
+ 0xb0, 0xd6, 0x7c, 0x1a, 0x35, 0x53, 0xf9, 0x9f, 0x00, 0x67,
+ 0xce, 0xa9, 0x81, 0xe6, 0x4f, 0x28, 0x1f, 0x78, 0xd1, 0xb6,
+ 0x9e, 0xf9, 0x50, 0x37, 0x3e, 0x59, 0xf0, 0x97, 0xbf, 0xd8,
+ 0x71, 0x16, 0x21, 0x46, 0xef, 0x88, 0xa0, 0xc7, 0x6e, 0x09,
+ 0x7c, 0x1b, 0xb2, 0xd5, 0xfd, 0x9a, 0x33, 0x54, 0x63, 0x04,
+ 0xad, 0xca, 0xe2, 0x85, 0x2c, 0x4b, 0x42, 0x25, 0x8c, 0xeb,
+ 0xc3, 0xa4, 0x0d, 0x6a, 0x5d, 0x3a, 0x93, 0xf4, 0xdc, 0xbb,
+ 0x12, 0x75, 0xf8, 0x9f, 0x36, 0x51, 0x79, 0x1e, 0xb7, 0xd0,
+ 0xe7, 0x80, 0x29, 0x4e, 0x66, 0x01, 0xa8, 0xcf, 0xc6, 0xa1,
+ 0x08, 0x6f, 0x47, 0x20, 0x89, 0xee, 0xd9, 0xbe, 0x17, 0x70,
+ 0x58, 0x3f, 0x96, 0xf1, 0x84, 0xe3, 0x4a, 0x2d, 0x05, 0x62,
+ 0xcb, 0xac, 0x9b, 0xfc, 0x55, 0x32, 0x1a, 0x7d, 0xd4, 0xb3,
+ 0xba, 0xdd, 0x74, 0x13, 0x3b, 0x5c, 0xf5, 0x92, 0xa5, 0xc2,
+ 0x6b, 0x0c, 0x24, 0x43, 0xea, 0x8d, 0xed, 0x8a, 0x23, 0x44,
+ 0x6c, 0x0b, 0xa2, 0xc5, 0xf2, 0x95, 0x3c, 0x5b, 0x73, 0x14,
+ 0xbd, 0xda, 0xd3, 0xb4, 0x1d, 0x7a, 0x52, 0x35, 0x9c, 0xfb,
+ 0xcc, 0xab, 0x02, 0x65, 0x4d, 0x2a, 0x83, 0xe4, 0x91, 0xf6,
+ 0x5f, 0x38, 0x10, 0x77, 0xde, 0xb9, 0x8e, 0xe9, 0x40, 0x27,
+ 0x0f, 0x68, 0xc1, 0xa6, 0xaf, 0xc8, 0x61, 0x06, 0x2e, 0x49,
+ 0xe0, 0x87, 0xb0, 0xd7, 0x7e, 0x19, 0x31, 0x56, 0xff, 0x98,
+ 0x15, 0x72, 0xdb, 0xbc, 0x94, 0xf3, 0x5a, 0x3d, 0x0a, 0x6d,
+ 0xc4, 0xa3, 0x8b, 0xec, 0x45, 0x22, 0x2b, 0x4c, 0xe5, 0x82,
+ 0xaa, 0xcd, 0x64, 0x03, 0x34, 0x53, 0xfa, 0x9d, 0xb5, 0xd2,
+ 0x7b, 0x1c, 0x69, 0x0e, 0xa7, 0xc0, 0xe8, 0x8f, 0x26, 0x41,
+ 0x76, 0x11, 0xb8, 0xdf, 0xf7, 0x90, 0x39, 0x5e, 0x57, 0x30,
+ 0x99, 0xfe, 0xd6, 0xb1, 0x18, 0x7f, 0x48, 0x2f, 0x86, 0xe1,
+ 0xc9, 0xae, 0x07, 0x60, 0x00, 0x68, 0xd0, 0xb8, 0xbd, 0xd5,
+ 0x6d, 0x05, 0x67, 0x0f, 0xb7, 0xdf, 0xda, 0xb2, 0x0a, 0x62,
+ 0xce, 0xa6, 0x1e, 0x76, 0x73, 0x1b, 0xa3, 0xcb, 0xa9, 0xc1,
+ 0x79, 0x11, 0x14, 0x7c, 0xc4, 0xac, 0x81, 0xe9, 0x51, 0x39,
+ 0x3c, 0x54, 0xec, 0x84, 0xe6, 0x8e, 0x36, 0x5e, 0x5b, 0x33,
+ 0x8b, 0xe3, 0x4f, 0x27, 0x9f, 0xf7, 0xf2, 0x9a, 0x22, 0x4a,
+ 0x28, 0x40, 0xf8, 0x90, 0x95, 0xfd, 0x45, 0x2d, 0x1f, 0x77,
+ 0xcf, 0xa7, 0xa2, 0xca, 0x72, 0x1a, 0x78, 0x10, 0xa8, 0xc0,
+ 0xc5, 0xad, 0x15, 0x7d, 0xd1, 0xb9, 0x01, 0x69, 0x6c, 0x04,
+ 0xbc, 0xd4, 0xb6, 0xde, 0x66, 0x0e, 0x0b, 0x63, 0xdb, 0xb3,
+ 0x9e, 0xf6, 0x4e, 0x26, 0x23, 0x4b, 0xf3, 0x9b, 0xf9, 0x91,
+ 0x29, 0x41, 0x44, 0x2c, 0x94, 0xfc, 0x50, 0x38, 0x80, 0xe8,
+ 0xed, 0x85, 0x3d, 0x55, 0x37, 0x5f, 0xe7, 0x8f, 0x8a, 0xe2,
+ 0x5a, 0x32, 0x3e, 0x56, 0xee, 0x86, 0x83, 0xeb, 0x53, 0x3b,
+ 0x59, 0x31, 0x89, 0xe1, 0xe4, 0x8c, 0x34, 0x5c, 0xf0, 0x98,
+ 0x20, 0x48, 0x4d, 0x25, 0x9d, 0xf5, 0x97, 0xff, 0x47, 0x2f,
+ 0x2a, 0x42, 0xfa, 0x92, 0xbf, 0xd7, 0x6f, 0x07, 0x02, 0x6a,
+ 0xd2, 0xba, 0xd8, 0xb0, 0x08, 0x60, 0x65, 0x0d, 0xb5, 0xdd,
+ 0x71, 0x19, 0xa1, 0xc9, 0xcc, 0xa4, 0x1c, 0x74, 0x16, 0x7e,
+ 0xc6, 0xae, 0xab, 0xc3, 0x7b, 0x13, 0x21, 0x49, 0xf1, 0x99,
+ 0x9c, 0xf4, 0x4c, 0x24, 0x46, 0x2e, 0x96, 0xfe, 0xfb, 0x93,
+ 0x2b, 0x43, 0xef, 0x87, 0x3f, 0x57, 0x52, 0x3a, 0x82, 0xea,
+ 0x88, 0xe0, 0x58, 0x30, 0x35, 0x5d, 0xe5, 0x8d, 0xa0, 0xc8,
+ 0x70, 0x18, 0x1d, 0x75, 0xcd, 0xa5, 0xc7, 0xaf, 0x17, 0x7f,
+ 0x7a, 0x12, 0xaa, 0xc2, 0x6e, 0x06, 0xbe, 0xd6, 0xd3, 0xbb,
+ 0x03, 0x6b, 0x09, 0x61, 0xd9, 0xb1, 0xb4, 0xdc, 0x64, 0x0c,
+ 0x00, 0x69, 0xd2, 0xbb, 0xb9, 0xd0, 0x6b, 0x02, 0x6f, 0x06,
+ 0xbd, 0xd4, 0xd6, 0xbf, 0x04, 0x6d, 0xde, 0xb7, 0x0c, 0x65,
+ 0x67, 0x0e, 0xb5, 0xdc, 0xb1, 0xd8, 0x63, 0x0a, 0x08, 0x61,
+ 0xda, 0xb3, 0xa1, 0xc8, 0x73, 0x1a, 0x18, 0x71, 0xca, 0xa3,
+ 0xce, 0xa7, 0x1c, 0x75, 0x77, 0x1e, 0xa5, 0xcc, 0x7f, 0x16,
+ 0xad, 0xc4, 0xc6, 0xaf, 0x14, 0x7d, 0x10, 0x79, 0xc2, 0xab,
+ 0xa9, 0xc0, 0x7b, 0x12, 0x5f, 0x36, 0x8d, 0xe4, 0xe6, 0x8f,
+ 0x34, 0x5d, 0x30, 0x59, 0xe2, 0x8b, 0x89, 0xe0, 0x5b, 0x32,
+ 0x81, 0xe8, 0x53, 0x3a, 0x38, 0x51, 0xea, 0x83, 0xee, 0x87,
+ 0x3c, 0x55, 0x57, 0x3e, 0x85, 0xec, 0xfe, 0x97, 0x2c, 0x45,
+ 0x47, 0x2e, 0x95, 0xfc, 0x91, 0xf8, 0x43, 0x2a, 0x28, 0x41,
+ 0xfa, 0x93, 0x20, 0x49, 0xf2, 0x9b, 0x99, 0xf0, 0x4b, 0x22,
+ 0x4f, 0x26, 0x9d, 0xf4, 0xf6, 0x9f, 0x24, 0x4d, 0xbe, 0xd7,
+ 0x6c, 0x05, 0x07, 0x6e, 0xd5, 0xbc, 0xd1, 0xb8, 0x03, 0x6a,
+ 0x68, 0x01, 0xba, 0xd3, 0x60, 0x09, 0xb2, 0xdb, 0xd9, 0xb0,
+ 0x0b, 0x62, 0x0f, 0x66, 0xdd, 0xb4, 0xb6, 0xdf, 0x64, 0x0d,
+ 0x1f, 0x76, 0xcd, 0xa4, 0xa6, 0xcf, 0x74, 0x1d, 0x70, 0x19,
+ 0xa2, 0xcb, 0xc9, 0xa0, 0x1b, 0x72, 0xc1, 0xa8, 0x13, 0x7a,
+ 0x78, 0x11, 0xaa, 0xc3, 0xae, 0xc7, 0x7c, 0x15, 0x17, 0x7e,
+ 0xc5, 0xac, 0xe1, 0x88, 0x33, 0x5a, 0x58, 0x31, 0x8a, 0xe3,
+ 0x8e, 0xe7, 0x5c, 0x35, 0x37, 0x5e, 0xe5, 0x8c, 0x3f, 0x56,
+ 0xed, 0x84, 0x86, 0xef, 0x54, 0x3d, 0x50, 0x39, 0x82, 0xeb,
+ 0xe9, 0x80, 0x3b, 0x52, 0x40, 0x29, 0x92, 0xfb, 0xf9, 0x90,
+ 0x2b, 0x42, 0x2f, 0x46, 0xfd, 0x94, 0x96, 0xff, 0x44, 0x2d,
+ 0x9e, 0xf7, 0x4c, 0x25, 0x27, 0x4e, 0xf5, 0x9c, 0xf1, 0x98,
+ 0x23, 0x4a, 0x48, 0x21, 0x9a, 0xf3, 0x00, 0x6a, 0xd4, 0xbe,
+ 0xb5, 0xdf, 0x61, 0x0b, 0x77, 0x1d, 0xa3, 0xc9, 0xc2, 0xa8,
+ 0x16, 0x7c, 0xee, 0x84, 0x3a, 0x50, 0x5b, 0x31, 0x8f, 0xe5,
+ 0x99, 0xf3, 0x4d, 0x27, 0x2c, 0x46, 0xf8, 0x92, 0xc1, 0xab,
+ 0x15, 0x7f, 0x74, 0x1e, 0xa0, 0xca, 0xb6, 0xdc, 0x62, 0x08,
+ 0x03, 0x69, 0xd7, 0xbd, 0x2f, 0x45, 0xfb, 0x91, 0x9a, 0xf0,
+ 0x4e, 0x24, 0x58, 0x32, 0x8c, 0xe6, 0xed, 0x87, 0x39, 0x53,
+ 0x9f, 0xf5, 0x4b, 0x21, 0x2a, 0x40, 0xfe, 0x94, 0xe8, 0x82,
+ 0x3c, 0x56, 0x5d, 0x37, 0x89, 0xe3, 0x71, 0x1b, 0xa5, 0xcf,
+ 0xc4, 0xae, 0x10, 0x7a, 0x06, 0x6c, 0xd2, 0xb8, 0xb3, 0xd9,
+ 0x67, 0x0d, 0x5e, 0x34, 0x8a, 0xe0, 0xeb, 0x81, 0x3f, 0x55,
+ 0x29, 0x43, 0xfd, 0x97, 0x9c, 0xf6, 0x48, 0x22, 0xb0, 0xda,
+ 0x64, 0x0e, 0x05, 0x6f, 0xd1, 0xbb, 0xc7, 0xad, 0x13, 0x79,
+ 0x72, 0x18, 0xa6, 0xcc, 0x23, 0x49, 0xf7, 0x9d, 0x96, 0xfc,
+ 0x42, 0x28, 0x54, 0x3e, 0x80, 0xea, 0xe1, 0x8b, 0x35, 0x5f,
+ 0xcd, 0xa7, 0x19, 0x73, 0x78, 0x12, 0xac, 0xc6, 0xba, 0xd0,
+ 0x6e, 0x04, 0x0f, 0x65, 0xdb, 0xb1, 0xe2, 0x88, 0x36, 0x5c,
+ 0x57, 0x3d, 0x83, 0xe9, 0x95, 0xff, 0x41, 0x2b, 0x20, 0x4a,
+ 0xf4, 0x9e, 0x0c, 0x66, 0xd8, 0xb2, 0xb9, 0xd3, 0x6d, 0x07,
+ 0x7b, 0x11, 0xaf, 0xc5, 0xce, 0xa4, 0x1a, 0x70, 0xbc, 0xd6,
+ 0x68, 0x02, 0x09, 0x63, 0xdd, 0xb7, 0xcb, 0xa1, 0x1f, 0x75,
+ 0x7e, 0x14, 0xaa, 0xc0, 0x52, 0x38, 0x86, 0xec, 0xe7, 0x8d,
+ 0x33, 0x59, 0x25, 0x4f, 0xf1, 0x9b, 0x90, 0xfa, 0x44, 0x2e,
+ 0x7d, 0x17, 0xa9, 0xc3, 0xc8, 0xa2, 0x1c, 0x76, 0x0a, 0x60,
+ 0xde, 0xb4, 0xbf, 0xd5, 0x6b, 0x01, 0x93, 0xf9, 0x47, 0x2d,
+ 0x26, 0x4c, 0xf2, 0x98, 0xe4, 0x8e, 0x30, 0x5a, 0x51, 0x3b,
+ 0x85, 0xef, 0x00, 0x6b, 0xd6, 0xbd, 0xb1, 0xda, 0x67, 0x0c,
+ 0x7f, 0x14, 0xa9, 0xc2, 0xce, 0xa5, 0x18, 0x73, 0xfe, 0x95,
+ 0x28, 0x43, 0x4f, 0x24, 0x99, 0xf2, 0x81, 0xea, 0x57, 0x3c,
+ 0x30, 0x5b, 0xe6, 0x8d, 0xe1, 0x8a, 0x37, 0x5c, 0x50, 0x3b,
+ 0x86, 0xed, 0x9e, 0xf5, 0x48, 0x23, 0x2f, 0x44, 0xf9, 0x92,
+ 0x1f, 0x74, 0xc9, 0xa2, 0xae, 0xc5, 0x78, 0x13, 0x60, 0x0b,
+ 0xb6, 0xdd, 0xd1, 0xba, 0x07, 0x6c, 0xdf, 0xb4, 0x09, 0x62,
+ 0x6e, 0x05, 0xb8, 0xd3, 0xa0, 0xcb, 0x76, 0x1d, 0x11, 0x7a,
+ 0xc7, 0xac, 0x21, 0x4a, 0xf7, 0x9c, 0x90, 0xfb, 0x46, 0x2d,
+ 0x5e, 0x35, 0x88, 0xe3, 0xef, 0x84, 0x39, 0x52, 0x3e, 0x55,
+ 0xe8, 0x83, 0x8f, 0xe4, 0x59, 0x32, 0x41, 0x2a, 0x97, 0xfc,
+ 0xf0, 0x9b, 0x26, 0x4d, 0xc0, 0xab, 0x16, 0x7d, 0x71, 0x1a,
+ 0xa7, 0xcc, 0xbf, 0xd4, 0x69, 0x02, 0x0e, 0x65, 0xd8, 0xb3,
+ 0xa3, 0xc8, 0x75, 0x1e, 0x12, 0x79, 0xc4, 0xaf, 0xdc, 0xb7,
+ 0x0a, 0x61, 0x6d, 0x06, 0xbb, 0xd0, 0x5d, 0x36, 0x8b, 0xe0,
+ 0xec, 0x87, 0x3a, 0x51, 0x22, 0x49, 0xf4, 0x9f, 0x93, 0xf8,
+ 0x45, 0x2e, 0x42, 0x29, 0x94, 0xff, 0xf3, 0x98, 0x25, 0x4e,
+ 0x3d, 0x56, 0xeb, 0x80, 0x8c, 0xe7, 0x5a, 0x31, 0xbc, 0xd7,
+ 0x6a, 0x01, 0x0d, 0x66, 0xdb, 0xb0, 0xc3, 0xa8, 0x15, 0x7e,
+ 0x72, 0x19, 0xa4, 0xcf, 0x7c, 0x17, 0xaa, 0xc1, 0xcd, 0xa6,
+ 0x1b, 0x70, 0x03, 0x68, 0xd5, 0xbe, 0xb2, 0xd9, 0x64, 0x0f,
+ 0x82, 0xe9, 0x54, 0x3f, 0x33, 0x58, 0xe5, 0x8e, 0xfd, 0x96,
+ 0x2b, 0x40, 0x4c, 0x27, 0x9a, 0xf1, 0x9d, 0xf6, 0x4b, 0x20,
+ 0x2c, 0x47, 0xfa, 0x91, 0xe2, 0x89, 0x34, 0x5f, 0x53, 0x38,
+ 0x85, 0xee, 0x63, 0x08, 0xb5, 0xde, 0xd2, 0xb9, 0x04, 0x6f,
+ 0x1c, 0x77, 0xca, 0xa1, 0xad, 0xc6, 0x7b, 0x10, 0x00, 0x6c,
+ 0xd8, 0xb4, 0xad, 0xc1, 0x75, 0x19, 0x47, 0x2b, 0x9f, 0xf3,
+ 0xea, 0x86, 0x32, 0x5e, 0x8e, 0xe2, 0x56, 0x3a, 0x23, 0x4f,
+ 0xfb, 0x97, 0xc9, 0xa5, 0x11, 0x7d, 0x64, 0x08, 0xbc, 0xd0,
+ 0x01, 0x6d, 0xd9, 0xb5, 0xac, 0xc0, 0x74, 0x18, 0x46, 0x2a,
+ 0x9e, 0xf2, 0xeb, 0x87, 0x33, 0x5f, 0x8f, 0xe3, 0x57, 0x3b,
+ 0x22, 0x4e, 0xfa, 0x96, 0xc8, 0xa4, 0x10, 0x7c, 0x65, 0x09,
+ 0xbd, 0xd1, 0x02, 0x6e, 0xda, 0xb6, 0xaf, 0xc3, 0x77, 0x1b,
+ 0x45, 0x29, 0x9d, 0xf1, 0xe8, 0x84, 0x30, 0x5c, 0x8c, 0xe0,
+ 0x54, 0x38, 0x21, 0x4d, 0xf9, 0x95, 0xcb, 0xa7, 0x13, 0x7f,
+ 0x66, 0x0a, 0xbe, 0xd2, 0x03, 0x6f, 0xdb, 0xb7, 0xae, 0xc2,
+ 0x76, 0x1a, 0x44, 0x28, 0x9c, 0xf0, 0xe9, 0x85, 0x31, 0x5d,
+ 0x8d, 0xe1, 0x55, 0x39, 0x20, 0x4c, 0xf8, 0x94, 0xca, 0xa6,
+ 0x12, 0x7e, 0x67, 0x0b, 0xbf, 0xd3, 0x04, 0x68, 0xdc, 0xb0,
+ 0xa9, 0xc5, 0x71, 0x1d, 0x43, 0x2f, 0x9b, 0xf7, 0xee, 0x82,
+ 0x36, 0x5a, 0x8a, 0xe6, 0x52, 0x3e, 0x27, 0x4b, 0xff, 0x93,
+ 0xcd, 0xa1, 0x15, 0x79, 0x60, 0x0c, 0xb8, 0xd4, 0x05, 0x69,
+ 0xdd, 0xb1, 0xa8, 0xc4, 0x70, 0x1c, 0x42, 0x2e, 0x9a, 0xf6,
+ 0xef, 0x83, 0x37, 0x5b, 0x8b, 0xe7, 0x53, 0x3f, 0x26, 0x4a,
+ 0xfe, 0x92, 0xcc, 0xa0, 0x14, 0x78, 0x61, 0x0d, 0xb9, 0xd5,
+ 0x06, 0x6a, 0xde, 0xb2, 0xab, 0xc7, 0x73, 0x1f, 0x41, 0x2d,
+ 0x99, 0xf5, 0xec, 0x80, 0x34, 0x58, 0x88, 0xe4, 0x50, 0x3c,
+ 0x25, 0x49, 0xfd, 0x91, 0xcf, 0xa3, 0x17, 0x7b, 0x62, 0x0e,
+ 0xba, 0xd6, 0x07, 0x6b, 0xdf, 0xb3, 0xaa, 0xc6, 0x72, 0x1e,
+ 0x40, 0x2c, 0x98, 0xf4, 0xed, 0x81, 0x35, 0x59, 0x89, 0xe5,
+ 0x51, 0x3d, 0x24, 0x48, 0xfc, 0x90, 0xce, 0xa2, 0x16, 0x7a,
+ 0x63, 0x0f, 0xbb, 0xd7, 0x00, 0x6d, 0xda, 0xb7, 0xa9, 0xc4,
+ 0x73, 0x1e, 0x4f, 0x22, 0x95, 0xf8, 0xe6, 0x8b, 0x3c, 0x51,
+ 0x9e, 0xf3, 0x44, 0x29, 0x37, 0x5a, 0xed, 0x80, 0xd1, 0xbc,
+ 0x0b, 0x66, 0x78, 0x15, 0xa2, 0xcf, 0x21, 0x4c, 0xfb, 0x96,
+ 0x88, 0xe5, 0x52, 0x3f, 0x6e, 0x03, 0xb4, 0xd9, 0xc7, 0xaa,
+ 0x1d, 0x70, 0xbf, 0xd2, 0x65, 0x08, 0x16, 0x7b, 0xcc, 0xa1,
+ 0xf0, 0x9d, 0x2a, 0x47, 0x59, 0x34, 0x83, 0xee, 0x42, 0x2f,
+ 0x98, 0xf5, 0xeb, 0x86, 0x31, 0x5c, 0x0d, 0x60, 0xd7, 0xba,
+ 0xa4, 0xc9, 0x7e, 0x13, 0xdc, 0xb1, 0x06, 0x6b, 0x75, 0x18,
+ 0xaf, 0xc2, 0x93, 0xfe, 0x49, 0x24, 0x3a, 0x57, 0xe0, 0x8d,
+ 0x63, 0x0e, 0xb9, 0xd4, 0xca, 0xa7, 0x10, 0x7d, 0x2c, 0x41,
+ 0xf6, 0x9b, 0x85, 0xe8, 0x5f, 0x32, 0xfd, 0x90, 0x27, 0x4a,
+ 0x54, 0x39, 0x8e, 0xe3, 0xb2, 0xdf, 0x68, 0x05, 0x1b, 0x76,
+ 0xc1, 0xac, 0x84, 0xe9, 0x5e, 0x33, 0x2d, 0x40, 0xf7, 0x9a,
+ 0xcb, 0xa6, 0x11, 0x7c, 0x62, 0x0f, 0xb8, 0xd5, 0x1a, 0x77,
+ 0xc0, 0xad, 0xb3, 0xde, 0x69, 0x04, 0x55, 0x38, 0x8f, 0xe2,
+ 0xfc, 0x91, 0x26, 0x4b, 0xa5, 0xc8, 0x7f, 0x12, 0x0c, 0x61,
+ 0xd6, 0xbb, 0xea, 0x87, 0x30, 0x5d, 0x43, 0x2e, 0x99, 0xf4,
+ 0x3b, 0x56, 0xe1, 0x8c, 0x92, 0xff, 0x48, 0x25, 0x74, 0x19,
+ 0xae, 0xc3, 0xdd, 0xb0, 0x07, 0x6a, 0xc6, 0xab, 0x1c, 0x71,
+ 0x6f, 0x02, 0xb5, 0xd8, 0x89, 0xe4, 0x53, 0x3e, 0x20, 0x4d,
+ 0xfa, 0x97, 0x58, 0x35, 0x82, 0xef, 0xf1, 0x9c, 0x2b, 0x46,
+ 0x17, 0x7a, 0xcd, 0xa0, 0xbe, 0xd3, 0x64, 0x09, 0xe7, 0x8a,
+ 0x3d, 0x50, 0x4e, 0x23, 0x94, 0xf9, 0xa8, 0xc5, 0x72, 0x1f,
+ 0x01, 0x6c, 0xdb, 0xb6, 0x79, 0x14, 0xa3, 0xce, 0xd0, 0xbd,
+ 0x0a, 0x67, 0x36, 0x5b, 0xec, 0x81, 0x9f, 0xf2, 0x45, 0x28,
+ 0x00, 0x6e, 0xdc, 0xb2, 0xa5, 0xcb, 0x79, 0x17, 0x57, 0x39,
+ 0x8b, 0xe5, 0xf2, 0x9c, 0x2e, 0x40, 0xae, 0xc0, 0x72, 0x1c,
+ 0x0b, 0x65, 0xd7, 0xb9, 0xf9, 0x97, 0x25, 0x4b, 0x5c, 0x32,
+ 0x80, 0xee, 0x41, 0x2f, 0x9d, 0xf3, 0xe4, 0x8a, 0x38, 0x56,
+ 0x16, 0x78, 0xca, 0xa4, 0xb3, 0xdd, 0x6f, 0x01, 0xef, 0x81,
+ 0x33, 0x5d, 0x4a, 0x24, 0x96, 0xf8, 0xb8, 0xd6, 0x64, 0x0a,
+ 0x1d, 0x73, 0xc1, 0xaf, 0x82, 0xec, 0x5e, 0x30, 0x27, 0x49,
+ 0xfb, 0x95, 0xd5, 0xbb, 0x09, 0x67, 0x70, 0x1e, 0xac, 0xc2,
+ 0x2c, 0x42, 0xf0, 0x9e, 0x89, 0xe7, 0x55, 0x3b, 0x7b, 0x15,
+ 0xa7, 0xc9, 0xde, 0xb0, 0x02, 0x6c, 0xc3, 0xad, 0x1f, 0x71,
+ 0x66, 0x08, 0xba, 0xd4, 0x94, 0xfa, 0x48, 0x26, 0x31, 0x5f,
+ 0xed, 0x83, 0x6d, 0x03, 0xb1, 0xdf, 0xc8, 0xa6, 0x14, 0x7a,
+ 0x3a, 0x54, 0xe6, 0x88, 0x9f, 0xf1, 0x43, 0x2d, 0x19, 0x77,
+ 0xc5, 0xab, 0xbc, 0xd2, 0x60, 0x0e, 0x4e, 0x20, 0x92, 0xfc,
+ 0xeb, 0x85, 0x37, 0x59, 0xb7, 0xd9, 0x6b, 0x05, 0x12, 0x7c,
+ 0xce, 0xa0, 0xe0, 0x8e, 0x3c, 0x52, 0x45, 0x2b, 0x99, 0xf7,
+ 0x58, 0x36, 0x84, 0xea, 0xfd, 0x93, 0x21, 0x4f, 0x0f, 0x61,
+ 0xd3, 0xbd, 0xaa, 0xc4, 0x76, 0x18, 0xf6, 0x98, 0x2a, 0x44,
+ 0x53, 0x3d, 0x8f, 0xe1, 0xa1, 0xcf, 0x7d, 0x13, 0x04, 0x6a,
+ 0xd8, 0xb6, 0x9b, 0xf5, 0x47, 0x29, 0x3e, 0x50, 0xe2, 0x8c,
+ 0xcc, 0xa2, 0x10, 0x7e, 0x69, 0x07, 0xb5, 0xdb, 0x35, 0x5b,
+ 0xe9, 0x87, 0x90, 0xfe, 0x4c, 0x22, 0x62, 0x0c, 0xbe, 0xd0,
+ 0xc7, 0xa9, 0x1b, 0x75, 0xda, 0xb4, 0x06, 0x68, 0x7f, 0x11,
+ 0xa3, 0xcd, 0x8d, 0xe3, 0x51, 0x3f, 0x28, 0x46, 0xf4, 0x9a,
+ 0x74, 0x1a, 0xa8, 0xc6, 0xd1, 0xbf, 0x0d, 0x63, 0x23, 0x4d,
+ 0xff, 0x91, 0x86, 0xe8, 0x5a, 0x34, 0x00, 0x6f, 0xde, 0xb1,
+ 0xa1, 0xce, 0x7f, 0x10, 0x5f, 0x30, 0x81, 0xee, 0xfe, 0x91,
+ 0x20, 0x4f, 0xbe, 0xd1, 0x60, 0x0f, 0x1f, 0x70, 0xc1, 0xae,
+ 0xe1, 0x8e, 0x3f, 0x50, 0x40, 0x2f, 0x9e, 0xf1, 0x61, 0x0e,
+ 0xbf, 0xd0, 0xc0, 0xaf, 0x1e, 0x71, 0x3e, 0x51, 0xe0, 0x8f,
+ 0x9f, 0xf0, 0x41, 0x2e, 0xdf, 0xb0, 0x01, 0x6e, 0x7e, 0x11,
+ 0xa0, 0xcf, 0x80, 0xef, 0x5e, 0x31, 0x21, 0x4e, 0xff, 0x90,
+ 0xc2, 0xad, 0x1c, 0x73, 0x63, 0x0c, 0xbd, 0xd2, 0x9d, 0xf2,
+ 0x43, 0x2c, 0x3c, 0x53, 0xe2, 0x8d, 0x7c, 0x13, 0xa2, 0xcd,
+ 0xdd, 0xb2, 0x03, 0x6c, 0x23, 0x4c, 0xfd, 0x92, 0x82, 0xed,
+ 0x5c, 0x33, 0xa3, 0xcc, 0x7d, 0x12, 0x02, 0x6d, 0xdc, 0xb3,
+ 0xfc, 0x93, 0x22, 0x4d, 0x5d, 0x32, 0x83, 0xec, 0x1d, 0x72,
+ 0xc3, 0xac, 0xbc, 0xd3, 0x62, 0x0d, 0x42, 0x2d, 0x9c, 0xf3,
+ 0xe3, 0x8c, 0x3d, 0x52, 0x99, 0xf6, 0x47, 0x28, 0x38, 0x57,
+ 0xe6, 0x89, 0xc6, 0xa9, 0x18, 0x77, 0x67, 0x08, 0xb9, 0xd6,
+ 0x27, 0x48, 0xf9, 0x96, 0x86, 0xe9, 0x58, 0x37, 0x78, 0x17,
+ 0xa6, 0xc9, 0xd9, 0xb6, 0x07, 0x68, 0xf8, 0x97, 0x26, 0x49,
+ 0x59, 0x36, 0x87, 0xe8, 0xa7, 0xc8, 0x79, 0x16, 0x06, 0x69,
+ 0xd8, 0xb7, 0x46, 0x29, 0x98, 0xf7, 0xe7, 0x88, 0x39, 0x56,
+ 0x19, 0x76, 0xc7, 0xa8, 0xb8, 0xd7, 0x66, 0x09, 0x5b, 0x34,
+ 0x85, 0xea, 0xfa, 0x95, 0x24, 0x4b, 0x04, 0x6b, 0xda, 0xb5,
+ 0xa5, 0xca, 0x7b, 0x14, 0xe5, 0x8a, 0x3b, 0x54, 0x44, 0x2b,
+ 0x9a, 0xf5, 0xba, 0xd5, 0x64, 0x0b, 0x1b, 0x74, 0xc5, 0xaa,
+ 0x3a, 0x55, 0xe4, 0x8b, 0x9b, 0xf4, 0x45, 0x2a, 0x65, 0x0a,
+ 0xbb, 0xd4, 0xc4, 0xab, 0x1a, 0x75, 0x84, 0xeb, 0x5a, 0x35,
+ 0x25, 0x4a, 0xfb, 0x94, 0xdb, 0xb4, 0x05, 0x6a, 0x7a, 0x15,
+ 0xa4, 0xcb, 0x00, 0x70, 0xe0, 0x90, 0xdd, 0xad, 0x3d, 0x4d,
+ 0xa7, 0xd7, 0x47, 0x37, 0x7a, 0x0a, 0x9a, 0xea, 0x53, 0x23,
+ 0xb3, 0xc3, 0x8e, 0xfe, 0x6e, 0x1e, 0xf4, 0x84, 0x14, 0x64,
+ 0x29, 0x59, 0xc9, 0xb9, 0xa6, 0xd6, 0x46, 0x36, 0x7b, 0x0b,
+ 0x9b, 0xeb, 0x01, 0x71, 0xe1, 0x91, 0xdc, 0xac, 0x3c, 0x4c,
+ 0xf5, 0x85, 0x15, 0x65, 0x28, 0x58, 0xc8, 0xb8, 0x52, 0x22,
+ 0xb2, 0xc2, 0x8f, 0xff, 0x6f, 0x1f, 0x51, 0x21, 0xb1, 0xc1,
+ 0x8c, 0xfc, 0x6c, 0x1c, 0xf6, 0x86, 0x16, 0x66, 0x2b, 0x5b,
+ 0xcb, 0xbb, 0x02, 0x72, 0xe2, 0x92, 0xdf, 0xaf, 0x3f, 0x4f,
+ 0xa5, 0xd5, 0x45, 0x35, 0x78, 0x08, 0x98, 0xe8, 0xf7, 0x87,
+ 0x17, 0x67, 0x2a, 0x5a, 0xca, 0xba, 0x50, 0x20, 0xb0, 0xc0,
+ 0x8d, 0xfd, 0x6d, 0x1d, 0xa4, 0xd4, 0x44, 0x34, 0x79, 0x09,
+ 0x99, 0xe9, 0x03, 0x73, 0xe3, 0x93, 0xde, 0xae, 0x3e, 0x4e,
+ 0xa2, 0xd2, 0x42, 0x32, 0x7f, 0x0f, 0x9f, 0xef, 0x05, 0x75,
+ 0xe5, 0x95, 0xd8, 0xa8, 0x38, 0x48, 0xf1, 0x81, 0x11, 0x61,
+ 0x2c, 0x5c, 0xcc, 0xbc, 0x56, 0x26, 0xb6, 0xc6, 0x8b, 0xfb,
+ 0x6b, 0x1b, 0x04, 0x74, 0xe4, 0x94, 0xd9, 0xa9, 0x39, 0x49,
+ 0xa3, 0xd3, 0x43, 0x33, 0x7e, 0x0e, 0x9e, 0xee, 0x57, 0x27,
+ 0xb7, 0xc7, 0x8a, 0xfa, 0x6a, 0x1a, 0xf0, 0x80, 0x10, 0x60,
+ 0x2d, 0x5d, 0xcd, 0xbd, 0xf3, 0x83, 0x13, 0x63, 0x2e, 0x5e,
+ 0xce, 0xbe, 0x54, 0x24, 0xb4, 0xc4, 0x89, 0xf9, 0x69, 0x19,
+ 0xa0, 0xd0, 0x40, 0x30, 0x7d, 0x0d, 0x9d, 0xed, 0x07, 0x77,
+ 0xe7, 0x97, 0xda, 0xaa, 0x3a, 0x4a, 0x55, 0x25, 0xb5, 0xc5,
+ 0x88, 0xf8, 0x68, 0x18, 0xf2, 0x82, 0x12, 0x62, 0x2f, 0x5f,
+ 0xcf, 0xbf, 0x06, 0x76, 0xe6, 0x96, 0xdb, 0xab, 0x3b, 0x4b,
+ 0xa1, 0xd1, 0x41, 0x31, 0x7c, 0x0c, 0x9c, 0xec, 0x00, 0x71,
+ 0xe2, 0x93, 0xd9, 0xa8, 0x3b, 0x4a, 0xaf, 0xde, 0x4d, 0x3c,
+ 0x76, 0x07, 0x94, 0xe5, 0x43, 0x32, 0xa1, 0xd0, 0x9a, 0xeb,
+ 0x78, 0x09, 0xec, 0x9d, 0x0e, 0x7f, 0x35, 0x44, 0xd7, 0xa6,
+ 0x86, 0xf7, 0x64, 0x15, 0x5f, 0x2e, 0xbd, 0xcc, 0x29, 0x58,
+ 0xcb, 0xba, 0xf0, 0x81, 0x12, 0x63, 0xc5, 0xb4, 0x27, 0x56,
+ 0x1c, 0x6d, 0xfe, 0x8f, 0x6a, 0x1b, 0x88, 0xf9, 0xb3, 0xc2,
+ 0x51, 0x20, 0x11, 0x60, 0xf3, 0x82, 0xc8, 0xb9, 0x2a, 0x5b,
+ 0xbe, 0xcf, 0x5c, 0x2d, 0x67, 0x16, 0x85, 0xf4, 0x52, 0x23,
+ 0xb0, 0xc1, 0x8b, 0xfa, 0x69, 0x18, 0xfd, 0x8c, 0x1f, 0x6e,
+ 0x24, 0x55, 0xc6, 0xb7, 0x97, 0xe6, 0x75, 0x04, 0x4e, 0x3f,
+ 0xac, 0xdd, 0x38, 0x49, 0xda, 0xab, 0xe1, 0x90, 0x03, 0x72,
+ 0xd4, 0xa5, 0x36, 0x47, 0x0d, 0x7c, 0xef, 0x9e, 0x7b, 0x0a,
+ 0x99, 0xe8, 0xa2, 0xd3, 0x40, 0x31, 0x22, 0x53, 0xc0, 0xb1,
+ 0xfb, 0x8a, 0x19, 0x68, 0x8d, 0xfc, 0x6f, 0x1e, 0x54, 0x25,
+ 0xb6, 0xc7, 0x61, 0x10, 0x83, 0xf2, 0xb8, 0xc9, 0x5a, 0x2b,
+ 0xce, 0xbf, 0x2c, 0x5d, 0x17, 0x66, 0xf5, 0x84, 0xa4, 0xd5,
+ 0x46, 0x37, 0x7d, 0x0c, 0x9f, 0xee, 0x0b, 0x7a, 0xe9, 0x98,
+ 0xd2, 0xa3, 0x30, 0x41, 0xe7, 0x96, 0x05, 0x74, 0x3e, 0x4f,
+ 0xdc, 0xad, 0x48, 0x39, 0xaa, 0xdb, 0x91, 0xe0, 0x73, 0x02,
+ 0x33, 0x42, 0xd1, 0xa0, 0xea, 0x9b, 0x08, 0x79, 0x9c, 0xed,
+ 0x7e, 0x0f, 0x45, 0x34, 0xa7, 0xd6, 0x70, 0x01, 0x92, 0xe3,
+ 0xa9, 0xd8, 0x4b, 0x3a, 0xdf, 0xae, 0x3d, 0x4c, 0x06, 0x77,
+ 0xe4, 0x95, 0xb5, 0xc4, 0x57, 0x26, 0x6c, 0x1d, 0x8e, 0xff,
+ 0x1a, 0x6b, 0xf8, 0x89, 0xc3, 0xb2, 0x21, 0x50, 0xf6, 0x87,
+ 0x14, 0x65, 0x2f, 0x5e, 0xcd, 0xbc, 0x59, 0x28, 0xbb, 0xca,
+ 0x80, 0xf1, 0x62, 0x13, 0x00, 0x72, 0xe4, 0x96, 0xd5, 0xa7,
+ 0x31, 0x43, 0xb7, 0xc5, 0x53, 0x21, 0x62, 0x10, 0x86, 0xf4,
+ 0x73, 0x01, 0x97, 0xe5, 0xa6, 0xd4, 0x42, 0x30, 0xc4, 0xb6,
+ 0x20, 0x52, 0x11, 0x63, 0xf5, 0x87, 0xe6, 0x94, 0x02, 0x70,
+ 0x33, 0x41, 0xd7, 0xa5, 0x51, 0x23, 0xb5, 0xc7, 0x84, 0xf6,
+ 0x60, 0x12, 0x95, 0xe7, 0x71, 0x03, 0x40, 0x32, 0xa4, 0xd6,
+ 0x22, 0x50, 0xc6, 0xb4, 0xf7, 0x85, 0x13, 0x61, 0xd1, 0xa3,
+ 0x35, 0x47, 0x04, 0x76, 0xe0, 0x92, 0x66, 0x14, 0x82, 0xf0,
+ 0xb3, 0xc1, 0x57, 0x25, 0xa2, 0xd0, 0x46, 0x34, 0x77, 0x05,
+ 0x93, 0xe1, 0x15, 0x67, 0xf1, 0x83, 0xc0, 0xb2, 0x24, 0x56,
+ 0x37, 0x45, 0xd3, 0xa1, 0xe2, 0x90, 0x06, 0x74, 0x80, 0xf2,
+ 0x64, 0x16, 0x55, 0x27, 0xb1, 0xc3, 0x44, 0x36, 0xa0, 0xd2,
+ 0x91, 0xe3, 0x75, 0x07, 0xf3, 0x81, 0x17, 0x65, 0x26, 0x54,
+ 0xc2, 0xb0, 0xbf, 0xcd, 0x5b, 0x29, 0x6a, 0x18, 0x8e, 0xfc,
+ 0x08, 0x7a, 0xec, 0x9e, 0xdd, 0xaf, 0x39, 0x4b, 0xcc, 0xbe,
+ 0x28, 0x5a, 0x19, 0x6b, 0xfd, 0x8f, 0x7b, 0x09, 0x9f, 0xed,
+ 0xae, 0xdc, 0x4a, 0x38, 0x59, 0x2b, 0xbd, 0xcf, 0x8c, 0xfe,
+ 0x68, 0x1a, 0xee, 0x9c, 0x0a, 0x78, 0x3b, 0x49, 0xdf, 0xad,
+ 0x2a, 0x58, 0xce, 0xbc, 0xff, 0x8d, 0x1b, 0x69, 0x9d, 0xef,
+ 0x79, 0x0b, 0x48, 0x3a, 0xac, 0xde, 0x6e, 0x1c, 0x8a, 0xf8,
+ 0xbb, 0xc9, 0x5f, 0x2d, 0xd9, 0xab, 0x3d, 0x4f, 0x0c, 0x7e,
+ 0xe8, 0x9a, 0x1d, 0x6f, 0xf9, 0x8b, 0xc8, 0xba, 0x2c, 0x5e,
+ 0xaa, 0xd8, 0x4e, 0x3c, 0x7f, 0x0d, 0x9b, 0xe9, 0x88, 0xfa,
+ 0x6c, 0x1e, 0x5d, 0x2f, 0xb9, 0xcb, 0x3f, 0x4d, 0xdb, 0xa9,
+ 0xea, 0x98, 0x0e, 0x7c, 0xfb, 0x89, 0x1f, 0x6d, 0x2e, 0x5c,
+ 0xca, 0xb8, 0x4c, 0x3e, 0xa8, 0xda, 0x99, 0xeb, 0x7d, 0x0f,
+ 0x00, 0x73, 0xe6, 0x95, 0xd1, 0xa2, 0x37, 0x44, 0xbf, 0xcc,
+ 0x59, 0x2a, 0x6e, 0x1d, 0x88, 0xfb, 0x63, 0x10, 0x85, 0xf6,
+ 0xb2, 0xc1, 0x54, 0x27, 0xdc, 0xaf, 0x3a, 0x49, 0x0d, 0x7e,
+ 0xeb, 0x98, 0xc6, 0xb5, 0x20, 0x53, 0x17, 0x64, 0xf1, 0x82,
+ 0x79, 0x0a, 0x9f, 0xec, 0xa8, 0xdb, 0x4e, 0x3d, 0xa5, 0xd6,
+ 0x43, 0x30, 0x74, 0x07, 0x92, 0xe1, 0x1a, 0x69, 0xfc, 0x8f,
+ 0xcb, 0xb8, 0x2d, 0x5e, 0x91, 0xe2, 0x77, 0x04, 0x40, 0x33,
+ 0xa6, 0xd5, 0x2e, 0x5d, 0xc8, 0xbb, 0xff, 0x8c, 0x19, 0x6a,
+ 0xf2, 0x81, 0x14, 0x67, 0x23, 0x50, 0xc5, 0xb6, 0x4d, 0x3e,
+ 0xab, 0xd8, 0x9c, 0xef, 0x7a, 0x09, 0x57, 0x24, 0xb1, 0xc2,
+ 0x86, 0xf5, 0x60, 0x13, 0xe8, 0x9b, 0x0e, 0x7d, 0x39, 0x4a,
+ 0xdf, 0xac, 0x34, 0x47, 0xd2, 0xa1, 0xe5, 0x96, 0x03, 0x70,
+ 0x8b, 0xf8, 0x6d, 0x1e, 0x5a, 0x29, 0xbc, 0xcf, 0x3f, 0x4c,
+ 0xd9, 0xaa, 0xee, 0x9d, 0x08, 0x7b, 0x80, 0xf3, 0x66, 0x15,
+ 0x51, 0x22, 0xb7, 0xc4, 0x5c, 0x2f, 0xba, 0xc9, 0x8d, 0xfe,
+ 0x6b, 0x18, 0xe3, 0x90, 0x05, 0x76, 0x32, 0x41, 0xd4, 0xa7,
+ 0xf9, 0x8a, 0x1f, 0x6c, 0x28, 0x5b, 0xce, 0xbd, 0x46, 0x35,
+ 0xa0, 0xd3, 0x97, 0xe4, 0x71, 0x02, 0x9a, 0xe9, 0x7c, 0x0f,
+ 0x4b, 0x38, 0xad, 0xde, 0x25, 0x56, 0xc3, 0xb0, 0xf4, 0x87,
+ 0x12, 0x61, 0xae, 0xdd, 0x48, 0x3b, 0x7f, 0x0c, 0x99, 0xea,
+ 0x11, 0x62, 0xf7, 0x84, 0xc0, 0xb3, 0x26, 0x55, 0xcd, 0xbe,
+ 0x2b, 0x58, 0x1c, 0x6f, 0xfa, 0x89, 0x72, 0x01, 0x94, 0xe7,
+ 0xa3, 0xd0, 0x45, 0x36, 0x68, 0x1b, 0x8e, 0xfd, 0xb9, 0xca,
+ 0x5f, 0x2c, 0xd7, 0xa4, 0x31, 0x42, 0x06, 0x75, 0xe0, 0x93,
+ 0x0b, 0x78, 0xed, 0x9e, 0xda, 0xa9, 0x3c, 0x4f, 0xb4, 0xc7,
+ 0x52, 0x21, 0x65, 0x16, 0x83, 0xf0, 0x00, 0x74, 0xe8, 0x9c,
+ 0xcd, 0xb9, 0x25, 0x51, 0x87, 0xf3, 0x6f, 0x1b, 0x4a, 0x3e,
+ 0xa2, 0xd6, 0x13, 0x67, 0xfb, 0x8f, 0xde, 0xaa, 0x36, 0x42,
+ 0x94, 0xe0, 0x7c, 0x08, 0x59, 0x2d, 0xb1, 0xc5, 0x26, 0x52,
+ 0xce, 0xba, 0xeb, 0x9f, 0x03, 0x77, 0xa1, 0xd5, 0x49, 0x3d,
+ 0x6c, 0x18, 0x84, 0xf0, 0x35, 0x41, 0xdd, 0xa9, 0xf8, 0x8c,
+ 0x10, 0x64, 0xb2, 0xc6, 0x5a, 0x2e, 0x7f, 0x0b, 0x97, 0xe3,
+ 0x4c, 0x38, 0xa4, 0xd0, 0x81, 0xf5, 0x69, 0x1d, 0xcb, 0xbf,
+ 0x23, 0x57, 0x06, 0x72, 0xee, 0x9a, 0x5f, 0x2b, 0xb7, 0xc3,
+ 0x92, 0xe6, 0x7a, 0x0e, 0xd8, 0xac, 0x30, 0x44, 0x15, 0x61,
+ 0xfd, 0x89, 0x6a, 0x1e, 0x82, 0xf6, 0xa7, 0xd3, 0x4f, 0x3b,
+ 0xed, 0x99, 0x05, 0x71, 0x20, 0x54, 0xc8, 0xbc, 0x79, 0x0d,
+ 0x91, 0xe5, 0xb4, 0xc0, 0x5c, 0x28, 0xfe, 0x8a, 0x16, 0x62,
+ 0x33, 0x47, 0xdb, 0xaf, 0x98, 0xec, 0x70, 0x04, 0x55, 0x21,
+ 0xbd, 0xc9, 0x1f, 0x6b, 0xf7, 0x83, 0xd2, 0xa6, 0x3a, 0x4e,
+ 0x8b, 0xff, 0x63, 0x17, 0x46, 0x32, 0xae, 0xda, 0x0c, 0x78,
+ 0xe4, 0x90, 0xc1, 0xb5, 0x29, 0x5d, 0xbe, 0xca, 0x56, 0x22,
+ 0x73, 0x07, 0x9b, 0xef, 0x39, 0x4d, 0xd1, 0xa5, 0xf4, 0x80,
+ 0x1c, 0x68, 0xad, 0xd9, 0x45, 0x31, 0x60, 0x14, 0x88, 0xfc,
+ 0x2a, 0x5e, 0xc2, 0xb6, 0xe7, 0x93, 0x0f, 0x7b, 0xd4, 0xa0,
+ 0x3c, 0x48, 0x19, 0x6d, 0xf1, 0x85, 0x53, 0x27, 0xbb, 0xcf,
+ 0x9e, 0xea, 0x76, 0x02, 0xc7, 0xb3, 0x2f, 0x5b, 0x0a, 0x7e,
+ 0xe2, 0x96, 0x40, 0x34, 0xa8, 0xdc, 0x8d, 0xf9, 0x65, 0x11,
+ 0xf2, 0x86, 0x1a, 0x6e, 0x3f, 0x4b, 0xd7, 0xa3, 0x75, 0x01,
+ 0x9d, 0xe9, 0xb8, 0xcc, 0x50, 0x24, 0xe1, 0x95, 0x09, 0x7d,
+ 0x2c, 0x58, 0xc4, 0xb0, 0x66, 0x12, 0x8e, 0xfa, 0xab, 0xdf,
+ 0x43, 0x37, 0x00, 0x75, 0xea, 0x9f, 0xc9, 0xbc, 0x23, 0x56,
+ 0x8f, 0xfa, 0x65, 0x10, 0x46, 0x33, 0xac, 0xd9, 0x03, 0x76,
+ 0xe9, 0x9c, 0xca, 0xbf, 0x20, 0x55, 0x8c, 0xf9, 0x66, 0x13,
+ 0x45, 0x30, 0xaf, 0xda, 0x06, 0x73, 0xec, 0x99, 0xcf, 0xba,
+ 0x25, 0x50, 0x89, 0xfc, 0x63, 0x16, 0x40, 0x35, 0xaa, 0xdf,
+ 0x05, 0x70, 0xef, 0x9a, 0xcc, 0xb9, 0x26, 0x53, 0x8a, 0xff,
+ 0x60, 0x15, 0x43, 0x36, 0xa9, 0xdc, 0x0c, 0x79, 0xe6, 0x93,
+ 0xc5, 0xb0, 0x2f, 0x5a, 0x83, 0xf6, 0x69, 0x1c, 0x4a, 0x3f,
+ 0xa0, 0xd5, 0x0f, 0x7a, 0xe5, 0x90, 0xc6, 0xb3, 0x2c, 0x59,
+ 0x80, 0xf5, 0x6a, 0x1f, 0x49, 0x3c, 0xa3, 0xd6, 0x0a, 0x7f,
+ 0xe0, 0x95, 0xc3, 0xb6, 0x29, 0x5c, 0x85, 0xf0, 0x6f, 0x1a,
+ 0x4c, 0x39, 0xa6, 0xd3, 0x09, 0x7c, 0xe3, 0x96, 0xc0, 0xb5,
+ 0x2a, 0x5f, 0x86, 0xf3, 0x6c, 0x19, 0x4f, 0x3a, 0xa5, 0xd0,
+ 0x18, 0x6d, 0xf2, 0x87, 0xd1, 0xa4, 0x3b, 0x4e, 0x97, 0xe2,
+ 0x7d, 0x08, 0x5e, 0x2b, 0xb4, 0xc1, 0x1b, 0x6e, 0xf1, 0x84,
+ 0xd2, 0xa7, 0x38, 0x4d, 0x94, 0xe1, 0x7e, 0x0b, 0x5d, 0x28,
+ 0xb7, 0xc2, 0x1e, 0x6b, 0xf4, 0x81, 0xd7, 0xa2, 0x3d, 0x48,
+ 0x91, 0xe4, 0x7b, 0x0e, 0x58, 0x2d, 0xb2, 0xc7, 0x1d, 0x68,
+ 0xf7, 0x82, 0xd4, 0xa1, 0x3e, 0x4b, 0x92, 0xe7, 0x78, 0x0d,
+ 0x5b, 0x2e, 0xb1, 0xc4, 0x14, 0x61, 0xfe, 0x8b, 0xdd, 0xa8,
+ 0x37, 0x42, 0x9b, 0xee, 0x71, 0x04, 0x52, 0x27, 0xb8, 0xcd,
+ 0x17, 0x62, 0xfd, 0x88, 0xde, 0xab, 0x34, 0x41, 0x98, 0xed,
+ 0x72, 0x07, 0x51, 0x24, 0xbb, 0xce, 0x12, 0x67, 0xf8, 0x8d,
+ 0xdb, 0xae, 0x31, 0x44, 0x9d, 0xe8, 0x77, 0x02, 0x54, 0x21,
+ 0xbe, 0xcb, 0x11, 0x64, 0xfb, 0x8e, 0xd8, 0xad, 0x32, 0x47,
+ 0x9e, 0xeb, 0x74, 0x01, 0x57, 0x22, 0xbd, 0xc8, 0x00, 0x76,
+ 0xec, 0x9a, 0xc5, 0xb3, 0x29, 0x5f, 0x97, 0xe1, 0x7b, 0x0d,
+ 0x52, 0x24, 0xbe, 0xc8, 0x33, 0x45, 0xdf, 0xa9, 0xf6, 0x80,
+ 0x1a, 0x6c, 0xa4, 0xd2, 0x48, 0x3e, 0x61, 0x17, 0x8d, 0xfb,
+ 0x66, 0x10, 0x8a, 0xfc, 0xa3, 0xd5, 0x4f, 0x39, 0xf1, 0x87,
+ 0x1d, 0x6b, 0x34, 0x42, 0xd8, 0xae, 0x55, 0x23, 0xb9, 0xcf,
+ 0x90, 0xe6, 0x7c, 0x0a, 0xc2, 0xb4, 0x2e, 0x58, 0x07, 0x71,
+ 0xeb, 0x9d, 0xcc, 0xba, 0x20, 0x56, 0x09, 0x7f, 0xe5, 0x93,
+ 0x5b, 0x2d, 0xb7, 0xc1, 0x9e, 0xe8, 0x72, 0x04, 0xff, 0x89,
+ 0x13, 0x65, 0x3a, 0x4c, 0xd6, 0xa0, 0x68, 0x1e, 0x84, 0xf2,
+ 0xad, 0xdb, 0x41, 0x37, 0xaa, 0xdc, 0x46, 0x30, 0x6f, 0x19,
+ 0x83, 0xf5, 0x3d, 0x4b, 0xd1, 0xa7, 0xf8, 0x8e, 0x14, 0x62,
+ 0x99, 0xef, 0x75, 0x03, 0x5c, 0x2a, 0xb0, 0xc6, 0x0e, 0x78,
+ 0xe2, 0x94, 0xcb, 0xbd, 0x27, 0x51, 0x85, 0xf3, 0x69, 0x1f,
+ 0x40, 0x36, 0xac, 0xda, 0x12, 0x64, 0xfe, 0x88, 0xd7, 0xa1,
+ 0x3b, 0x4d, 0xb6, 0xc0, 0x5a, 0x2c, 0x73, 0x05, 0x9f, 0xe9,
+ 0x21, 0x57, 0xcd, 0xbb, 0xe4, 0x92, 0x08, 0x7e, 0xe3, 0x95,
+ 0x0f, 0x79, 0x26, 0x50, 0xca, 0xbc, 0x74, 0x02, 0x98, 0xee,
+ 0xb1, 0xc7, 0x5d, 0x2b, 0xd0, 0xa6, 0x3c, 0x4a, 0x15, 0x63,
+ 0xf9, 0x8f, 0x47, 0x31, 0xab, 0xdd, 0x82, 0xf4, 0x6e, 0x18,
+ 0x49, 0x3f, 0xa5, 0xd3, 0x8c, 0xfa, 0x60, 0x16, 0xde, 0xa8,
+ 0x32, 0x44, 0x1b, 0x6d, 0xf7, 0x81, 0x7a, 0x0c, 0x96, 0xe0,
+ 0xbf, 0xc9, 0x53, 0x25, 0xed, 0x9b, 0x01, 0x77, 0x28, 0x5e,
+ 0xc4, 0xb2, 0x2f, 0x59, 0xc3, 0xb5, 0xea, 0x9c, 0x06, 0x70,
+ 0xb8, 0xce, 0x54, 0x22, 0x7d, 0x0b, 0x91, 0xe7, 0x1c, 0x6a,
+ 0xf0, 0x86, 0xd9, 0xaf, 0x35, 0x43, 0x8b, 0xfd, 0x67, 0x11,
+ 0x4e, 0x38, 0xa2, 0xd4, 0x00, 0x77, 0xee, 0x99, 0xc1, 0xb6,
+ 0x2f, 0x58, 0x9f, 0xe8, 0x71, 0x06, 0x5e, 0x29, 0xb0, 0xc7,
+ 0x23, 0x54, 0xcd, 0xba, 0xe2, 0x95, 0x0c, 0x7b, 0xbc, 0xcb,
+ 0x52, 0x25, 0x7d, 0x0a, 0x93, 0xe4, 0x46, 0x31, 0xa8, 0xdf,
+ 0x87, 0xf0, 0x69, 0x1e, 0xd9, 0xae, 0x37, 0x40, 0x18, 0x6f,
+ 0xf6, 0x81, 0x65, 0x12, 0x8b, 0xfc, 0xa4, 0xd3, 0x4a, 0x3d,
+ 0xfa, 0x8d, 0x14, 0x63, 0x3b, 0x4c, 0xd5, 0xa2, 0x8c, 0xfb,
+ 0x62, 0x15, 0x4d, 0x3a, 0xa3, 0xd4, 0x13, 0x64, 0xfd, 0x8a,
+ 0xd2, 0xa5, 0x3c, 0x4b, 0xaf, 0xd8, 0x41, 0x36, 0x6e, 0x19,
+ 0x80, 0xf7, 0x30, 0x47, 0xde, 0xa9, 0xf1, 0x86, 0x1f, 0x68,
+ 0xca, 0xbd, 0x24, 0x53, 0x0b, 0x7c, 0xe5, 0x92, 0x55, 0x22,
+ 0xbb, 0xcc, 0x94, 0xe3, 0x7a, 0x0d, 0xe9, 0x9e, 0x07, 0x70,
+ 0x28, 0x5f, 0xc6, 0xb1, 0x76, 0x01, 0x98, 0xef, 0xb7, 0xc0,
+ 0x59, 0x2e, 0x05, 0x72, 0xeb, 0x9c, 0xc4, 0xb3, 0x2a, 0x5d,
+ 0x9a, 0xed, 0x74, 0x03, 0x5b, 0x2c, 0xb5, 0xc2, 0x26, 0x51,
+ 0xc8, 0xbf, 0xe7, 0x90, 0x09, 0x7e, 0xb9, 0xce, 0x57, 0x20,
+ 0x78, 0x0f, 0x96, 0xe1, 0x43, 0x34, 0xad, 0xda, 0x82, 0xf5,
+ 0x6c, 0x1b, 0xdc, 0xab, 0x32, 0x45, 0x1d, 0x6a, 0xf3, 0x84,
+ 0x60, 0x17, 0x8e, 0xf9, 0xa1, 0xd6, 0x4f, 0x38, 0xff, 0x88,
+ 0x11, 0x66, 0x3e, 0x49, 0xd0, 0xa7, 0x89, 0xfe, 0x67, 0x10,
+ 0x48, 0x3f, 0xa6, 0xd1, 0x16, 0x61, 0xf8, 0x8f, 0xd7, 0xa0,
+ 0x39, 0x4e, 0xaa, 0xdd, 0x44, 0x33, 0x6b, 0x1c, 0x85, 0xf2,
+ 0x35, 0x42, 0xdb, 0xac, 0xf4, 0x83, 0x1a, 0x6d, 0xcf, 0xb8,
+ 0x21, 0x56, 0x0e, 0x79, 0xe0, 0x97, 0x50, 0x27, 0xbe, 0xc9,
+ 0x91, 0xe6, 0x7f, 0x08, 0xec, 0x9b, 0x02, 0x75, 0x2d, 0x5a,
+ 0xc3, 0xb4, 0x73, 0x04, 0x9d, 0xea, 0xb2, 0xc5, 0x5c, 0x2b,
+ 0x00, 0x78, 0xf0, 0x88, 0xfd, 0x85, 0x0d, 0x75, 0xe7, 0x9f,
+ 0x17, 0x6f, 0x1a, 0x62, 0xea, 0x92, 0xd3, 0xab, 0x23, 0x5b,
+ 0x2e, 0x56, 0xde, 0xa6, 0x34, 0x4c, 0xc4, 0xbc, 0xc9, 0xb1,
+ 0x39, 0x41, 0xbb, 0xc3, 0x4b, 0x33, 0x46, 0x3e, 0xb6, 0xce,
+ 0x5c, 0x24, 0xac, 0xd4, 0xa1, 0xd9, 0x51, 0x29, 0x68, 0x10,
+ 0x98, 0xe0, 0x95, 0xed, 0x65, 0x1d, 0x8f, 0xf7, 0x7f, 0x07,
+ 0x72, 0x0a, 0x82, 0xfa, 0x6b, 0x13, 0x9b, 0xe3, 0x96, 0xee,
+ 0x66, 0x1e, 0x8c, 0xf4, 0x7c, 0x04, 0x71, 0x09, 0x81, 0xf9,
+ 0xb8, 0xc0, 0x48, 0x30, 0x45, 0x3d, 0xb5, 0xcd, 0x5f, 0x27,
+ 0xaf, 0xd7, 0xa2, 0xda, 0x52, 0x2a, 0xd0, 0xa8, 0x20, 0x58,
+ 0x2d, 0x55, 0xdd, 0xa5, 0x37, 0x4f, 0xc7, 0xbf, 0xca, 0xb2,
+ 0x3a, 0x42, 0x03, 0x7b, 0xf3, 0x8b, 0xfe, 0x86, 0x0e, 0x76,
+ 0xe4, 0x9c, 0x14, 0x6c, 0x19, 0x61, 0xe9, 0x91, 0xd6, 0xae,
+ 0x26, 0x5e, 0x2b, 0x53, 0xdb, 0xa3, 0x31, 0x49, 0xc1, 0xb9,
+ 0xcc, 0xb4, 0x3c, 0x44, 0x05, 0x7d, 0xf5, 0x8d, 0xf8, 0x80,
+ 0x08, 0x70, 0xe2, 0x9a, 0x12, 0x6a, 0x1f, 0x67, 0xef, 0x97,
+ 0x6d, 0x15, 0x9d, 0xe5, 0x90, 0xe8, 0x60, 0x18, 0x8a, 0xf2,
+ 0x7a, 0x02, 0x77, 0x0f, 0x87, 0xff, 0xbe, 0xc6, 0x4e, 0x36,
+ 0x43, 0x3b, 0xb3, 0xcb, 0x59, 0x21, 0xa9, 0xd1, 0xa4, 0xdc,
+ 0x54, 0x2c, 0xbd, 0xc5, 0x4d, 0x35, 0x40, 0x38, 0xb0, 0xc8,
+ 0x5a, 0x22, 0xaa, 0xd2, 0xa7, 0xdf, 0x57, 0x2f, 0x6e, 0x16,
+ 0x9e, 0xe6, 0x93, 0xeb, 0x63, 0x1b, 0x89, 0xf1, 0x79, 0x01,
+ 0x74, 0x0c, 0x84, 0xfc, 0x06, 0x7e, 0xf6, 0x8e, 0xfb, 0x83,
+ 0x0b, 0x73, 0xe1, 0x99, 0x11, 0x69, 0x1c, 0x64, 0xec, 0x94,
+ 0xd5, 0xad, 0x25, 0x5d, 0x28, 0x50, 0xd8, 0xa0, 0x32, 0x4a,
+ 0xc2, 0xba, 0xcf, 0xb7, 0x3f, 0x47, 0x00, 0x79, 0xf2, 0x8b,
+ 0xf9, 0x80, 0x0b, 0x72, 0xef, 0x96, 0x1d, 0x64, 0x16, 0x6f,
+ 0xe4, 0x9d, 0xc3, 0xba, 0x31, 0x48, 0x3a, 0x43, 0xc8, 0xb1,
+ 0x2c, 0x55, 0xde, 0xa7, 0xd5, 0xac, 0x27, 0x5e, 0x9b, 0xe2,
+ 0x69, 0x10, 0x62, 0x1b, 0x90, 0xe9, 0x74, 0x0d, 0x86, 0xff,
+ 0x8d, 0xf4, 0x7f, 0x06, 0x58, 0x21, 0xaa, 0xd3, 0xa1, 0xd8,
+ 0x53, 0x2a, 0xb7, 0xce, 0x45, 0x3c, 0x4e, 0x37, 0xbc, 0xc5,
+ 0x2b, 0x52, 0xd9, 0xa0, 0xd2, 0xab, 0x20, 0x59, 0xc4, 0xbd,
+ 0x36, 0x4f, 0x3d, 0x44, 0xcf, 0xb6, 0xe8, 0x91, 0x1a, 0x63,
+ 0x11, 0x68, 0xe3, 0x9a, 0x07, 0x7e, 0xf5, 0x8c, 0xfe, 0x87,
+ 0x0c, 0x75, 0xb0, 0xc9, 0x42, 0x3b, 0x49, 0x30, 0xbb, 0xc2,
+ 0x5f, 0x26, 0xad, 0xd4, 0xa6, 0xdf, 0x54, 0x2d, 0x73, 0x0a,
+ 0x81, 0xf8, 0x8a, 0xf3, 0x78, 0x01, 0x9c, 0xe5, 0x6e, 0x17,
+ 0x65, 0x1c, 0x97, 0xee, 0x56, 0x2f, 0xa4, 0xdd, 0xaf, 0xd6,
+ 0x5d, 0x24, 0xb9, 0xc0, 0x4b, 0x32, 0x40, 0x39, 0xb2, 0xcb,
+ 0x95, 0xec, 0x67, 0x1e, 0x6c, 0x15, 0x9e, 0xe7, 0x7a, 0x03,
+ 0x88, 0xf1, 0x83, 0xfa, 0x71, 0x08, 0xcd, 0xb4, 0x3f, 0x46,
+ 0x34, 0x4d, 0xc6, 0xbf, 0x22, 0x5b, 0xd0, 0xa9, 0xdb, 0xa2,
+ 0x29, 0x50, 0x0e, 0x77, 0xfc, 0x85, 0xf7, 0x8e, 0x05, 0x7c,
+ 0xe1, 0x98, 0x13, 0x6a, 0x18, 0x61, 0xea, 0x93, 0x7d, 0x04,
+ 0x8f, 0xf6, 0x84, 0xfd, 0x76, 0x0f, 0x92, 0xeb, 0x60, 0x19,
+ 0x6b, 0x12, 0x99, 0xe0, 0xbe, 0xc7, 0x4c, 0x35, 0x47, 0x3e,
+ 0xb5, 0xcc, 0x51, 0x28, 0xa3, 0xda, 0xa8, 0xd1, 0x5a, 0x23,
+ 0xe6, 0x9f, 0x14, 0x6d, 0x1f, 0x66, 0xed, 0x94, 0x09, 0x70,
+ 0xfb, 0x82, 0xf0, 0x89, 0x02, 0x7b, 0x25, 0x5c, 0xd7, 0xae,
+ 0xdc, 0xa5, 0x2e, 0x57, 0xca, 0xb3, 0x38, 0x41, 0x33, 0x4a,
+ 0xc1, 0xb8, 0x00, 0x7a, 0xf4, 0x8e, 0xf5, 0x8f, 0x01, 0x7b,
+ 0xf7, 0x8d, 0x03, 0x79, 0x02, 0x78, 0xf6, 0x8c, 0xf3, 0x89,
+ 0x07, 0x7d, 0x06, 0x7c, 0xf2, 0x88, 0x04, 0x7e, 0xf0, 0x8a,
+ 0xf1, 0x8b, 0x05, 0x7f, 0xfb, 0x81, 0x0f, 0x75, 0x0e, 0x74,
+ 0xfa, 0x80, 0x0c, 0x76, 0xf8, 0x82, 0xf9, 0x83, 0x0d, 0x77,
+ 0x08, 0x72, 0xfc, 0x86, 0xfd, 0x87, 0x09, 0x73, 0xff, 0x85,
+ 0x0b, 0x71, 0x0a, 0x70, 0xfe, 0x84, 0xeb, 0x91, 0x1f, 0x65,
+ 0x1e, 0x64, 0xea, 0x90, 0x1c, 0x66, 0xe8, 0x92, 0xe9, 0x93,
+ 0x1d, 0x67, 0x18, 0x62, 0xec, 0x96, 0xed, 0x97, 0x19, 0x63,
+ 0xef, 0x95, 0x1b, 0x61, 0x1a, 0x60, 0xee, 0x94, 0x10, 0x6a,
+ 0xe4, 0x9e, 0xe5, 0x9f, 0x11, 0x6b, 0xe7, 0x9d, 0x13, 0x69,
+ 0x12, 0x68, 0xe6, 0x9c, 0xe3, 0x99, 0x17, 0x6d, 0x16, 0x6c,
+ 0xe2, 0x98, 0x14, 0x6e, 0xe0, 0x9a, 0xe1, 0x9b, 0x15, 0x6f,
+ 0xcb, 0xb1, 0x3f, 0x45, 0x3e, 0x44, 0xca, 0xb0, 0x3c, 0x46,
+ 0xc8, 0xb2, 0xc9, 0xb3, 0x3d, 0x47, 0x38, 0x42, 0xcc, 0xb6,
+ 0xcd, 0xb7, 0x39, 0x43, 0xcf, 0xb5, 0x3b, 0x41, 0x3a, 0x40,
+ 0xce, 0xb4, 0x30, 0x4a, 0xc4, 0xbe, 0xc5, 0xbf, 0x31, 0x4b,
+ 0xc7, 0xbd, 0x33, 0x49, 0x32, 0x48, 0xc6, 0xbc, 0xc3, 0xb9,
+ 0x37, 0x4d, 0x36, 0x4c, 0xc2, 0xb8, 0x34, 0x4e, 0xc0, 0xba,
+ 0xc1, 0xbb, 0x35, 0x4f, 0x20, 0x5a, 0xd4, 0xae, 0xd5, 0xaf,
+ 0x21, 0x5b, 0xd7, 0xad, 0x23, 0x59, 0x22, 0x58, 0xd6, 0xac,
+ 0xd3, 0xa9, 0x27, 0x5d, 0x26, 0x5c, 0xd2, 0xa8, 0x24, 0x5e,
+ 0xd0, 0xaa, 0xd1, 0xab, 0x25, 0x5f, 0xdb, 0xa1, 0x2f, 0x55,
+ 0x2e, 0x54, 0xda, 0xa0, 0x2c, 0x56, 0xd8, 0xa2, 0xd9, 0xa3,
+ 0x2d, 0x57, 0x28, 0x52, 0xdc, 0xa6, 0xdd, 0xa7, 0x29, 0x53,
+ 0xdf, 0xa5, 0x2b, 0x51, 0x2a, 0x50, 0xde, 0xa4, 0x00, 0x7b,
+ 0xf6, 0x8d, 0xf1, 0x8a, 0x07, 0x7c, 0xff, 0x84, 0x09, 0x72,
+ 0x0e, 0x75, 0xf8, 0x83, 0xe3, 0x98, 0x15, 0x6e, 0x12, 0x69,
+ 0xe4, 0x9f, 0x1c, 0x67, 0xea, 0x91, 0xed, 0x96, 0x1b, 0x60,
+ 0xdb, 0xa0, 0x2d, 0x56, 0x2a, 0x51, 0xdc, 0xa7, 0x24, 0x5f,
+ 0xd2, 0xa9, 0xd5, 0xae, 0x23, 0x58, 0x38, 0x43, 0xce, 0xb5,
+ 0xc9, 0xb2, 0x3f, 0x44, 0xc7, 0xbc, 0x31, 0x4a, 0x36, 0x4d,
+ 0xc0, 0xbb, 0xab, 0xd0, 0x5d, 0x26, 0x5a, 0x21, 0xac, 0xd7,
+ 0x54, 0x2f, 0xa2, 0xd9, 0xa5, 0xde, 0x53, 0x28, 0x48, 0x33,
+ 0xbe, 0xc5, 0xb9, 0xc2, 0x4f, 0x34, 0xb7, 0xcc, 0x41, 0x3a,
+ 0x46, 0x3d, 0xb0, 0xcb, 0x70, 0x0b, 0x86, 0xfd, 0x81, 0xfa,
+ 0x77, 0x0c, 0x8f, 0xf4, 0x79, 0x02, 0x7e, 0x05, 0x88, 0xf3,
+ 0x93, 0xe8, 0x65, 0x1e, 0x62, 0x19, 0x94, 0xef, 0x6c, 0x17,
+ 0x9a, 0xe1, 0x9d, 0xe6, 0x6b, 0x10, 0x4b, 0x30, 0xbd, 0xc6,
+ 0xba, 0xc1, 0x4c, 0x37, 0xb4, 0xcf, 0x42, 0x39, 0x45, 0x3e,
+ 0xb3, 0xc8, 0xa8, 0xd3, 0x5e, 0x25, 0x59, 0x22, 0xaf, 0xd4,
+ 0x57, 0x2c, 0xa1, 0xda, 0xa6, 0xdd, 0x50, 0x2b, 0x90, 0xeb,
+ 0x66, 0x1d, 0x61, 0x1a, 0x97, 0xec, 0x6f, 0x14, 0x99, 0xe2,
+ 0x9e, 0xe5, 0x68, 0x13, 0x73, 0x08, 0x85, 0xfe, 0x82, 0xf9,
+ 0x74, 0x0f, 0x8c, 0xf7, 0x7a, 0x01, 0x7d, 0x06, 0x8b, 0xf0,
+ 0xe0, 0x9b, 0x16, 0x6d, 0x11, 0x6a, 0xe7, 0x9c, 0x1f, 0x64,
+ 0xe9, 0x92, 0xee, 0x95, 0x18, 0x63, 0x03, 0x78, 0xf5, 0x8e,
+ 0xf2, 0x89, 0x04, 0x7f, 0xfc, 0x87, 0x0a, 0x71, 0x0d, 0x76,
+ 0xfb, 0x80, 0x3b, 0x40, 0xcd, 0xb6, 0xca, 0xb1, 0x3c, 0x47,
+ 0xc4, 0xbf, 0x32, 0x49, 0x35, 0x4e, 0xc3, 0xb8, 0xd8, 0xa3,
+ 0x2e, 0x55, 0x29, 0x52, 0xdf, 0xa4, 0x27, 0x5c, 0xd1, 0xaa,
+ 0xd6, 0xad, 0x20, 0x5b, 0x00, 0x7c, 0xf8, 0x84, 0xed, 0x91,
+ 0x15, 0x69, 0xc7, 0xbb, 0x3f, 0x43, 0x2a, 0x56, 0xd2, 0xae,
+ 0x93, 0xef, 0x6b, 0x17, 0x7e, 0x02, 0x86, 0xfa, 0x54, 0x28,
+ 0xac, 0xd0, 0xb9, 0xc5, 0x41, 0x3d, 0x3b, 0x47, 0xc3, 0xbf,
+ 0xd6, 0xaa, 0x2e, 0x52, 0xfc, 0x80, 0x04, 0x78, 0x11, 0x6d,
+ 0xe9, 0x95, 0xa8, 0xd4, 0x50, 0x2c, 0x45, 0x39, 0xbd, 0xc1,
+ 0x6f, 0x13, 0x97, 0xeb, 0x82, 0xfe, 0x7a, 0x06, 0x76, 0x0a,
+ 0x8e, 0xf2, 0x9b, 0xe7, 0x63, 0x1f, 0xb1, 0xcd, 0x49, 0x35,
+ 0x5c, 0x20, 0xa4, 0xd8, 0xe5, 0x99, 0x1d, 0x61, 0x08, 0x74,
+ 0xf0, 0x8c, 0x22, 0x5e, 0xda, 0xa6, 0xcf, 0xb3, 0x37, 0x4b,
+ 0x4d, 0x31, 0xb5, 0xc9, 0xa0, 0xdc, 0x58, 0x24, 0x8a, 0xf6,
+ 0x72, 0x0e, 0x67, 0x1b, 0x9f, 0xe3, 0xde, 0xa2, 0x26, 0x5a,
+ 0x33, 0x4f, 0xcb, 0xb7, 0x19, 0x65, 0xe1, 0x9d, 0xf4, 0x88,
+ 0x0c, 0x70, 0xec, 0x90, 0x14, 0x68, 0x01, 0x7d, 0xf9, 0x85,
+ 0x2b, 0x57, 0xd3, 0xaf, 0xc6, 0xba, 0x3e, 0x42, 0x7f, 0x03,
+ 0x87, 0xfb, 0x92, 0xee, 0x6a, 0x16, 0xb8, 0xc4, 0x40, 0x3c,
+ 0x55, 0x29, 0xad, 0xd1, 0xd7, 0xab, 0x2f, 0x53, 0x3a, 0x46,
+ 0xc2, 0xbe, 0x10, 0x6c, 0xe8, 0x94, 0xfd, 0x81, 0x05, 0x79,
+ 0x44, 0x38, 0xbc, 0xc0, 0xa9, 0xd5, 0x51, 0x2d, 0x83, 0xff,
+ 0x7b, 0x07, 0x6e, 0x12, 0x96, 0xea, 0x9a, 0xe6, 0x62, 0x1e,
+ 0x77, 0x0b, 0x8f, 0xf3, 0x5d, 0x21, 0xa5, 0xd9, 0xb0, 0xcc,
+ 0x48, 0x34, 0x09, 0x75, 0xf1, 0x8d, 0xe4, 0x98, 0x1c, 0x60,
+ 0xce, 0xb2, 0x36, 0x4a, 0x23, 0x5f, 0xdb, 0xa7, 0xa1, 0xdd,
+ 0x59, 0x25, 0x4c, 0x30, 0xb4, 0xc8, 0x66, 0x1a, 0x9e, 0xe2,
+ 0x8b, 0xf7, 0x73, 0x0f, 0x32, 0x4e, 0xca, 0xb6, 0xdf, 0xa3,
+ 0x27, 0x5b, 0xf5, 0x89, 0x0d, 0x71, 0x18, 0x64, 0xe0, 0x9c,
+ 0x00, 0x7d, 0xfa, 0x87, 0xe9, 0x94, 0x13, 0x6e, 0xcf, 0xb2,
+ 0x35, 0x48, 0x26, 0x5b, 0xdc, 0xa1, 0x83, 0xfe, 0x79, 0x04,
+ 0x6a, 0x17, 0x90, 0xed, 0x4c, 0x31, 0xb6, 0xcb, 0xa5, 0xd8,
+ 0x5f, 0x22, 0x1b, 0x66, 0xe1, 0x9c, 0xf2, 0x8f, 0x08, 0x75,
+ 0xd4, 0xa9, 0x2e, 0x53, 0x3d, 0x40, 0xc7, 0xba, 0x98, 0xe5,
+ 0x62, 0x1f, 0x71, 0x0c, 0x8b, 0xf6, 0x57, 0x2a, 0xad, 0xd0,
+ 0xbe, 0xc3, 0x44, 0x39, 0x36, 0x4b, 0xcc, 0xb1, 0xdf, 0xa2,
+ 0x25, 0x58, 0xf9, 0x84, 0x03, 0x7e, 0x10, 0x6d, 0xea, 0x97,
+ 0xb5, 0xc8, 0x4f, 0x32, 0x5c, 0x21, 0xa6, 0xdb, 0x7a, 0x07,
+ 0x80, 0xfd, 0x93, 0xee, 0x69, 0x14, 0x2d, 0x50, 0xd7, 0xaa,
+ 0xc4, 0xb9, 0x3e, 0x43, 0xe2, 0x9f, 0x18, 0x65, 0x0b, 0x76,
+ 0xf1, 0x8c, 0xae, 0xd3, 0x54, 0x29, 0x47, 0x3a, 0xbd, 0xc0,
+ 0x61, 0x1c, 0x9b, 0xe6, 0x88, 0xf5, 0x72, 0x0f, 0x6c, 0x11,
+ 0x96, 0xeb, 0x85, 0xf8, 0x7f, 0x02, 0xa3, 0xde, 0x59, 0x24,
+ 0x4a, 0x37, 0xb0, 0xcd, 0xef, 0x92, 0x15, 0x68, 0x06, 0x7b,
+ 0xfc, 0x81, 0x20, 0x5d, 0xda, 0xa7, 0xc9, 0xb4, 0x33, 0x4e,
+ 0x77, 0x0a, 0x8d, 0xf0, 0x9e, 0xe3, 0x64, 0x19, 0xb8, 0xc5,
+ 0x42, 0x3f, 0x51, 0x2c, 0xab, 0xd6, 0xf4, 0x89, 0x0e, 0x73,
+ 0x1d, 0x60, 0xe7, 0x9a, 0x3b, 0x46, 0xc1, 0xbc, 0xd2, 0xaf,
+ 0x28, 0x55, 0x5a, 0x27, 0xa0, 0xdd, 0xb3, 0xce, 0x49, 0x34,
+ 0x95, 0xe8, 0x6f, 0x12, 0x7c, 0x01, 0x86, 0xfb, 0xd9, 0xa4,
+ 0x23, 0x5e, 0x30, 0x4d, 0xca, 0xb7, 0x16, 0x6b, 0xec, 0x91,
+ 0xff, 0x82, 0x05, 0x78, 0x41, 0x3c, 0xbb, 0xc6, 0xa8, 0xd5,
+ 0x52, 0x2f, 0x8e, 0xf3, 0x74, 0x09, 0x67, 0x1a, 0x9d, 0xe0,
+ 0xc2, 0xbf, 0x38, 0x45, 0x2b, 0x56, 0xd1, 0xac, 0x0d, 0x70,
+ 0xf7, 0x8a, 0xe4, 0x99, 0x1e, 0x63, 0x00, 0x7e, 0xfc, 0x82,
+ 0xe5, 0x9b, 0x19, 0x67, 0xd7, 0xa9, 0x2b, 0x55, 0x32, 0x4c,
+ 0xce, 0xb0, 0xb3, 0xcd, 0x4f, 0x31, 0x56, 0x28, 0xaa, 0xd4,
+ 0x64, 0x1a, 0x98, 0xe6, 0x81, 0xff, 0x7d, 0x03, 0x7b, 0x05,
+ 0x87, 0xf9, 0x9e, 0xe0, 0x62, 0x1c, 0xac, 0xd2, 0x50, 0x2e,
+ 0x49, 0x37, 0xb5, 0xcb, 0xc8, 0xb6, 0x34, 0x4a, 0x2d, 0x53,
+ 0xd1, 0xaf, 0x1f, 0x61, 0xe3, 0x9d, 0xfa, 0x84, 0x06, 0x78,
+ 0xf6, 0x88, 0x0a, 0x74, 0x13, 0x6d, 0xef, 0x91, 0x21, 0x5f,
+ 0xdd, 0xa3, 0xc4, 0xba, 0x38, 0x46, 0x45, 0x3b, 0xb9, 0xc7,
+ 0xa0, 0xde, 0x5c, 0x22, 0x92, 0xec, 0x6e, 0x10, 0x77, 0x09,
+ 0x8b, 0xf5, 0x8d, 0xf3, 0x71, 0x0f, 0x68, 0x16, 0x94, 0xea,
+ 0x5a, 0x24, 0xa6, 0xd8, 0xbf, 0xc1, 0x43, 0x3d, 0x3e, 0x40,
+ 0xc2, 0xbc, 0xdb, 0xa5, 0x27, 0x59, 0xe9, 0x97, 0x15, 0x6b,
+ 0x0c, 0x72, 0xf0, 0x8e, 0xf1, 0x8f, 0x0d, 0x73, 0x14, 0x6a,
+ 0xe8, 0x96, 0x26, 0x58, 0xda, 0xa4, 0xc3, 0xbd, 0x3f, 0x41,
+ 0x42, 0x3c, 0xbe, 0xc0, 0xa7, 0xd9, 0x5b, 0x25, 0x95, 0xeb,
+ 0x69, 0x17, 0x70, 0x0e, 0x8c, 0xf2, 0x8a, 0xf4, 0x76, 0x08,
+ 0x6f, 0x11, 0x93, 0xed, 0x5d, 0x23, 0xa1, 0xdf, 0xb8, 0xc6,
+ 0x44, 0x3a, 0x39, 0x47, 0xc5, 0xbb, 0xdc, 0xa2, 0x20, 0x5e,
+ 0xee, 0x90, 0x12, 0x6c, 0x0b, 0x75, 0xf7, 0x89, 0x07, 0x79,
+ 0xfb, 0x85, 0xe2, 0x9c, 0x1e, 0x60, 0xd0, 0xae, 0x2c, 0x52,
+ 0x35, 0x4b, 0xc9, 0xb7, 0xb4, 0xca, 0x48, 0x36, 0x51, 0x2f,
+ 0xad, 0xd3, 0x63, 0x1d, 0x9f, 0xe1, 0x86, 0xf8, 0x7a, 0x04,
+ 0x7c, 0x02, 0x80, 0xfe, 0x99, 0xe7, 0x65, 0x1b, 0xab, 0xd5,
+ 0x57, 0x29, 0x4e, 0x30, 0xb2, 0xcc, 0xcf, 0xb1, 0x33, 0x4d,
+ 0x2a, 0x54, 0xd6, 0xa8, 0x18, 0x66, 0xe4, 0x9a, 0xfd, 0x83,
+ 0x01, 0x7f, 0x00, 0x7f, 0xfe, 0x81, 0xe1, 0x9e, 0x1f, 0x60,
+ 0xdf, 0xa0, 0x21, 0x5e, 0x3e, 0x41, 0xc0, 0xbf, 0xa3, 0xdc,
+ 0x5d, 0x22, 0x42, 0x3d, 0xbc, 0xc3, 0x7c, 0x03, 0x82, 0xfd,
+ 0x9d, 0xe2, 0x63, 0x1c, 0x5b, 0x24, 0xa5, 0xda, 0xba, 0xc5,
+ 0x44, 0x3b, 0x84, 0xfb, 0x7a, 0x05, 0x65, 0x1a, 0x9b, 0xe4,
+ 0xf8, 0x87, 0x06, 0x79, 0x19, 0x66, 0xe7, 0x98, 0x27, 0x58,
+ 0xd9, 0xa6, 0xc6, 0xb9, 0x38, 0x47, 0xb6, 0xc9, 0x48, 0x37,
+ 0x57, 0x28, 0xa9, 0xd6, 0x69, 0x16, 0x97, 0xe8, 0x88, 0xf7,
+ 0x76, 0x09, 0x15, 0x6a, 0xeb, 0x94, 0xf4, 0x8b, 0x0a, 0x75,
+ 0xca, 0xb5, 0x34, 0x4b, 0x2b, 0x54, 0xd5, 0xaa, 0xed, 0x92,
+ 0x13, 0x6c, 0x0c, 0x73, 0xf2, 0x8d, 0x32, 0x4d, 0xcc, 0xb3,
+ 0xd3, 0xac, 0x2d, 0x52, 0x4e, 0x31, 0xb0, 0xcf, 0xaf, 0xd0,
+ 0x51, 0x2e, 0x91, 0xee, 0x6f, 0x10, 0x70, 0x0f, 0x8e, 0xf1,
+ 0x71, 0x0e, 0x8f, 0xf0, 0x90, 0xef, 0x6e, 0x11, 0xae, 0xd1,
+ 0x50, 0x2f, 0x4f, 0x30, 0xb1, 0xce, 0xd2, 0xad, 0x2c, 0x53,
+ 0x33, 0x4c, 0xcd, 0xb2, 0x0d, 0x72, 0xf3, 0x8c, 0xec, 0x93,
+ 0x12, 0x6d, 0x2a, 0x55, 0xd4, 0xab, 0xcb, 0xb4, 0x35, 0x4a,
+ 0xf5, 0x8a, 0x0b, 0x74, 0x14, 0x6b, 0xea, 0x95, 0x89, 0xf6,
+ 0x77, 0x08, 0x68, 0x17, 0x96, 0xe9, 0x56, 0x29, 0xa8, 0xd7,
+ 0xb7, 0xc8, 0x49, 0x36, 0xc7, 0xb8, 0x39, 0x46, 0x26, 0x59,
+ 0xd8, 0xa7, 0x18, 0x67, 0xe6, 0x99, 0xf9, 0x86, 0x07, 0x78,
+ 0x64, 0x1b, 0x9a, 0xe5, 0x85, 0xfa, 0x7b, 0x04, 0xbb, 0xc4,
+ 0x45, 0x3a, 0x5a, 0x25, 0xa4, 0xdb, 0x9c, 0xe3, 0x62, 0x1d,
+ 0x7d, 0x02, 0x83, 0xfc, 0x43, 0x3c, 0xbd, 0xc2, 0xa2, 0xdd,
+ 0x5c, 0x23, 0x3f, 0x40, 0xc1, 0xbe, 0xde, 0xa1, 0x20, 0x5f,
+ 0xe0, 0x9f, 0x1e, 0x61, 0x01, 0x7e, 0xff, 0x80, 0x00, 0x80,
+ 0x1d, 0x9d, 0x3a, 0xba, 0x27, 0xa7, 0x74, 0xf4, 0x69, 0xe9,
+ 0x4e, 0xce, 0x53, 0xd3, 0xe8, 0x68, 0xf5, 0x75, 0xd2, 0x52,
+ 0xcf, 0x4f, 0x9c, 0x1c, 0x81, 0x01, 0xa6, 0x26, 0xbb, 0x3b,
+ 0xcd, 0x4d, 0xd0, 0x50, 0xf7, 0x77, 0xea, 0x6a, 0xb9, 0x39,
+ 0xa4, 0x24, 0x83, 0x03, 0x9e, 0x1e, 0x25, 0xa5, 0x38, 0xb8,
+ 0x1f, 0x9f, 0x02, 0x82, 0x51, 0xd1, 0x4c, 0xcc, 0x6b, 0xeb,
+ 0x76, 0xf6, 0x87, 0x07, 0x9a, 0x1a, 0xbd, 0x3d, 0xa0, 0x20,
+ 0xf3, 0x73, 0xee, 0x6e, 0xc9, 0x49, 0xd4, 0x54, 0x6f, 0xef,
+ 0x72, 0xf2, 0x55, 0xd5, 0x48, 0xc8, 0x1b, 0x9b, 0x06, 0x86,
+ 0x21, 0xa1, 0x3c, 0xbc, 0x4a, 0xca, 0x57, 0xd7, 0x70, 0xf0,
+ 0x6d, 0xed, 0x3e, 0xbe, 0x23, 0xa3, 0x04, 0x84, 0x19, 0x99,
+ 0xa2, 0x22, 0xbf, 0x3f, 0x98, 0x18, 0x85, 0x05, 0xd6, 0x56,
+ 0xcb, 0x4b, 0xec, 0x6c, 0xf1, 0x71, 0x13, 0x93, 0x0e, 0x8e,
+ 0x29, 0xa9, 0x34, 0xb4, 0x67, 0xe7, 0x7a, 0xfa, 0x5d, 0xdd,
+ 0x40, 0xc0, 0xfb, 0x7b, 0xe6, 0x66, 0xc1, 0x41, 0xdc, 0x5c,
+ 0x8f, 0x0f, 0x92, 0x12, 0xb5, 0x35, 0xa8, 0x28, 0xde, 0x5e,
+ 0xc3, 0x43, 0xe4, 0x64, 0xf9, 0x79, 0xaa, 0x2a, 0xb7, 0x37,
+ 0x90, 0x10, 0x8d, 0x0d, 0x36, 0xb6, 0x2b, 0xab, 0x0c, 0x8c,
+ 0x11, 0x91, 0x42, 0xc2, 0x5f, 0xdf, 0x78, 0xf8, 0x65, 0xe5,
+ 0x94, 0x14, 0x89, 0x09, 0xae, 0x2e, 0xb3, 0x33, 0xe0, 0x60,
+ 0xfd, 0x7d, 0xda, 0x5a, 0xc7, 0x47, 0x7c, 0xfc, 0x61, 0xe1,
+ 0x46, 0xc6, 0x5b, 0xdb, 0x08, 0x88, 0x15, 0x95, 0x32, 0xb2,
+ 0x2f, 0xaf, 0x59, 0xd9, 0x44, 0xc4, 0x63, 0xe3, 0x7e, 0xfe,
+ 0x2d, 0xad, 0x30, 0xb0, 0x17, 0x97, 0x0a, 0x8a, 0xb1, 0x31,
+ 0xac, 0x2c, 0x8b, 0x0b, 0x96, 0x16, 0xc5, 0x45, 0xd8, 0x58,
+ 0xff, 0x7f, 0xe2, 0x62, 0x00, 0x81, 0x1f, 0x9e, 0x3e, 0xbf,
+ 0x21, 0xa0, 0x7c, 0xfd, 0x63, 0xe2, 0x42, 0xc3, 0x5d, 0xdc,
+ 0xf8, 0x79, 0xe7, 0x66, 0xc6, 0x47, 0xd9, 0x58, 0x84, 0x05,
+ 0x9b, 0x1a, 0xba, 0x3b, 0xa5, 0x24, 0xed, 0x6c, 0xf2, 0x73,
+ 0xd3, 0x52, 0xcc, 0x4d, 0x91, 0x10, 0x8e, 0x0f, 0xaf, 0x2e,
+ 0xb0, 0x31, 0x15, 0x94, 0x0a, 0x8b, 0x2b, 0xaa, 0x34, 0xb5,
+ 0x69, 0xe8, 0x76, 0xf7, 0x57, 0xd6, 0x48, 0xc9, 0xc7, 0x46,
+ 0xd8, 0x59, 0xf9, 0x78, 0xe6, 0x67, 0xbb, 0x3a, 0xa4, 0x25,
+ 0x85, 0x04, 0x9a, 0x1b, 0x3f, 0xbe, 0x20, 0xa1, 0x01, 0x80,
+ 0x1e, 0x9f, 0x43, 0xc2, 0x5c, 0xdd, 0x7d, 0xfc, 0x62, 0xe3,
+ 0x2a, 0xab, 0x35, 0xb4, 0x14, 0x95, 0x0b, 0x8a, 0x56, 0xd7,
+ 0x49, 0xc8, 0x68, 0xe9, 0x77, 0xf6, 0xd2, 0x53, 0xcd, 0x4c,
+ 0xec, 0x6d, 0xf3, 0x72, 0xae, 0x2f, 0xb1, 0x30, 0x90, 0x11,
+ 0x8f, 0x0e, 0x93, 0x12, 0x8c, 0x0d, 0xad, 0x2c, 0xb2, 0x33,
+ 0xef, 0x6e, 0xf0, 0x71, 0xd1, 0x50, 0xce, 0x4f, 0x6b, 0xea,
+ 0x74, 0xf5, 0x55, 0xd4, 0x4a, 0xcb, 0x17, 0x96, 0x08, 0x89,
+ 0x29, 0xa8, 0x36, 0xb7, 0x7e, 0xff, 0x61, 0xe0, 0x40, 0xc1,
+ 0x5f, 0xde, 0x02, 0x83, 0x1d, 0x9c, 0x3c, 0xbd, 0x23, 0xa2,
+ 0x86, 0x07, 0x99, 0x18, 0xb8, 0x39, 0xa7, 0x26, 0xfa, 0x7b,
+ 0xe5, 0x64, 0xc4, 0x45, 0xdb, 0x5a, 0x54, 0xd5, 0x4b, 0xca,
+ 0x6a, 0xeb, 0x75, 0xf4, 0x28, 0xa9, 0x37, 0xb6, 0x16, 0x97,
+ 0x09, 0x88, 0xac, 0x2d, 0xb3, 0x32, 0x92, 0x13, 0x8d, 0x0c,
+ 0xd0, 0x51, 0xcf, 0x4e, 0xee, 0x6f, 0xf1, 0x70, 0xb9, 0x38,
+ 0xa6, 0x27, 0x87, 0x06, 0x98, 0x19, 0xc5, 0x44, 0xda, 0x5b,
+ 0xfb, 0x7a, 0xe4, 0x65, 0x41, 0xc0, 0x5e, 0xdf, 0x7f, 0xfe,
+ 0x60, 0xe1, 0x3d, 0xbc, 0x22, 0xa3, 0x03, 0x82, 0x1c, 0x9d,
+ 0x00, 0x82, 0x19, 0x9b, 0x32, 0xb0, 0x2b, 0xa9, 0x64, 0xe6,
+ 0x7d, 0xff, 0x56, 0xd4, 0x4f, 0xcd, 0xc8, 0x4a, 0xd1, 0x53,
+ 0xfa, 0x78, 0xe3, 0x61, 0xac, 0x2e, 0xb5, 0x37, 0x9e, 0x1c,
+ 0x87, 0x05, 0x8d, 0x0f, 0x94, 0x16, 0xbf, 0x3d, 0xa6, 0x24,
+ 0xe9, 0x6b, 0xf0, 0x72, 0xdb, 0x59, 0xc2, 0x40, 0x45, 0xc7,
+ 0x5c, 0xde, 0x77, 0xf5, 0x6e, 0xec, 0x21, 0xa3, 0x38, 0xba,
+ 0x13, 0x91, 0x0a, 0x88, 0x07, 0x85, 0x1e, 0x9c, 0x35, 0xb7,
+ 0x2c, 0xae, 0x63, 0xe1, 0x7a, 0xf8, 0x51, 0xd3, 0x48, 0xca,
+ 0xcf, 0x4d, 0xd6, 0x54, 0xfd, 0x7f, 0xe4, 0x66, 0xab, 0x29,
+ 0xb2, 0x30, 0x99, 0x1b, 0x80, 0x02, 0x8a, 0x08, 0x93, 0x11,
+ 0xb8, 0x3a, 0xa1, 0x23, 0xee, 0x6c, 0xf7, 0x75, 0xdc, 0x5e,
+ 0xc5, 0x47, 0x42, 0xc0, 0x5b, 0xd9, 0x70, 0xf2, 0x69, 0xeb,
+ 0x26, 0xa4, 0x3f, 0xbd, 0x14, 0x96, 0x0d, 0x8f, 0x0e, 0x8c,
+ 0x17, 0x95, 0x3c, 0xbe, 0x25, 0xa7, 0x6a, 0xe8, 0x73, 0xf1,
+ 0x58, 0xda, 0x41, 0xc3, 0xc6, 0x44, 0xdf, 0x5d, 0xf4, 0x76,
+ 0xed, 0x6f, 0xa2, 0x20, 0xbb, 0x39, 0x90, 0x12, 0x89, 0x0b,
+ 0x83, 0x01, 0x9a, 0x18, 0xb1, 0x33, 0xa8, 0x2a, 0xe7, 0x65,
+ 0xfe, 0x7c, 0xd5, 0x57, 0xcc, 0x4e, 0x4b, 0xc9, 0x52, 0xd0,
+ 0x79, 0xfb, 0x60, 0xe2, 0x2f, 0xad, 0x36, 0xb4, 0x1d, 0x9f,
+ 0x04, 0x86, 0x09, 0x8b, 0x10, 0x92, 0x3b, 0xb9, 0x22, 0xa0,
+ 0x6d, 0xef, 0x74, 0xf6, 0x5f, 0xdd, 0x46, 0xc4, 0xc1, 0x43,
+ 0xd8, 0x5a, 0xf3, 0x71, 0xea, 0x68, 0xa5, 0x27, 0xbc, 0x3e,
+ 0x97, 0x15, 0x8e, 0x0c, 0x84, 0x06, 0x9d, 0x1f, 0xb6, 0x34,
+ 0xaf, 0x2d, 0xe0, 0x62, 0xf9, 0x7b, 0xd2, 0x50, 0xcb, 0x49,
+ 0x4c, 0xce, 0x55, 0xd7, 0x7e, 0xfc, 0x67, 0xe5, 0x28, 0xaa,
+ 0x31, 0xb3, 0x1a, 0x98, 0x03, 0x81, 0x00, 0x83, 0x1b, 0x98,
+ 0x36, 0xb5, 0x2d, 0xae, 0x6c, 0xef, 0x77, 0xf4, 0x5a, 0xd9,
+ 0x41, 0xc2, 0xd8, 0x5b, 0xc3, 0x40, 0xee, 0x6d, 0xf5, 0x76,
+ 0xb4, 0x37, 0xaf, 0x2c, 0x82, 0x01, 0x99, 0x1a, 0xad, 0x2e,
+ 0xb6, 0x35, 0x9b, 0x18, 0x80, 0x03, 0xc1, 0x42, 0xda, 0x59,
+ 0xf7, 0x74, 0xec, 0x6f, 0x75, 0xf6, 0x6e, 0xed, 0x43, 0xc0,
+ 0x58, 0xdb, 0x19, 0x9a, 0x02, 0x81, 0x2f, 0xac, 0x34, 0xb7,
+ 0x47, 0xc4, 0x5c, 0xdf, 0x71, 0xf2, 0x6a, 0xe9, 0x2b, 0xa8,
+ 0x30, 0xb3, 0x1d, 0x9e, 0x06, 0x85, 0x9f, 0x1c, 0x84, 0x07,
+ 0xa9, 0x2a, 0xb2, 0x31, 0xf3, 0x70, 0xe8, 0x6b, 0xc5, 0x46,
+ 0xde, 0x5d, 0xea, 0x69, 0xf1, 0x72, 0xdc, 0x5f, 0xc7, 0x44,
+ 0x86, 0x05, 0x9d, 0x1e, 0xb0, 0x33, 0xab, 0x28, 0x32, 0xb1,
+ 0x29, 0xaa, 0x04, 0x87, 0x1f, 0x9c, 0x5e, 0xdd, 0x45, 0xc6,
+ 0x68, 0xeb, 0x73, 0xf0, 0x8e, 0x0d, 0x95, 0x16, 0xb8, 0x3b,
+ 0xa3, 0x20, 0xe2, 0x61, 0xf9, 0x7a, 0xd4, 0x57, 0xcf, 0x4c,
+ 0x56, 0xd5, 0x4d, 0xce, 0x60, 0xe3, 0x7b, 0xf8, 0x3a, 0xb9,
+ 0x21, 0xa2, 0x0c, 0x8f, 0x17, 0x94, 0x23, 0xa0, 0x38, 0xbb,
+ 0x15, 0x96, 0x0e, 0x8d, 0x4f, 0xcc, 0x54, 0xd7, 0x79, 0xfa,
+ 0x62, 0xe1, 0xfb, 0x78, 0xe0, 0x63, 0xcd, 0x4e, 0xd6, 0x55,
+ 0x97, 0x14, 0x8c, 0x0f, 0xa1, 0x22, 0xba, 0x39, 0xc9, 0x4a,
+ 0xd2, 0x51, 0xff, 0x7c, 0xe4, 0x67, 0xa5, 0x26, 0xbe, 0x3d,
+ 0x93, 0x10, 0x88, 0x0b, 0x11, 0x92, 0x0a, 0x89, 0x27, 0xa4,
+ 0x3c, 0xbf, 0x7d, 0xfe, 0x66, 0xe5, 0x4b, 0xc8, 0x50, 0xd3,
+ 0x64, 0xe7, 0x7f, 0xfc, 0x52, 0xd1, 0x49, 0xca, 0x08, 0x8b,
+ 0x13, 0x90, 0x3e, 0xbd, 0x25, 0xa6, 0xbc, 0x3f, 0xa7, 0x24,
+ 0x8a, 0x09, 0x91, 0x12, 0xd0, 0x53, 0xcb, 0x48, 0xe6, 0x65,
+ 0xfd, 0x7e, 0x00, 0x84, 0x15, 0x91, 0x2a, 0xae, 0x3f, 0xbb,
+ 0x54, 0xd0, 0x41, 0xc5, 0x7e, 0xfa, 0x6b, 0xef, 0xa8, 0x2c,
+ 0xbd, 0x39, 0x82, 0x06, 0x97, 0x13, 0xfc, 0x78, 0xe9, 0x6d,
+ 0xd6, 0x52, 0xc3, 0x47, 0x4d, 0xc9, 0x58, 0xdc, 0x67, 0xe3,
+ 0x72, 0xf6, 0x19, 0x9d, 0x0c, 0x88, 0x33, 0xb7, 0x26, 0xa2,
+ 0xe5, 0x61, 0xf0, 0x74, 0xcf, 0x4b, 0xda, 0x5e, 0xb1, 0x35,
+ 0xa4, 0x20, 0x9b, 0x1f, 0x8e, 0x0a, 0x9a, 0x1e, 0x8f, 0x0b,
+ 0xb0, 0x34, 0xa5, 0x21, 0xce, 0x4a, 0xdb, 0x5f, 0xe4, 0x60,
+ 0xf1, 0x75, 0x32, 0xb6, 0x27, 0xa3, 0x18, 0x9c, 0x0d, 0x89,
+ 0x66, 0xe2, 0x73, 0xf7, 0x4c, 0xc8, 0x59, 0xdd, 0xd7, 0x53,
+ 0xc2, 0x46, 0xfd, 0x79, 0xe8, 0x6c, 0x83, 0x07, 0x96, 0x12,
+ 0xa9, 0x2d, 0xbc, 0x38, 0x7f, 0xfb, 0x6a, 0xee, 0x55, 0xd1,
+ 0x40, 0xc4, 0x2b, 0xaf, 0x3e, 0xba, 0x01, 0x85, 0x14, 0x90,
+ 0x29, 0xad, 0x3c, 0xb8, 0x03, 0x87, 0x16, 0x92, 0x7d, 0xf9,
+ 0x68, 0xec, 0x57, 0xd3, 0x42, 0xc6, 0x81, 0x05, 0x94, 0x10,
+ 0xab, 0x2f, 0xbe, 0x3a, 0xd5, 0x51, 0xc0, 0x44, 0xff, 0x7b,
+ 0xea, 0x6e, 0x64, 0xe0, 0x71, 0xf5, 0x4e, 0xca, 0x5b, 0xdf,
+ 0x30, 0xb4, 0x25, 0xa1, 0x1a, 0x9e, 0x0f, 0x8b, 0xcc, 0x48,
+ 0xd9, 0x5d, 0xe6, 0x62, 0xf3, 0x77, 0x98, 0x1c, 0x8d, 0x09,
+ 0xb2, 0x36, 0xa7, 0x23, 0xb3, 0x37, 0xa6, 0x22, 0x99, 0x1d,
+ 0x8c, 0x08, 0xe7, 0x63, 0xf2, 0x76, 0xcd, 0x49, 0xd8, 0x5c,
+ 0x1b, 0x9f, 0x0e, 0x8a, 0x31, 0xb5, 0x24, 0xa0, 0x4f, 0xcb,
+ 0x5a, 0xde, 0x65, 0xe1, 0x70, 0xf4, 0xfe, 0x7a, 0xeb, 0x6f,
+ 0xd4, 0x50, 0xc1, 0x45, 0xaa, 0x2e, 0xbf, 0x3b, 0x80, 0x04,
+ 0x95, 0x11, 0x56, 0xd2, 0x43, 0xc7, 0x7c, 0xf8, 0x69, 0xed,
+ 0x02, 0x86, 0x17, 0x93, 0x28, 0xac, 0x3d, 0xb9, 0x00, 0x85,
+ 0x17, 0x92, 0x2e, 0xab, 0x39, 0xbc, 0x5c, 0xd9, 0x4b, 0xce,
+ 0x72, 0xf7, 0x65, 0xe0, 0xb8, 0x3d, 0xaf, 0x2a, 0x96, 0x13,
+ 0x81, 0x04, 0xe4, 0x61, 0xf3, 0x76, 0xca, 0x4f, 0xdd, 0x58,
+ 0x6d, 0xe8, 0x7a, 0xff, 0x43, 0xc6, 0x54, 0xd1, 0x31, 0xb4,
+ 0x26, 0xa3, 0x1f, 0x9a, 0x08, 0x8d, 0xd5, 0x50, 0xc2, 0x47,
+ 0xfb, 0x7e, 0xec, 0x69, 0x89, 0x0c, 0x9e, 0x1b, 0xa7, 0x22,
+ 0xb0, 0x35, 0xda, 0x5f, 0xcd, 0x48, 0xf4, 0x71, 0xe3, 0x66,
+ 0x86, 0x03, 0x91, 0x14, 0xa8, 0x2d, 0xbf, 0x3a, 0x62, 0xe7,
+ 0x75, 0xf0, 0x4c, 0xc9, 0x5b, 0xde, 0x3e, 0xbb, 0x29, 0xac,
+ 0x10, 0x95, 0x07, 0x82, 0xb7, 0x32, 0xa0, 0x25, 0x99, 0x1c,
+ 0x8e, 0x0b, 0xeb, 0x6e, 0xfc, 0x79, 0xc5, 0x40, 0xd2, 0x57,
+ 0x0f, 0x8a, 0x18, 0x9d, 0x21, 0xa4, 0x36, 0xb3, 0x53, 0xd6,
+ 0x44, 0xc1, 0x7d, 0xf8, 0x6a, 0xef, 0xa9, 0x2c, 0xbe, 0x3b,
+ 0x87, 0x02, 0x90, 0x15, 0xf5, 0x70, 0xe2, 0x67, 0xdb, 0x5e,
+ 0xcc, 0x49, 0x11, 0x94, 0x06, 0x83, 0x3f, 0xba, 0x28, 0xad,
+ 0x4d, 0xc8, 0x5a, 0xdf, 0x63, 0xe6, 0x74, 0xf1, 0xc4, 0x41,
+ 0xd3, 0x56, 0xea, 0x6f, 0xfd, 0x78, 0x98, 0x1d, 0x8f, 0x0a,
+ 0xb6, 0x33, 0xa1, 0x24, 0x7c, 0xf9, 0x6b, 0xee, 0x52, 0xd7,
+ 0x45, 0xc0, 0x20, 0xa5, 0x37, 0xb2, 0x0e, 0x8b, 0x19, 0x9c,
+ 0x73, 0xf6, 0x64, 0xe1, 0x5d, 0xd8, 0x4a, 0xcf, 0x2f, 0xaa,
+ 0x38, 0xbd, 0x01, 0x84, 0x16, 0x93, 0xcb, 0x4e, 0xdc, 0x59,
+ 0xe5, 0x60, 0xf2, 0x77, 0x97, 0x12, 0x80, 0x05, 0xb9, 0x3c,
+ 0xae, 0x2b, 0x1e, 0x9b, 0x09, 0x8c, 0x30, 0xb5, 0x27, 0xa2,
+ 0x42, 0xc7, 0x55, 0xd0, 0x6c, 0xe9, 0x7b, 0xfe, 0xa6, 0x23,
+ 0xb1, 0x34, 0x88, 0x0d, 0x9f, 0x1a, 0xfa, 0x7f, 0xed, 0x68,
+ 0xd4, 0x51, 0xc3, 0x46, 0x00, 0x86, 0x11, 0x97, 0x22, 0xa4,
+ 0x33, 0xb5, 0x44, 0xc2, 0x55, 0xd3, 0x66, 0xe0, 0x77, 0xf1,
+ 0x88, 0x0e, 0x99, 0x1f, 0xaa, 0x2c, 0xbb, 0x3d, 0xcc, 0x4a,
+ 0xdd, 0x5b, 0xee, 0x68, 0xff, 0x79, 0x0d, 0x8b, 0x1c, 0x9a,
+ 0x2f, 0xa9, 0x3e, 0xb8, 0x49, 0xcf, 0x58, 0xde, 0x6b, 0xed,
+ 0x7a, 0xfc, 0x85, 0x03, 0x94, 0x12, 0xa7, 0x21, 0xb6, 0x30,
+ 0xc1, 0x47, 0xd0, 0x56, 0xe3, 0x65, 0xf2, 0x74, 0x1a, 0x9c,
+ 0x0b, 0x8d, 0x38, 0xbe, 0x29, 0xaf, 0x5e, 0xd8, 0x4f, 0xc9,
+ 0x7c, 0xfa, 0x6d, 0xeb, 0x92, 0x14, 0x83, 0x05, 0xb0, 0x36,
+ 0xa1, 0x27, 0xd6, 0x50, 0xc7, 0x41, 0xf4, 0x72, 0xe5, 0x63,
+ 0x17, 0x91, 0x06, 0x80, 0x35, 0xb3, 0x24, 0xa2, 0x53, 0xd5,
+ 0x42, 0xc4, 0x71, 0xf7, 0x60, 0xe6, 0x9f, 0x19, 0x8e, 0x08,
+ 0xbd, 0x3b, 0xac, 0x2a, 0xdb, 0x5d, 0xca, 0x4c, 0xf9, 0x7f,
+ 0xe8, 0x6e, 0x34, 0xb2, 0x25, 0xa3, 0x16, 0x90, 0x07, 0x81,
+ 0x70, 0xf6, 0x61, 0xe7, 0x52, 0xd4, 0x43, 0xc5, 0xbc, 0x3a,
+ 0xad, 0x2b, 0x9e, 0x18, 0x8f, 0x09, 0xf8, 0x7e, 0xe9, 0x6f,
+ 0xda, 0x5c, 0xcb, 0x4d, 0x39, 0xbf, 0x28, 0xae, 0x1b, 0x9d,
+ 0x0a, 0x8c, 0x7d, 0xfb, 0x6c, 0xea, 0x5f, 0xd9, 0x4e, 0xc8,
+ 0xb1, 0x37, 0xa0, 0x26, 0x93, 0x15, 0x82, 0x04, 0xf5, 0x73,
+ 0xe4, 0x62, 0xd7, 0x51, 0xc6, 0x40, 0x2e, 0xa8, 0x3f, 0xb9,
+ 0x0c, 0x8a, 0x1d, 0x9b, 0x6a, 0xec, 0x7b, 0xfd, 0x48, 0xce,
+ 0x59, 0xdf, 0xa6, 0x20, 0xb7, 0x31, 0x84, 0x02, 0x95, 0x13,
+ 0xe2, 0x64, 0xf3, 0x75, 0xc0, 0x46, 0xd1, 0x57, 0x23, 0xa5,
+ 0x32, 0xb4, 0x01, 0x87, 0x10, 0x96, 0x67, 0xe1, 0x76, 0xf0,
+ 0x45, 0xc3, 0x54, 0xd2, 0xab, 0x2d, 0xba, 0x3c, 0x89, 0x0f,
+ 0x98, 0x1e, 0xef, 0x69, 0xfe, 0x78, 0xcd, 0x4b, 0xdc, 0x5a,
+ 0x00, 0x87, 0x13, 0x94, 0x26, 0xa1, 0x35, 0xb2, 0x4c, 0xcb,
+ 0x5f, 0xd8, 0x6a, 0xed, 0x79, 0xfe, 0x98, 0x1f, 0x8b, 0x0c,
+ 0xbe, 0x39, 0xad, 0x2a, 0xd4, 0x53, 0xc7, 0x40, 0xf2, 0x75,
+ 0xe1, 0x66, 0x2d, 0xaa, 0x3e, 0xb9, 0x0b, 0x8c, 0x18, 0x9f,
+ 0x61, 0xe6, 0x72, 0xf5, 0x47, 0xc0, 0x54, 0xd3, 0xb5, 0x32,
+ 0xa6, 0x21, 0x93, 0x14, 0x80, 0x07, 0xf9, 0x7e, 0xea, 0x6d,
+ 0xdf, 0x58, 0xcc, 0x4b, 0x5a, 0xdd, 0x49, 0xce, 0x7c, 0xfb,
+ 0x6f, 0xe8, 0x16, 0x91, 0x05, 0x82, 0x30, 0xb7, 0x23, 0xa4,
+ 0xc2, 0x45, 0xd1, 0x56, 0xe4, 0x63, 0xf7, 0x70, 0x8e, 0x09,
+ 0x9d, 0x1a, 0xa8, 0x2f, 0xbb, 0x3c, 0x77, 0xf0, 0x64, 0xe3,
+ 0x51, 0xd6, 0x42, 0xc5, 0x3b, 0xbc, 0x28, 0xaf, 0x1d, 0x9a,
+ 0x0e, 0x89, 0xef, 0x68, 0xfc, 0x7b, 0xc9, 0x4e, 0xda, 0x5d,
+ 0xa3, 0x24, 0xb0, 0x37, 0x85, 0x02, 0x96, 0x11, 0xb4, 0x33,
+ 0xa7, 0x20, 0x92, 0x15, 0x81, 0x06, 0xf8, 0x7f, 0xeb, 0x6c,
+ 0xde, 0x59, 0xcd, 0x4a, 0x2c, 0xab, 0x3f, 0xb8, 0x0a, 0x8d,
+ 0x19, 0x9e, 0x60, 0xe7, 0x73, 0xf4, 0x46, 0xc1, 0x55, 0xd2,
+ 0x99, 0x1e, 0x8a, 0x0d, 0xbf, 0x38, 0xac, 0x2b, 0xd5, 0x52,
+ 0xc6, 0x41, 0xf3, 0x74, 0xe0, 0x67, 0x01, 0x86, 0x12, 0x95,
+ 0x27, 0xa0, 0x34, 0xb3, 0x4d, 0xca, 0x5e, 0xd9, 0x6b, 0xec,
+ 0x78, 0xff, 0xee, 0x69, 0xfd, 0x7a, 0xc8, 0x4f, 0xdb, 0x5c,
+ 0xa2, 0x25, 0xb1, 0x36, 0x84, 0x03, 0x97, 0x10, 0x76, 0xf1,
+ 0x65, 0xe2, 0x50, 0xd7, 0x43, 0xc4, 0x3a, 0xbd, 0x29, 0xae,
+ 0x1c, 0x9b, 0x0f, 0x88, 0xc3, 0x44, 0xd0, 0x57, 0xe5, 0x62,
+ 0xf6, 0x71, 0x8f, 0x08, 0x9c, 0x1b, 0xa9, 0x2e, 0xba, 0x3d,
+ 0x5b, 0xdc, 0x48, 0xcf, 0x7d, 0xfa, 0x6e, 0xe9, 0x17, 0x90,
+ 0x04, 0x83, 0x31, 0xb6, 0x22, 0xa5, 0x00, 0x88, 0x0d, 0x85,
+ 0x1a, 0x92, 0x17, 0x9f, 0x34, 0xbc, 0x39, 0xb1, 0x2e, 0xa6,
+ 0x23, 0xab, 0x68, 0xe0, 0x65, 0xed, 0x72, 0xfa, 0x7f, 0xf7,
+ 0x5c, 0xd4, 0x51, 0xd9, 0x46, 0xce, 0x4b, 0xc3, 0xd0, 0x58,
+ 0xdd, 0x55, 0xca, 0x42, 0xc7, 0x4f, 0xe4, 0x6c, 0xe9, 0x61,
+ 0xfe, 0x76, 0xf3, 0x7b, 0xb8, 0x30, 0xb5, 0x3d, 0xa2, 0x2a,
+ 0xaf, 0x27, 0x8c, 0x04, 0x81, 0x09, 0x96, 0x1e, 0x9b, 0x13,
+ 0xbd, 0x35, 0xb0, 0x38, 0xa7, 0x2f, 0xaa, 0x22, 0x89, 0x01,
+ 0x84, 0x0c, 0x93, 0x1b, 0x9e, 0x16, 0xd5, 0x5d, 0xd8, 0x50,
+ 0xcf, 0x47, 0xc2, 0x4a, 0xe1, 0x69, 0xec, 0x64, 0xfb, 0x73,
+ 0xf6, 0x7e, 0x6d, 0xe5, 0x60, 0xe8, 0x77, 0xff, 0x7a, 0xf2,
+ 0x59, 0xd1, 0x54, 0xdc, 0x43, 0xcb, 0x4e, 0xc6, 0x05, 0x8d,
+ 0x08, 0x80, 0x1f, 0x97, 0x12, 0x9a, 0x31, 0xb9, 0x3c, 0xb4,
+ 0x2b, 0xa3, 0x26, 0xae, 0x67, 0xef, 0x6a, 0xe2, 0x7d, 0xf5,
+ 0x70, 0xf8, 0x53, 0xdb, 0x5e, 0xd6, 0x49, 0xc1, 0x44, 0xcc,
+ 0x0f, 0x87, 0x02, 0x8a, 0x15, 0x9d, 0x18, 0x90, 0x3b, 0xb3,
+ 0x36, 0xbe, 0x21, 0xa9, 0x2c, 0xa4, 0xb7, 0x3f, 0xba, 0x32,
+ 0xad, 0x25, 0xa0, 0x28, 0x83, 0x0b, 0x8e, 0x06, 0x99, 0x11,
+ 0x94, 0x1c, 0xdf, 0x57, 0xd2, 0x5a, 0xc5, 0x4d, 0xc8, 0x40,
+ 0xeb, 0x63, 0xe6, 0x6e, 0xf1, 0x79, 0xfc, 0x74, 0xda, 0x52,
+ 0xd7, 0x5f, 0xc0, 0x48, 0xcd, 0x45, 0xee, 0x66, 0xe3, 0x6b,
+ 0xf4, 0x7c, 0xf9, 0x71, 0xb2, 0x3a, 0xbf, 0x37, 0xa8, 0x20,
+ 0xa5, 0x2d, 0x86, 0x0e, 0x8b, 0x03, 0x9c, 0x14, 0x91, 0x19,
+ 0x0a, 0x82, 0x07, 0x8f, 0x10, 0x98, 0x1d, 0x95, 0x3e, 0xb6,
+ 0x33, 0xbb, 0x24, 0xac, 0x29, 0xa1, 0x62, 0xea, 0x6f, 0xe7,
+ 0x78, 0xf0, 0x75, 0xfd, 0x56, 0xde, 0x5b, 0xd3, 0x4c, 0xc4,
+ 0x41, 0xc9, 0x00, 0x89, 0x0f, 0x86, 0x1e, 0x97, 0x11, 0x98,
+ 0x3c, 0xb5, 0x33, 0xba, 0x22, 0xab, 0x2d, 0xa4, 0x78, 0xf1,
+ 0x77, 0xfe, 0x66, 0xef, 0x69, 0xe0, 0x44, 0xcd, 0x4b, 0xc2,
+ 0x5a, 0xd3, 0x55, 0xdc, 0xf0, 0x79, 0xff, 0x76, 0xee, 0x67,
+ 0xe1, 0x68, 0xcc, 0x45, 0xc3, 0x4a, 0xd2, 0x5b, 0xdd, 0x54,
+ 0x88, 0x01, 0x87, 0x0e, 0x96, 0x1f, 0x99, 0x10, 0xb4, 0x3d,
+ 0xbb, 0x32, 0xaa, 0x23, 0xa5, 0x2c, 0xfd, 0x74, 0xf2, 0x7b,
+ 0xe3, 0x6a, 0xec, 0x65, 0xc1, 0x48, 0xce, 0x47, 0xdf, 0x56,
+ 0xd0, 0x59, 0x85, 0x0c, 0x8a, 0x03, 0x9b, 0x12, 0x94, 0x1d,
+ 0xb9, 0x30, 0xb6, 0x3f, 0xa7, 0x2e, 0xa8, 0x21, 0x0d, 0x84,
+ 0x02, 0x8b, 0x13, 0x9a, 0x1c, 0x95, 0x31, 0xb8, 0x3e, 0xb7,
+ 0x2f, 0xa6, 0x20, 0xa9, 0x75, 0xfc, 0x7a, 0xf3, 0x6b, 0xe2,
+ 0x64, 0xed, 0x49, 0xc0, 0x46, 0xcf, 0x57, 0xde, 0x58, 0xd1,
+ 0xe7, 0x6e, 0xe8, 0x61, 0xf9, 0x70, 0xf6, 0x7f, 0xdb, 0x52,
+ 0xd4, 0x5d, 0xc5, 0x4c, 0xca, 0x43, 0x9f, 0x16, 0x90, 0x19,
+ 0x81, 0x08, 0x8e, 0x07, 0xa3, 0x2a, 0xac, 0x25, 0xbd, 0x34,
+ 0xb2, 0x3b, 0x17, 0x9e, 0x18, 0x91, 0x09, 0x80, 0x06, 0x8f,
+ 0x2b, 0xa2, 0x24, 0xad, 0x35, 0xbc, 0x3a, 0xb3, 0x6f, 0xe6,
+ 0x60, 0xe9, 0x71, 0xf8, 0x7e, 0xf7, 0x53, 0xda, 0x5c, 0xd5,
+ 0x4d, 0xc4, 0x42, 0xcb, 0x1a, 0x93, 0x15, 0x9c, 0x04, 0x8d,
+ 0x0b, 0x82, 0x26, 0xaf, 0x29, 0xa0, 0x38, 0xb1, 0x37, 0xbe,
+ 0x62, 0xeb, 0x6d, 0xe4, 0x7c, 0xf5, 0x73, 0xfa, 0x5e, 0xd7,
+ 0x51, 0xd8, 0x40, 0xc9, 0x4f, 0xc6, 0xea, 0x63, 0xe5, 0x6c,
+ 0xf4, 0x7d, 0xfb, 0x72, 0xd6, 0x5f, 0xd9, 0x50, 0xc8, 0x41,
+ 0xc7, 0x4e, 0x92, 0x1b, 0x9d, 0x14, 0x8c, 0x05, 0x83, 0x0a,
+ 0xae, 0x27, 0xa1, 0x28, 0xb0, 0x39, 0xbf, 0x36, 0x00, 0x8a,
+ 0x09, 0x83, 0x12, 0x98, 0x1b, 0x91, 0x24, 0xae, 0x2d, 0xa7,
+ 0x36, 0xbc, 0x3f, 0xb5, 0x48, 0xc2, 0x41, 0xcb, 0x5a, 0xd0,
+ 0x53, 0xd9, 0x6c, 0xe6, 0x65, 0xef, 0x7e, 0xf4, 0x77, 0xfd,
+ 0x90, 0x1a, 0x99, 0x13, 0x82, 0x08, 0x8b, 0x01, 0xb4, 0x3e,
+ 0xbd, 0x37, 0xa6, 0x2c, 0xaf, 0x25, 0xd8, 0x52, 0xd1, 0x5b,
+ 0xca, 0x40, 0xc3, 0x49, 0xfc, 0x76, 0xf5, 0x7f, 0xee, 0x64,
+ 0xe7, 0x6d, 0x3d, 0xb7, 0x34, 0xbe, 0x2f, 0xa5, 0x26, 0xac,
+ 0x19, 0x93, 0x10, 0x9a, 0x0b, 0x81, 0x02, 0x88, 0x75, 0xff,
+ 0x7c, 0xf6, 0x67, 0xed, 0x6e, 0xe4, 0x51, 0xdb, 0x58, 0xd2,
+ 0x43, 0xc9, 0x4a, 0xc0, 0xad, 0x27, 0xa4, 0x2e, 0xbf, 0x35,
+ 0xb6, 0x3c, 0x89, 0x03, 0x80, 0x0a, 0x9b, 0x11, 0x92, 0x18,
+ 0xe5, 0x6f, 0xec, 0x66, 0xf7, 0x7d, 0xfe, 0x74, 0xc1, 0x4b,
+ 0xc8, 0x42, 0xd3, 0x59, 0xda, 0x50, 0x7a, 0xf0, 0x73, 0xf9,
+ 0x68, 0xe2, 0x61, 0xeb, 0x5e, 0xd4, 0x57, 0xdd, 0x4c, 0xc6,
+ 0x45, 0xcf, 0x32, 0xb8, 0x3b, 0xb1, 0x20, 0xaa, 0x29, 0xa3,
+ 0x16, 0x9c, 0x1f, 0x95, 0x04, 0x8e, 0x0d, 0x87, 0xea, 0x60,
+ 0xe3, 0x69, 0xf8, 0x72, 0xf1, 0x7b, 0xce, 0x44, 0xc7, 0x4d,
+ 0xdc, 0x56, 0xd5, 0x5f, 0xa2, 0x28, 0xab, 0x21, 0xb0, 0x3a,
+ 0xb9, 0x33, 0x86, 0x0c, 0x8f, 0x05, 0x94, 0x1e, 0x9d, 0x17,
+ 0x47, 0xcd, 0x4e, 0xc4, 0x55, 0xdf, 0x5c, 0xd6, 0x63, 0xe9,
+ 0x6a, 0xe0, 0x71, 0xfb, 0x78, 0xf2, 0x0f, 0x85, 0x06, 0x8c,
+ 0x1d, 0x97, 0x14, 0x9e, 0x2b, 0xa1, 0x22, 0xa8, 0x39, 0xb3,
+ 0x30, 0xba, 0xd7, 0x5d, 0xde, 0x54, 0xc5, 0x4f, 0xcc, 0x46,
+ 0xf3, 0x79, 0xfa, 0x70, 0xe1, 0x6b, 0xe8, 0x62, 0x9f, 0x15,
+ 0x96, 0x1c, 0x8d, 0x07, 0x84, 0x0e, 0xbb, 0x31, 0xb2, 0x38,
+ 0xa9, 0x23, 0xa0, 0x2a, 0x00, 0x8b, 0x0b, 0x80, 0x16, 0x9d,
+ 0x1d, 0x96, 0x2c, 0xa7, 0x27, 0xac, 0x3a, 0xb1, 0x31, 0xba,
+ 0x58, 0xd3, 0x53, 0xd8, 0x4e, 0xc5, 0x45, 0xce, 0x74, 0xff,
+ 0x7f, 0xf4, 0x62, 0xe9, 0x69, 0xe2, 0xb0, 0x3b, 0xbb, 0x30,
+ 0xa6, 0x2d, 0xad, 0x26, 0x9c, 0x17, 0x97, 0x1c, 0x8a, 0x01,
+ 0x81, 0x0a, 0xe8, 0x63, 0xe3, 0x68, 0xfe, 0x75, 0xf5, 0x7e,
+ 0xc4, 0x4f, 0xcf, 0x44, 0xd2, 0x59, 0xd9, 0x52, 0x7d, 0xf6,
+ 0x76, 0xfd, 0x6b, 0xe0, 0x60, 0xeb, 0x51, 0xda, 0x5a, 0xd1,
+ 0x47, 0xcc, 0x4c, 0xc7, 0x25, 0xae, 0x2e, 0xa5, 0x33, 0xb8,
+ 0x38, 0xb3, 0x09, 0x82, 0x02, 0x89, 0x1f, 0x94, 0x14, 0x9f,
+ 0xcd, 0x46, 0xc6, 0x4d, 0xdb, 0x50, 0xd0, 0x5b, 0xe1, 0x6a,
+ 0xea, 0x61, 0xf7, 0x7c, 0xfc, 0x77, 0x95, 0x1e, 0x9e, 0x15,
+ 0x83, 0x08, 0x88, 0x03, 0xb9, 0x32, 0xb2, 0x39, 0xaf, 0x24,
+ 0xa4, 0x2f, 0xfa, 0x71, 0xf1, 0x7a, 0xec, 0x67, 0xe7, 0x6c,
+ 0xd6, 0x5d, 0xdd, 0x56, 0xc0, 0x4b, 0xcb, 0x40, 0xa2, 0x29,
+ 0xa9, 0x22, 0xb4, 0x3f, 0xbf, 0x34, 0x8e, 0x05, 0x85, 0x0e,
+ 0x98, 0x13, 0x93, 0x18, 0x4a, 0xc1, 0x41, 0xca, 0x5c, 0xd7,
+ 0x57, 0xdc, 0x66, 0xed, 0x6d, 0xe6, 0x70, 0xfb, 0x7b, 0xf0,
+ 0x12, 0x99, 0x19, 0x92, 0x04, 0x8f, 0x0f, 0x84, 0x3e, 0xb5,
+ 0x35, 0xbe, 0x28, 0xa3, 0x23, 0xa8, 0x87, 0x0c, 0x8c, 0x07,
+ 0x91, 0x1a, 0x9a, 0x11, 0xab, 0x20, 0xa0, 0x2b, 0xbd, 0x36,
+ 0xb6, 0x3d, 0xdf, 0x54, 0xd4, 0x5f, 0xc9, 0x42, 0xc2, 0x49,
+ 0xf3, 0x78, 0xf8, 0x73, 0xe5, 0x6e, 0xee, 0x65, 0x37, 0xbc,
+ 0x3c, 0xb7, 0x21, 0xaa, 0x2a, 0xa1, 0x1b, 0x90, 0x10, 0x9b,
+ 0x0d, 0x86, 0x06, 0x8d, 0x6f, 0xe4, 0x64, 0xef, 0x79, 0xf2,
+ 0x72, 0xf9, 0x43, 0xc8, 0x48, 0xc3, 0x55, 0xde, 0x5e, 0xd5,
+ 0x00, 0x8c, 0x05, 0x89, 0x0a, 0x86, 0x0f, 0x83, 0x14, 0x98,
+ 0x11, 0x9d, 0x1e, 0x92, 0x1b, 0x97, 0x28, 0xa4, 0x2d, 0xa1,
+ 0x22, 0xae, 0x27, 0xab, 0x3c, 0xb0, 0x39, 0xb5, 0x36, 0xba,
+ 0x33, 0xbf, 0x50, 0xdc, 0x55, 0xd9, 0x5a, 0xd6, 0x5f, 0xd3,
+ 0x44, 0xc8, 0x41, 0xcd, 0x4e, 0xc2, 0x4b, 0xc7, 0x78, 0xf4,
+ 0x7d, 0xf1, 0x72, 0xfe, 0x77, 0xfb, 0x6c, 0xe0, 0x69, 0xe5,
+ 0x66, 0xea, 0x63, 0xef, 0xa0, 0x2c, 0xa5, 0x29, 0xaa, 0x26,
+ 0xaf, 0x23, 0xb4, 0x38, 0xb1, 0x3d, 0xbe, 0x32, 0xbb, 0x37,
+ 0x88, 0x04, 0x8d, 0x01, 0x82, 0x0e, 0x87, 0x0b, 0x9c, 0x10,
+ 0x99, 0x15, 0x96, 0x1a, 0x93, 0x1f, 0xf0, 0x7c, 0xf5, 0x79,
+ 0xfa, 0x76, 0xff, 0x73, 0xe4, 0x68, 0xe1, 0x6d, 0xee, 0x62,
+ 0xeb, 0x67, 0xd8, 0x54, 0xdd, 0x51, 0xd2, 0x5e, 0xd7, 0x5b,
+ 0xcc, 0x40, 0xc9, 0x45, 0xc6, 0x4a, 0xc3, 0x4f, 0x5d, 0xd1,
+ 0x58, 0xd4, 0x57, 0xdb, 0x52, 0xde, 0x49, 0xc5, 0x4c, 0xc0,
+ 0x43, 0xcf, 0x46, 0xca, 0x75, 0xf9, 0x70, 0xfc, 0x7f, 0xf3,
+ 0x7a, 0xf6, 0x61, 0xed, 0x64, 0xe8, 0x6b, 0xe7, 0x6e, 0xe2,
+ 0x0d, 0x81, 0x08, 0x84, 0x07, 0x8b, 0x02, 0x8e, 0x19, 0x95,
+ 0x1c, 0x90, 0x13, 0x9f, 0x16, 0x9a, 0x25, 0xa9, 0x20, 0xac,
+ 0x2f, 0xa3, 0x2a, 0xa6, 0x31, 0xbd, 0x34, 0xb8, 0x3b, 0xb7,
+ 0x3e, 0xb2, 0xfd, 0x71, 0xf8, 0x74, 0xf7, 0x7b, 0xf2, 0x7e,
+ 0xe9, 0x65, 0xec, 0x60, 0xe3, 0x6f, 0xe6, 0x6a, 0xd5, 0x59,
+ 0xd0, 0x5c, 0xdf, 0x53, 0xda, 0x56, 0xc1, 0x4d, 0xc4, 0x48,
+ 0xcb, 0x47, 0xce, 0x42, 0xad, 0x21, 0xa8, 0x24, 0xa7, 0x2b,
+ 0xa2, 0x2e, 0xb9, 0x35, 0xbc, 0x30, 0xb3, 0x3f, 0xb6, 0x3a,
+ 0x85, 0x09, 0x80, 0x0c, 0x8f, 0x03, 0x8a, 0x06, 0x91, 0x1d,
+ 0x94, 0x18, 0x9b, 0x17, 0x9e, 0x12, 0x00, 0x8d, 0x07, 0x8a,
+ 0x0e, 0x83, 0x09, 0x84, 0x1c, 0x91, 0x1b, 0x96, 0x12, 0x9f,
+ 0x15, 0x98, 0x38, 0xb5, 0x3f, 0xb2, 0x36, 0xbb, 0x31, 0xbc,
+ 0x24, 0xa9, 0x23, 0xae, 0x2a, 0xa7, 0x2d, 0xa0, 0x70, 0xfd,
+ 0x77, 0xfa, 0x7e, 0xf3, 0x79, 0xf4, 0x6c, 0xe1, 0x6b, 0xe6,
+ 0x62, 0xef, 0x65, 0xe8, 0x48, 0xc5, 0x4f, 0xc2, 0x46, 0xcb,
+ 0x41, 0xcc, 0x54, 0xd9, 0x53, 0xde, 0x5a, 0xd7, 0x5d, 0xd0,
+ 0xe0, 0x6d, 0xe7, 0x6a, 0xee, 0x63, 0xe9, 0x64, 0xfc, 0x71,
+ 0xfb, 0x76, 0xf2, 0x7f, 0xf5, 0x78, 0xd8, 0x55, 0xdf, 0x52,
+ 0xd6, 0x5b, 0xd1, 0x5c, 0xc4, 0x49, 0xc3, 0x4e, 0xca, 0x47,
+ 0xcd, 0x40, 0x90, 0x1d, 0x97, 0x1a, 0x9e, 0x13, 0x99, 0x14,
+ 0x8c, 0x01, 0x8b, 0x06, 0x82, 0x0f, 0x85, 0x08, 0xa8, 0x25,
+ 0xaf, 0x22, 0xa6, 0x2b, 0xa1, 0x2c, 0xb4, 0x39, 0xb3, 0x3e,
+ 0xba, 0x37, 0xbd, 0x30, 0xdd, 0x50, 0xda, 0x57, 0xd3, 0x5e,
+ 0xd4, 0x59, 0xc1, 0x4c, 0xc6, 0x4b, 0xcf, 0x42, 0xc8, 0x45,
+ 0xe5, 0x68, 0xe2, 0x6f, 0xeb, 0x66, 0xec, 0x61, 0xf9, 0x74,
+ 0xfe, 0x73, 0xf7, 0x7a, 0xf0, 0x7d, 0xad, 0x20, 0xaa, 0x27,
+ 0xa3, 0x2e, 0xa4, 0x29, 0xb1, 0x3c, 0xb6, 0x3b, 0xbf, 0x32,
+ 0xb8, 0x35, 0x95, 0x18, 0x92, 0x1f, 0x9b, 0x16, 0x9c, 0x11,
+ 0x89, 0x04, 0x8e, 0x03, 0x87, 0x0a, 0x80, 0x0d, 0x3d, 0xb0,
+ 0x3a, 0xb7, 0x33, 0xbe, 0x34, 0xb9, 0x21, 0xac, 0x26, 0xab,
+ 0x2f, 0xa2, 0x28, 0xa5, 0x05, 0x88, 0x02, 0x8f, 0x0b, 0x86,
+ 0x0c, 0x81, 0x19, 0x94, 0x1e, 0x93, 0x17, 0x9a, 0x10, 0x9d,
+ 0x4d, 0xc0, 0x4a, 0xc7, 0x43, 0xce, 0x44, 0xc9, 0x51, 0xdc,
+ 0x56, 0xdb, 0x5f, 0xd2, 0x58, 0xd5, 0x75, 0xf8, 0x72, 0xff,
+ 0x7b, 0xf6, 0x7c, 0xf1, 0x69, 0xe4, 0x6e, 0xe3, 0x67, 0xea,
+ 0x60, 0xed, 0x00, 0x8e, 0x01, 0x8f, 0x02, 0x8c, 0x03, 0x8d,
+ 0x04, 0x8a, 0x05, 0x8b, 0x06, 0x88, 0x07, 0x89, 0x08, 0x86,
+ 0x09, 0x87, 0x0a, 0x84, 0x0b, 0x85, 0x0c, 0x82, 0x0d, 0x83,
+ 0x0e, 0x80, 0x0f, 0x81, 0x10, 0x9e, 0x11, 0x9f, 0x12, 0x9c,
+ 0x13, 0x9d, 0x14, 0x9a, 0x15, 0x9b, 0x16, 0x98, 0x17, 0x99,
+ 0x18, 0x96, 0x19, 0x97, 0x1a, 0x94, 0x1b, 0x95, 0x1c, 0x92,
+ 0x1d, 0x93, 0x1e, 0x90, 0x1f, 0x91, 0x20, 0xae, 0x21, 0xaf,
+ 0x22, 0xac, 0x23, 0xad, 0x24, 0xaa, 0x25, 0xab, 0x26, 0xa8,
+ 0x27, 0xa9, 0x28, 0xa6, 0x29, 0xa7, 0x2a, 0xa4, 0x2b, 0xa5,
+ 0x2c, 0xa2, 0x2d, 0xa3, 0x2e, 0xa0, 0x2f, 0xa1, 0x30, 0xbe,
+ 0x31, 0xbf, 0x32, 0xbc, 0x33, 0xbd, 0x34, 0xba, 0x35, 0xbb,
+ 0x36, 0xb8, 0x37, 0xb9, 0x38, 0xb6, 0x39, 0xb7, 0x3a, 0xb4,
+ 0x3b, 0xb5, 0x3c, 0xb2, 0x3d, 0xb3, 0x3e, 0xb0, 0x3f, 0xb1,
+ 0x40, 0xce, 0x41, 0xcf, 0x42, 0xcc, 0x43, 0xcd, 0x44, 0xca,
+ 0x45, 0xcb, 0x46, 0xc8, 0x47, 0xc9, 0x48, 0xc6, 0x49, 0xc7,
+ 0x4a, 0xc4, 0x4b, 0xc5, 0x4c, 0xc2, 0x4d, 0xc3, 0x4e, 0xc0,
+ 0x4f, 0xc1, 0x50, 0xde, 0x51, 0xdf, 0x52, 0xdc, 0x53, 0xdd,
+ 0x54, 0xda, 0x55, 0xdb, 0x56, 0xd8, 0x57, 0xd9, 0x58, 0xd6,
+ 0x59, 0xd7, 0x5a, 0xd4, 0x5b, 0xd5, 0x5c, 0xd2, 0x5d, 0xd3,
+ 0x5e, 0xd0, 0x5f, 0xd1, 0x60, 0xee, 0x61, 0xef, 0x62, 0xec,
+ 0x63, 0xed, 0x64, 0xea, 0x65, 0xeb, 0x66, 0xe8, 0x67, 0xe9,
+ 0x68, 0xe6, 0x69, 0xe7, 0x6a, 0xe4, 0x6b, 0xe5, 0x6c, 0xe2,
+ 0x6d, 0xe3, 0x6e, 0xe0, 0x6f, 0xe1, 0x70, 0xfe, 0x71, 0xff,
+ 0x72, 0xfc, 0x73, 0xfd, 0x74, 0xfa, 0x75, 0xfb, 0x76, 0xf8,
+ 0x77, 0xf9, 0x78, 0xf6, 0x79, 0xf7, 0x7a, 0xf4, 0x7b, 0xf5,
+ 0x7c, 0xf2, 0x7d, 0xf3, 0x7e, 0xf0, 0x7f, 0xf1, 0x00, 0x8f,
+ 0x03, 0x8c, 0x06, 0x89, 0x05, 0x8a, 0x0c, 0x83, 0x0f, 0x80,
+ 0x0a, 0x85, 0x09, 0x86, 0x18, 0x97, 0x1b, 0x94, 0x1e, 0x91,
+ 0x1d, 0x92, 0x14, 0x9b, 0x17, 0x98, 0x12, 0x9d, 0x11, 0x9e,
+ 0x30, 0xbf, 0x33, 0xbc, 0x36, 0xb9, 0x35, 0xba, 0x3c, 0xb3,
+ 0x3f, 0xb0, 0x3a, 0xb5, 0x39, 0xb6, 0x28, 0xa7, 0x2b, 0xa4,
+ 0x2e, 0xa1, 0x2d, 0xa2, 0x24, 0xab, 0x27, 0xa8, 0x22, 0xad,
+ 0x21, 0xae, 0x60, 0xef, 0x63, 0xec, 0x66, 0xe9, 0x65, 0xea,
+ 0x6c, 0xe3, 0x6f, 0xe0, 0x6a, 0xe5, 0x69, 0xe6, 0x78, 0xf7,
+ 0x7b, 0xf4, 0x7e, 0xf1, 0x7d, 0xf2, 0x74, 0xfb, 0x77, 0xf8,
+ 0x72, 0xfd, 0x71, 0xfe, 0x50, 0xdf, 0x53, 0xdc, 0x56, 0xd9,
+ 0x55, 0xda, 0x5c, 0xd3, 0x5f, 0xd0, 0x5a, 0xd5, 0x59, 0xd6,
+ 0x48, 0xc7, 0x4b, 0xc4, 0x4e, 0xc1, 0x4d, 0xc2, 0x44, 0xcb,
+ 0x47, 0xc8, 0x42, 0xcd, 0x41, 0xce, 0xc0, 0x4f, 0xc3, 0x4c,
+ 0xc6, 0x49, 0xc5, 0x4a, 0xcc, 0x43, 0xcf, 0x40, 0xca, 0x45,
+ 0xc9, 0x46, 0xd8, 0x57, 0xdb, 0x54, 0xde, 0x51, 0xdd, 0x52,
+ 0xd4, 0x5b, 0xd7, 0x58, 0xd2, 0x5d, 0xd1, 0x5e, 0xf0, 0x7f,
+ 0xf3, 0x7c, 0xf6, 0x79, 0xf5, 0x7a, 0xfc, 0x73, 0xff, 0x70,
+ 0xfa, 0x75, 0xf9, 0x76, 0xe8, 0x67, 0xeb, 0x64, 0xee, 0x61,
+ 0xed, 0x62, 0xe4, 0x6b, 0xe7, 0x68, 0xe2, 0x6d, 0xe1, 0x6e,
+ 0xa0, 0x2f, 0xa3, 0x2c, 0xa6, 0x29, 0xa5, 0x2a, 0xac, 0x23,
+ 0xaf, 0x20, 0xaa, 0x25, 0xa9, 0x26, 0xb8, 0x37, 0xbb, 0x34,
+ 0xbe, 0x31, 0xbd, 0x32, 0xb4, 0x3b, 0xb7, 0x38, 0xb2, 0x3d,
+ 0xb1, 0x3e, 0x90, 0x1f, 0x93, 0x1c, 0x96, 0x19, 0x95, 0x1a,
+ 0x9c, 0x13, 0x9f, 0x10, 0x9a, 0x15, 0x99, 0x16, 0x88, 0x07,
+ 0x8b, 0x04, 0x8e, 0x01, 0x8d, 0x02, 0x84, 0x0b, 0x87, 0x08,
+ 0x82, 0x0d, 0x81, 0x0e, 0x00, 0x90, 0x3d, 0xad, 0x7a, 0xea,
+ 0x47, 0xd7, 0xf4, 0x64, 0xc9, 0x59, 0x8e, 0x1e, 0xb3, 0x23,
+ 0xf5, 0x65, 0xc8, 0x58, 0x8f, 0x1f, 0xb2, 0x22, 0x01, 0x91,
+ 0x3c, 0xac, 0x7b, 0xeb, 0x46, 0xd6, 0xf7, 0x67, 0xca, 0x5a,
+ 0x8d, 0x1d, 0xb0, 0x20, 0x03, 0x93, 0x3e, 0xae, 0x79, 0xe9,
+ 0x44, 0xd4, 0x02, 0x92, 0x3f, 0xaf, 0x78, 0xe8, 0x45, 0xd5,
+ 0xf6, 0x66, 0xcb, 0x5b, 0x8c, 0x1c, 0xb1, 0x21, 0xf3, 0x63,
+ 0xce, 0x5e, 0x89, 0x19, 0xb4, 0x24, 0x07, 0x97, 0x3a, 0xaa,
+ 0x7d, 0xed, 0x40, 0xd0, 0x06, 0x96, 0x3b, 0xab, 0x7c, 0xec,
+ 0x41, 0xd1, 0xf2, 0x62, 0xcf, 0x5f, 0x88, 0x18, 0xb5, 0x25,
+ 0x04, 0x94, 0x39, 0xa9, 0x7e, 0xee, 0x43, 0xd3, 0xf0, 0x60,
+ 0xcd, 0x5d, 0x8a, 0x1a, 0xb7, 0x27, 0xf1, 0x61, 0xcc, 0x5c,
+ 0x8b, 0x1b, 0xb6, 0x26, 0x05, 0x95, 0x38, 0xa8, 0x7f, 0xef,
+ 0x42, 0xd2, 0xfb, 0x6b, 0xc6, 0x56, 0x81, 0x11, 0xbc, 0x2c,
+ 0x0f, 0x9f, 0x32, 0xa2, 0x75, 0xe5, 0x48, 0xd8, 0x0e, 0x9e,
+ 0x33, 0xa3, 0x74, 0xe4, 0x49, 0xd9, 0xfa, 0x6a, 0xc7, 0x57,
+ 0x80, 0x10, 0xbd, 0x2d, 0x0c, 0x9c, 0x31, 0xa1, 0x76, 0xe6,
+ 0x4b, 0xdb, 0xf8, 0x68, 0xc5, 0x55, 0x82, 0x12, 0xbf, 0x2f,
+ 0xf9, 0x69, 0xc4, 0x54, 0x83, 0x13, 0xbe, 0x2e, 0x0d, 0x9d,
+ 0x30, 0xa0, 0x77, 0xe7, 0x4a, 0xda, 0x08, 0x98, 0x35, 0xa5,
+ 0x72, 0xe2, 0x4f, 0xdf, 0xfc, 0x6c, 0xc1, 0x51, 0x86, 0x16,
+ 0xbb, 0x2b, 0xfd, 0x6d, 0xc0, 0x50, 0x87, 0x17, 0xba, 0x2a,
+ 0x09, 0x99, 0x34, 0xa4, 0x73, 0xe3, 0x4e, 0xde, 0xff, 0x6f,
+ 0xc2, 0x52, 0x85, 0x15, 0xb8, 0x28, 0x0b, 0x9b, 0x36, 0xa6,
+ 0x71, 0xe1, 0x4c, 0xdc, 0x0a, 0x9a, 0x37, 0xa7, 0x70, 0xe0,
+ 0x4d, 0xdd, 0xfe, 0x6e, 0xc3, 0x53, 0x84, 0x14, 0xb9, 0x29,
+ 0x00, 0x91, 0x3f, 0xae, 0x7e, 0xef, 0x41, 0xd0, 0xfc, 0x6d,
+ 0xc3, 0x52, 0x82, 0x13, 0xbd, 0x2c, 0xe5, 0x74, 0xda, 0x4b,
+ 0x9b, 0x0a, 0xa4, 0x35, 0x19, 0x88, 0x26, 0xb7, 0x67, 0xf6,
+ 0x58, 0xc9, 0xd7, 0x46, 0xe8, 0x79, 0xa9, 0x38, 0x96, 0x07,
+ 0x2b, 0xba, 0x14, 0x85, 0x55, 0xc4, 0x6a, 0xfb, 0x32, 0xa3,
+ 0x0d, 0x9c, 0x4c, 0xdd, 0x73, 0xe2, 0xce, 0x5f, 0xf1, 0x60,
+ 0xb0, 0x21, 0x8f, 0x1e, 0xb3, 0x22, 0x8c, 0x1d, 0xcd, 0x5c,
+ 0xf2, 0x63, 0x4f, 0xde, 0x70, 0xe1, 0x31, 0xa0, 0x0e, 0x9f,
+ 0x56, 0xc7, 0x69, 0xf8, 0x28, 0xb9, 0x17, 0x86, 0xaa, 0x3b,
+ 0x95, 0x04, 0xd4, 0x45, 0xeb, 0x7a, 0x64, 0xf5, 0x5b, 0xca,
+ 0x1a, 0x8b, 0x25, 0xb4, 0x98, 0x09, 0xa7, 0x36, 0xe6, 0x77,
+ 0xd9, 0x48, 0x81, 0x10, 0xbe, 0x2f, 0xff, 0x6e, 0xc0, 0x51,
+ 0x7d, 0xec, 0x42, 0xd3, 0x03, 0x92, 0x3c, 0xad, 0x7b, 0xea,
+ 0x44, 0xd5, 0x05, 0x94, 0x3a, 0xab, 0x87, 0x16, 0xb8, 0x29,
+ 0xf9, 0x68, 0xc6, 0x57, 0x9e, 0x0f, 0xa1, 0x30, 0xe0, 0x71,
+ 0xdf, 0x4e, 0x62, 0xf3, 0x5d, 0xcc, 0x1c, 0x8d, 0x23, 0xb2,
+ 0xac, 0x3d, 0x93, 0x02, 0xd2, 0x43, 0xed, 0x7c, 0x50, 0xc1,
+ 0x6f, 0xfe, 0x2e, 0xbf, 0x11, 0x80, 0x49, 0xd8, 0x76, 0xe7,
+ 0x37, 0xa6, 0x08, 0x99, 0xb5, 0x24, 0x8a, 0x1b, 0xcb, 0x5a,
+ 0xf4, 0x65, 0xc8, 0x59, 0xf7, 0x66, 0xb6, 0x27, 0x89, 0x18,
+ 0x34, 0xa5, 0x0b, 0x9a, 0x4a, 0xdb, 0x75, 0xe4, 0x2d, 0xbc,
+ 0x12, 0x83, 0x53, 0xc2, 0x6c, 0xfd, 0xd1, 0x40, 0xee, 0x7f,
+ 0xaf, 0x3e, 0x90, 0x01, 0x1f, 0x8e, 0x20, 0xb1, 0x61, 0xf0,
+ 0x5e, 0xcf, 0xe3, 0x72, 0xdc, 0x4d, 0x9d, 0x0c, 0xa2, 0x33,
+ 0xfa, 0x6b, 0xc5, 0x54, 0x84, 0x15, 0xbb, 0x2a, 0x06, 0x97,
+ 0x39, 0xa8, 0x78, 0xe9, 0x47, 0xd6, 0x00, 0x92, 0x39, 0xab,
+ 0x72, 0xe0, 0x4b, 0xd9, 0xe4, 0x76, 0xdd, 0x4f, 0x96, 0x04,
+ 0xaf, 0x3d, 0xd5, 0x47, 0xec, 0x7e, 0xa7, 0x35, 0x9e, 0x0c,
+ 0x31, 0xa3, 0x08, 0x9a, 0x43, 0xd1, 0x7a, 0xe8, 0xb7, 0x25,
+ 0x8e, 0x1c, 0xc5, 0x57, 0xfc, 0x6e, 0x53, 0xc1, 0x6a, 0xf8,
+ 0x21, 0xb3, 0x18, 0x8a, 0x62, 0xf0, 0x5b, 0xc9, 0x10, 0x82,
+ 0x29, 0xbb, 0x86, 0x14, 0xbf, 0x2d, 0xf4, 0x66, 0xcd, 0x5f,
+ 0x73, 0xe1, 0x4a, 0xd8, 0x01, 0x93, 0x38, 0xaa, 0x97, 0x05,
+ 0xae, 0x3c, 0xe5, 0x77, 0xdc, 0x4e, 0xa6, 0x34, 0x9f, 0x0d,
+ 0xd4, 0x46, 0xed, 0x7f, 0x42, 0xd0, 0x7b, 0xe9, 0x30, 0xa2,
+ 0x09, 0x9b, 0xc4, 0x56, 0xfd, 0x6f, 0xb6, 0x24, 0x8f, 0x1d,
+ 0x20, 0xb2, 0x19, 0x8b, 0x52, 0xc0, 0x6b, 0xf9, 0x11, 0x83,
+ 0x28, 0xba, 0x63, 0xf1, 0x5a, 0xc8, 0xf5, 0x67, 0xcc, 0x5e,
+ 0x87, 0x15, 0xbe, 0x2c, 0xe6, 0x74, 0xdf, 0x4d, 0x94, 0x06,
+ 0xad, 0x3f, 0x02, 0x90, 0x3b, 0xa9, 0x70, 0xe2, 0x49, 0xdb,
+ 0x33, 0xa1, 0x0a, 0x98, 0x41, 0xd3, 0x78, 0xea, 0xd7, 0x45,
+ 0xee, 0x7c, 0xa5, 0x37, 0x9c, 0x0e, 0x51, 0xc3, 0x68, 0xfa,
+ 0x23, 0xb1, 0x1a, 0x88, 0xb5, 0x27, 0x8c, 0x1e, 0xc7, 0x55,
+ 0xfe, 0x6c, 0x84, 0x16, 0xbd, 0x2f, 0xf6, 0x64, 0xcf, 0x5d,
+ 0x60, 0xf2, 0x59, 0xcb, 0x12, 0x80, 0x2b, 0xb9, 0x95, 0x07,
+ 0xac, 0x3e, 0xe7, 0x75, 0xde, 0x4c, 0x71, 0xe3, 0x48, 0xda,
+ 0x03, 0x91, 0x3a, 0xa8, 0x40, 0xd2, 0x79, 0xeb, 0x32, 0xa0,
+ 0x0b, 0x99, 0xa4, 0x36, 0x9d, 0x0f, 0xd6, 0x44, 0xef, 0x7d,
+ 0x22, 0xb0, 0x1b, 0x89, 0x50, 0xc2, 0x69, 0xfb, 0xc6, 0x54,
+ 0xff, 0x6d, 0xb4, 0x26, 0x8d, 0x1f, 0xf7, 0x65, 0xce, 0x5c,
+ 0x85, 0x17, 0xbc, 0x2e, 0x13, 0x81, 0x2a, 0xb8, 0x61, 0xf3,
+ 0x58, 0xca, 0x00, 0x93, 0x3b, 0xa8, 0x76, 0xe5, 0x4d, 0xde,
+ 0xec, 0x7f, 0xd7, 0x44, 0x9a, 0x09, 0xa1, 0x32, 0xc5, 0x56,
+ 0xfe, 0x6d, 0xb3, 0x20, 0x88, 0x1b, 0x29, 0xba, 0x12, 0x81,
+ 0x5f, 0xcc, 0x64, 0xf7, 0x97, 0x04, 0xac, 0x3f, 0xe1, 0x72,
+ 0xda, 0x49, 0x7b, 0xe8, 0x40, 0xd3, 0x0d, 0x9e, 0x36, 0xa5,
+ 0x52, 0xc1, 0x69, 0xfa, 0x24, 0xb7, 0x1f, 0x8c, 0xbe, 0x2d,
+ 0x85, 0x16, 0xc8, 0x5b, 0xf3, 0x60, 0x33, 0xa0, 0x08, 0x9b,
+ 0x45, 0xd6, 0x7e, 0xed, 0xdf, 0x4c, 0xe4, 0x77, 0xa9, 0x3a,
+ 0x92, 0x01, 0xf6, 0x65, 0xcd, 0x5e, 0x80, 0x13, 0xbb, 0x28,
+ 0x1a, 0x89, 0x21, 0xb2, 0x6c, 0xff, 0x57, 0xc4, 0xa4, 0x37,
+ 0x9f, 0x0c, 0xd2, 0x41, 0xe9, 0x7a, 0x48, 0xdb, 0x73, 0xe0,
+ 0x3e, 0xad, 0x05, 0x96, 0x61, 0xf2, 0x5a, 0xc9, 0x17, 0x84,
+ 0x2c, 0xbf, 0x8d, 0x1e, 0xb6, 0x25, 0xfb, 0x68, 0xc0, 0x53,
+ 0x66, 0xf5, 0x5d, 0xce, 0x10, 0x83, 0x2b, 0xb8, 0x8a, 0x19,
+ 0xb1, 0x22, 0xfc, 0x6f, 0xc7, 0x54, 0xa3, 0x30, 0x98, 0x0b,
+ 0xd5, 0x46, 0xee, 0x7d, 0x4f, 0xdc, 0x74, 0xe7, 0x39, 0xaa,
+ 0x02, 0x91, 0xf1, 0x62, 0xca, 0x59, 0x87, 0x14, 0xbc, 0x2f,
+ 0x1d, 0x8e, 0x26, 0xb5, 0x6b, 0xf8, 0x50, 0xc3, 0x34, 0xa7,
+ 0x0f, 0x9c, 0x42, 0xd1, 0x79, 0xea, 0xd8, 0x4b, 0xe3, 0x70,
+ 0xae, 0x3d, 0x95, 0x06, 0x55, 0xc6, 0x6e, 0xfd, 0x23, 0xb0,
+ 0x18, 0x8b, 0xb9, 0x2a, 0x82, 0x11, 0xcf, 0x5c, 0xf4, 0x67,
+ 0x90, 0x03, 0xab, 0x38, 0xe6, 0x75, 0xdd, 0x4e, 0x7c, 0xef,
+ 0x47, 0xd4, 0x0a, 0x99, 0x31, 0xa2, 0xc2, 0x51, 0xf9, 0x6a,
+ 0xb4, 0x27, 0x8f, 0x1c, 0x2e, 0xbd, 0x15, 0x86, 0x58, 0xcb,
+ 0x63, 0xf0, 0x07, 0x94, 0x3c, 0xaf, 0x71, 0xe2, 0x4a, 0xd9,
+ 0xeb, 0x78, 0xd0, 0x43, 0x9d, 0x0e, 0xa6, 0x35, 0x00, 0x94,
+ 0x35, 0xa1, 0x6a, 0xfe, 0x5f, 0xcb, 0xd4, 0x40, 0xe1, 0x75,
+ 0xbe, 0x2a, 0x8b, 0x1f, 0xb5, 0x21, 0x80, 0x14, 0xdf, 0x4b,
+ 0xea, 0x7e, 0x61, 0xf5, 0x54, 0xc0, 0x0b, 0x9f, 0x3e, 0xaa,
+ 0x77, 0xe3, 0x42, 0xd6, 0x1d, 0x89, 0x28, 0xbc, 0xa3, 0x37,
+ 0x96, 0x02, 0xc9, 0x5d, 0xfc, 0x68, 0xc2, 0x56, 0xf7, 0x63,
+ 0xa8, 0x3c, 0x9d, 0x09, 0x16, 0x82, 0x23, 0xb7, 0x7c, 0xe8,
+ 0x49, 0xdd, 0xee, 0x7a, 0xdb, 0x4f, 0x84, 0x10, 0xb1, 0x25,
+ 0x3a, 0xae, 0x0f, 0x9b, 0x50, 0xc4, 0x65, 0xf1, 0x5b, 0xcf,
+ 0x6e, 0xfa, 0x31, 0xa5, 0x04, 0x90, 0x8f, 0x1b, 0xba, 0x2e,
+ 0xe5, 0x71, 0xd0, 0x44, 0x99, 0x0d, 0xac, 0x38, 0xf3, 0x67,
+ 0xc6, 0x52, 0x4d, 0xd9, 0x78, 0xec, 0x27, 0xb3, 0x12, 0x86,
+ 0x2c, 0xb8, 0x19, 0x8d, 0x46, 0xd2, 0x73, 0xe7, 0xf8, 0x6c,
+ 0xcd, 0x59, 0x92, 0x06, 0xa7, 0x33, 0xc1, 0x55, 0xf4, 0x60,
+ 0xab, 0x3f, 0x9e, 0x0a, 0x15, 0x81, 0x20, 0xb4, 0x7f, 0xeb,
+ 0x4a, 0xde, 0x74, 0xe0, 0x41, 0xd5, 0x1e, 0x8a, 0x2b, 0xbf,
+ 0xa0, 0x34, 0x95, 0x01, 0xca, 0x5e, 0xff, 0x6b, 0xb6, 0x22,
+ 0x83, 0x17, 0xdc, 0x48, 0xe9, 0x7d, 0x62, 0xf6, 0x57, 0xc3,
+ 0x08, 0x9c, 0x3d, 0xa9, 0x03, 0x97, 0x36, 0xa2, 0x69, 0xfd,
+ 0x5c, 0xc8, 0xd7, 0x43, 0xe2, 0x76, 0xbd, 0x29, 0x88, 0x1c,
+ 0x2f, 0xbb, 0x1a, 0x8e, 0x45, 0xd1, 0x70, 0xe4, 0xfb, 0x6f,
+ 0xce, 0x5a, 0x91, 0x05, 0xa4, 0x30, 0x9a, 0x0e, 0xaf, 0x3b,
+ 0xf0, 0x64, 0xc5, 0x51, 0x4e, 0xda, 0x7b, 0xef, 0x24, 0xb0,
+ 0x11, 0x85, 0x58, 0xcc, 0x6d, 0xf9, 0x32, 0xa6, 0x07, 0x93,
+ 0x8c, 0x18, 0xb9, 0x2d, 0xe6, 0x72, 0xd3, 0x47, 0xed, 0x79,
+ 0xd8, 0x4c, 0x87, 0x13, 0xb2, 0x26, 0x39, 0xad, 0x0c, 0x98,
+ 0x53, 0xc7, 0x66, 0xf2, 0x00, 0x95, 0x37, 0xa2, 0x6e, 0xfb,
+ 0x59, 0xcc, 0xdc, 0x49, 0xeb, 0x7e, 0xb2, 0x27, 0x85, 0x10,
+ 0xa5, 0x30, 0x92, 0x07, 0xcb, 0x5e, 0xfc, 0x69, 0x79, 0xec,
+ 0x4e, 0xdb, 0x17, 0x82, 0x20, 0xb5, 0x57, 0xc2, 0x60, 0xf5,
+ 0x39, 0xac, 0x0e, 0x9b, 0x8b, 0x1e, 0xbc, 0x29, 0xe5, 0x70,
+ 0xd2, 0x47, 0xf2, 0x67, 0xc5, 0x50, 0x9c, 0x09, 0xab, 0x3e,
+ 0x2e, 0xbb, 0x19, 0x8c, 0x40, 0xd5, 0x77, 0xe2, 0xae, 0x3b,
+ 0x99, 0x0c, 0xc0, 0x55, 0xf7, 0x62, 0x72, 0xe7, 0x45, 0xd0,
+ 0x1c, 0x89, 0x2b, 0xbe, 0x0b, 0x9e, 0x3c, 0xa9, 0x65, 0xf0,
+ 0x52, 0xc7, 0xd7, 0x42, 0xe0, 0x75, 0xb9, 0x2c, 0x8e, 0x1b,
+ 0xf9, 0x6c, 0xce, 0x5b, 0x97, 0x02, 0xa0, 0x35, 0x25, 0xb0,
+ 0x12, 0x87, 0x4b, 0xde, 0x7c, 0xe9, 0x5c, 0xc9, 0x6b, 0xfe,
+ 0x32, 0xa7, 0x05, 0x90, 0x80, 0x15, 0xb7, 0x22, 0xee, 0x7b,
+ 0xd9, 0x4c, 0x41, 0xd4, 0x76, 0xe3, 0x2f, 0xba, 0x18, 0x8d,
+ 0x9d, 0x08, 0xaa, 0x3f, 0xf3, 0x66, 0xc4, 0x51, 0xe4, 0x71,
+ 0xd3, 0x46, 0x8a, 0x1f, 0xbd, 0x28, 0x38, 0xad, 0x0f, 0x9a,
+ 0x56, 0xc3, 0x61, 0xf4, 0x16, 0x83, 0x21, 0xb4, 0x78, 0xed,
+ 0x4f, 0xda, 0xca, 0x5f, 0xfd, 0x68, 0xa4, 0x31, 0x93, 0x06,
+ 0xb3, 0x26, 0x84, 0x11, 0xdd, 0x48, 0xea, 0x7f, 0x6f, 0xfa,
+ 0x58, 0xcd, 0x01, 0x94, 0x36, 0xa3, 0xef, 0x7a, 0xd8, 0x4d,
+ 0x81, 0x14, 0xb6, 0x23, 0x33, 0xa6, 0x04, 0x91, 0x5d, 0xc8,
+ 0x6a, 0xff, 0x4a, 0xdf, 0x7d, 0xe8, 0x24, 0xb1, 0x13, 0x86,
+ 0x96, 0x03, 0xa1, 0x34, 0xf8, 0x6d, 0xcf, 0x5a, 0xb8, 0x2d,
+ 0x8f, 0x1a, 0xd6, 0x43, 0xe1, 0x74, 0x64, 0xf1, 0x53, 0xc6,
+ 0x0a, 0x9f, 0x3d, 0xa8, 0x1d, 0x88, 0x2a, 0xbf, 0x73, 0xe6,
+ 0x44, 0xd1, 0xc1, 0x54, 0xf6, 0x63, 0xaf, 0x3a, 0x98, 0x0d,
+ 0x00, 0x96, 0x31, 0xa7, 0x62, 0xf4, 0x53, 0xc5, 0xc4, 0x52,
+ 0xf5, 0x63, 0xa6, 0x30, 0x97, 0x01, 0x95, 0x03, 0xa4, 0x32,
+ 0xf7, 0x61, 0xc6, 0x50, 0x51, 0xc7, 0x60, 0xf6, 0x33, 0xa5,
+ 0x02, 0x94, 0x37, 0xa1, 0x06, 0x90, 0x55, 0xc3, 0x64, 0xf2,
+ 0xf3, 0x65, 0xc2, 0x54, 0x91, 0x07, 0xa0, 0x36, 0xa2, 0x34,
+ 0x93, 0x05, 0xc0, 0x56, 0xf1, 0x67, 0x66, 0xf0, 0x57, 0xc1,
+ 0x04, 0x92, 0x35, 0xa3, 0x6e, 0xf8, 0x5f, 0xc9, 0x0c, 0x9a,
+ 0x3d, 0xab, 0xaa, 0x3c, 0x9b, 0x0d, 0xc8, 0x5e, 0xf9, 0x6f,
+ 0xfb, 0x6d, 0xca, 0x5c, 0x99, 0x0f, 0xa8, 0x3e, 0x3f, 0xa9,
+ 0x0e, 0x98, 0x5d, 0xcb, 0x6c, 0xfa, 0x59, 0xcf, 0x68, 0xfe,
+ 0x3b, 0xad, 0x0a, 0x9c, 0x9d, 0x0b, 0xac, 0x3a, 0xff, 0x69,
+ 0xce, 0x58, 0xcc, 0x5a, 0xfd, 0x6b, 0xae, 0x38, 0x9f, 0x09,
+ 0x08, 0x9e, 0x39, 0xaf, 0x6a, 0xfc, 0x5b, 0xcd, 0xdc, 0x4a,
+ 0xed, 0x7b, 0xbe, 0x28, 0x8f, 0x19, 0x18, 0x8e, 0x29, 0xbf,
+ 0x7a, 0xec, 0x4b, 0xdd, 0x49, 0xdf, 0x78, 0xee, 0x2b, 0xbd,
+ 0x1a, 0x8c, 0x8d, 0x1b, 0xbc, 0x2a, 0xef, 0x79, 0xde, 0x48,
+ 0xeb, 0x7d, 0xda, 0x4c, 0x89, 0x1f, 0xb8, 0x2e, 0x2f, 0xb9,
+ 0x1e, 0x88, 0x4d, 0xdb, 0x7c, 0xea, 0x7e, 0xe8, 0x4f, 0xd9,
+ 0x1c, 0x8a, 0x2d, 0xbb, 0xba, 0x2c, 0x8b, 0x1d, 0xd8, 0x4e,
+ 0xe9, 0x7f, 0xb2, 0x24, 0x83, 0x15, 0xd0, 0x46, 0xe1, 0x77,
+ 0x76, 0xe0, 0x47, 0xd1, 0x14, 0x82, 0x25, 0xb3, 0x27, 0xb1,
+ 0x16, 0x80, 0x45, 0xd3, 0x74, 0xe2, 0xe3, 0x75, 0xd2, 0x44,
+ 0x81, 0x17, 0xb0, 0x26, 0x85, 0x13, 0xb4, 0x22, 0xe7, 0x71,
+ 0xd6, 0x40, 0x41, 0xd7, 0x70, 0xe6, 0x23, 0xb5, 0x12, 0x84,
+ 0x10, 0x86, 0x21, 0xb7, 0x72, 0xe4, 0x43, 0xd5, 0xd4, 0x42,
+ 0xe5, 0x73, 0xb6, 0x20, 0x87, 0x11, 0x00, 0x97, 0x33, 0xa4,
+ 0x66, 0xf1, 0x55, 0xc2, 0xcc, 0x5b, 0xff, 0x68, 0xaa, 0x3d,
+ 0x99, 0x0e, 0x85, 0x12, 0xb6, 0x21, 0xe3, 0x74, 0xd0, 0x47,
+ 0x49, 0xde, 0x7a, 0xed, 0x2f, 0xb8, 0x1c, 0x8b, 0x17, 0x80,
+ 0x24, 0xb3, 0x71, 0xe6, 0x42, 0xd5, 0xdb, 0x4c, 0xe8, 0x7f,
+ 0xbd, 0x2a, 0x8e, 0x19, 0x92, 0x05, 0xa1, 0x36, 0xf4, 0x63,
+ 0xc7, 0x50, 0x5e, 0xc9, 0x6d, 0xfa, 0x38, 0xaf, 0x0b, 0x9c,
+ 0x2e, 0xb9, 0x1d, 0x8a, 0x48, 0xdf, 0x7b, 0xec, 0xe2, 0x75,
+ 0xd1, 0x46, 0x84, 0x13, 0xb7, 0x20, 0xab, 0x3c, 0x98, 0x0f,
+ 0xcd, 0x5a, 0xfe, 0x69, 0x67, 0xf0, 0x54, 0xc3, 0x01, 0x96,
+ 0x32, 0xa5, 0x39, 0xae, 0x0a, 0x9d, 0x5f, 0xc8, 0x6c, 0xfb,
+ 0xf5, 0x62, 0xc6, 0x51, 0x93, 0x04, 0xa0, 0x37, 0xbc, 0x2b,
+ 0x8f, 0x18, 0xda, 0x4d, 0xe9, 0x7e, 0x70, 0xe7, 0x43, 0xd4,
+ 0x16, 0x81, 0x25, 0xb2, 0x5c, 0xcb, 0x6f, 0xf8, 0x3a, 0xad,
+ 0x09, 0x9e, 0x90, 0x07, 0xa3, 0x34, 0xf6, 0x61, 0xc5, 0x52,
+ 0xd9, 0x4e, 0xea, 0x7d, 0xbf, 0x28, 0x8c, 0x1b, 0x15, 0x82,
+ 0x26, 0xb1, 0x73, 0xe4, 0x40, 0xd7, 0x4b, 0xdc, 0x78, 0xef,
+ 0x2d, 0xba, 0x1e, 0x89, 0x87, 0x10, 0xb4, 0x23, 0xe1, 0x76,
+ 0xd2, 0x45, 0xce, 0x59, 0xfd, 0x6a, 0xa8, 0x3f, 0x9b, 0x0c,
+ 0x02, 0x95, 0x31, 0xa6, 0x64, 0xf3, 0x57, 0xc0, 0x72, 0xe5,
+ 0x41, 0xd6, 0x14, 0x83, 0x27, 0xb0, 0xbe, 0x29, 0x8d, 0x1a,
+ 0xd8, 0x4f, 0xeb, 0x7c, 0xf7, 0x60, 0xc4, 0x53, 0x91, 0x06,
+ 0xa2, 0x35, 0x3b, 0xac, 0x08, 0x9f, 0x5d, 0xca, 0x6e, 0xf9,
+ 0x65, 0xf2, 0x56, 0xc1, 0x03, 0x94, 0x30, 0xa7, 0xa9, 0x3e,
+ 0x9a, 0x0d, 0xcf, 0x58, 0xfc, 0x6b, 0xe0, 0x77, 0xd3, 0x44,
+ 0x86, 0x11, 0xb5, 0x22, 0x2c, 0xbb, 0x1f, 0x88, 0x4a, 0xdd,
+ 0x79, 0xee, 0x00, 0x98, 0x2d, 0xb5, 0x5a, 0xc2, 0x77, 0xef,
+ 0xb4, 0x2c, 0x99, 0x01, 0xee, 0x76, 0xc3, 0x5b, 0x75, 0xed,
+ 0x58, 0xc0, 0x2f, 0xb7, 0x02, 0x9a, 0xc1, 0x59, 0xec, 0x74,
+ 0x9b, 0x03, 0xb6, 0x2e, 0xea, 0x72, 0xc7, 0x5f, 0xb0, 0x28,
+ 0x9d, 0x05, 0x5e, 0xc6, 0x73, 0xeb, 0x04, 0x9c, 0x29, 0xb1,
+ 0x9f, 0x07, 0xb2, 0x2a, 0xc5, 0x5d, 0xe8, 0x70, 0x2b, 0xb3,
+ 0x06, 0x9e, 0x71, 0xe9, 0x5c, 0xc4, 0xc9, 0x51, 0xe4, 0x7c,
+ 0x93, 0x0b, 0xbe, 0x26, 0x7d, 0xe5, 0x50, 0xc8, 0x27, 0xbf,
+ 0x0a, 0x92, 0xbc, 0x24, 0x91, 0x09, 0xe6, 0x7e, 0xcb, 0x53,
+ 0x08, 0x90, 0x25, 0xbd, 0x52, 0xca, 0x7f, 0xe7, 0x23, 0xbb,
+ 0x0e, 0x96, 0x79, 0xe1, 0x54, 0xcc, 0x97, 0x0f, 0xba, 0x22,
+ 0xcd, 0x55, 0xe0, 0x78, 0x56, 0xce, 0x7b, 0xe3, 0x0c, 0x94,
+ 0x21, 0xb9, 0xe2, 0x7a, 0xcf, 0x57, 0xb8, 0x20, 0x95, 0x0d,
+ 0x8f, 0x17, 0xa2, 0x3a, 0xd5, 0x4d, 0xf8, 0x60, 0x3b, 0xa3,
+ 0x16, 0x8e, 0x61, 0xf9, 0x4c, 0xd4, 0xfa, 0x62, 0xd7, 0x4f,
+ 0xa0, 0x38, 0x8d, 0x15, 0x4e, 0xd6, 0x63, 0xfb, 0x14, 0x8c,
+ 0x39, 0xa1, 0x65, 0xfd, 0x48, 0xd0, 0x3f, 0xa7, 0x12, 0x8a,
+ 0xd1, 0x49, 0xfc, 0x64, 0x8b, 0x13, 0xa6, 0x3e, 0x10, 0x88,
+ 0x3d, 0xa5, 0x4a, 0xd2, 0x67, 0xff, 0xa4, 0x3c, 0x89, 0x11,
+ 0xfe, 0x66, 0xd3, 0x4b, 0x46, 0xde, 0x6b, 0xf3, 0x1c, 0x84,
+ 0x31, 0xa9, 0xf2, 0x6a, 0xdf, 0x47, 0xa8, 0x30, 0x85, 0x1d,
+ 0x33, 0xab, 0x1e, 0x86, 0x69, 0xf1, 0x44, 0xdc, 0x87, 0x1f,
+ 0xaa, 0x32, 0xdd, 0x45, 0xf0, 0x68, 0xac, 0x34, 0x81, 0x19,
+ 0xf6, 0x6e, 0xdb, 0x43, 0x18, 0x80, 0x35, 0xad, 0x42, 0xda,
+ 0x6f, 0xf7, 0xd9, 0x41, 0xf4, 0x6c, 0x83, 0x1b, 0xae, 0x36,
+ 0x6d, 0xf5, 0x40, 0xd8, 0x37, 0xaf, 0x1a, 0x82, 0x00, 0x99,
+ 0x2f, 0xb6, 0x5e, 0xc7, 0x71, 0xe8, 0xbc, 0x25, 0x93, 0x0a,
+ 0xe2, 0x7b, 0xcd, 0x54, 0x65, 0xfc, 0x4a, 0xd3, 0x3b, 0xa2,
+ 0x14, 0x8d, 0xd9, 0x40, 0xf6, 0x6f, 0x87, 0x1e, 0xa8, 0x31,
+ 0xca, 0x53, 0xe5, 0x7c, 0x94, 0x0d, 0xbb, 0x22, 0x76, 0xef,
+ 0x59, 0xc0, 0x28, 0xb1, 0x07, 0x9e, 0xaf, 0x36, 0x80, 0x19,
+ 0xf1, 0x68, 0xde, 0x47, 0x13, 0x8a, 0x3c, 0xa5, 0x4d, 0xd4,
+ 0x62, 0xfb, 0x89, 0x10, 0xa6, 0x3f, 0xd7, 0x4e, 0xf8, 0x61,
+ 0x35, 0xac, 0x1a, 0x83, 0x6b, 0xf2, 0x44, 0xdd, 0xec, 0x75,
+ 0xc3, 0x5a, 0xb2, 0x2b, 0x9d, 0x04, 0x50, 0xc9, 0x7f, 0xe6,
+ 0x0e, 0x97, 0x21, 0xb8, 0x43, 0xda, 0x6c, 0xf5, 0x1d, 0x84,
+ 0x32, 0xab, 0xff, 0x66, 0xd0, 0x49, 0xa1, 0x38, 0x8e, 0x17,
+ 0x26, 0xbf, 0x09, 0x90, 0x78, 0xe1, 0x57, 0xce, 0x9a, 0x03,
+ 0xb5, 0x2c, 0xc4, 0x5d, 0xeb, 0x72, 0x0f, 0x96, 0x20, 0xb9,
+ 0x51, 0xc8, 0x7e, 0xe7, 0xb3, 0x2a, 0x9c, 0x05, 0xed, 0x74,
+ 0xc2, 0x5b, 0x6a, 0xf3, 0x45, 0xdc, 0x34, 0xad, 0x1b, 0x82,
+ 0xd6, 0x4f, 0xf9, 0x60, 0x88, 0x11, 0xa7, 0x3e, 0xc5, 0x5c,
+ 0xea, 0x73, 0x9b, 0x02, 0xb4, 0x2d, 0x79, 0xe0, 0x56, 0xcf,
+ 0x27, 0xbe, 0x08, 0x91, 0xa0, 0x39, 0x8f, 0x16, 0xfe, 0x67,
+ 0xd1, 0x48, 0x1c, 0x85, 0x33, 0xaa, 0x42, 0xdb, 0x6d, 0xf4,
+ 0x86, 0x1f, 0xa9, 0x30, 0xd8, 0x41, 0xf7, 0x6e, 0x3a, 0xa3,
+ 0x15, 0x8c, 0x64, 0xfd, 0x4b, 0xd2, 0xe3, 0x7a, 0xcc, 0x55,
+ 0xbd, 0x24, 0x92, 0x0b, 0x5f, 0xc6, 0x70, 0xe9, 0x01, 0x98,
+ 0x2e, 0xb7, 0x4c, 0xd5, 0x63, 0xfa, 0x12, 0x8b, 0x3d, 0xa4,
+ 0xf0, 0x69, 0xdf, 0x46, 0xae, 0x37, 0x81, 0x18, 0x29, 0xb0,
+ 0x06, 0x9f, 0x77, 0xee, 0x58, 0xc1, 0x95, 0x0c, 0xba, 0x23,
+ 0xcb, 0x52, 0xe4, 0x7d, 0x00, 0x9a, 0x29, 0xb3, 0x52, 0xc8,
+ 0x7b, 0xe1, 0xa4, 0x3e, 0x8d, 0x17, 0xf6, 0x6c, 0xdf, 0x45,
+ 0x55, 0xcf, 0x7c, 0xe6, 0x07, 0x9d, 0x2e, 0xb4, 0xf1, 0x6b,
+ 0xd8, 0x42, 0xa3, 0x39, 0x8a, 0x10, 0xaa, 0x30, 0x83, 0x19,
+ 0xf8, 0x62, 0xd1, 0x4b, 0x0e, 0x94, 0x27, 0xbd, 0x5c, 0xc6,
+ 0x75, 0xef, 0xff, 0x65, 0xd6, 0x4c, 0xad, 0x37, 0x84, 0x1e,
+ 0x5b, 0xc1, 0x72, 0xe8, 0x09, 0x93, 0x20, 0xba, 0x49, 0xd3,
+ 0x60, 0xfa, 0x1b, 0x81, 0x32, 0xa8, 0xed, 0x77, 0xc4, 0x5e,
+ 0xbf, 0x25, 0x96, 0x0c, 0x1c, 0x86, 0x35, 0xaf, 0x4e, 0xd4,
+ 0x67, 0xfd, 0xb8, 0x22, 0x91, 0x0b, 0xea, 0x70, 0xc3, 0x59,
+ 0xe3, 0x79, 0xca, 0x50, 0xb1, 0x2b, 0x98, 0x02, 0x47, 0xdd,
+ 0x6e, 0xf4, 0x15, 0x8f, 0x3c, 0xa6, 0xb6, 0x2c, 0x9f, 0x05,
+ 0xe4, 0x7e, 0xcd, 0x57, 0x12, 0x88, 0x3b, 0xa1, 0x40, 0xda,
+ 0x69, 0xf3, 0x92, 0x08, 0xbb, 0x21, 0xc0, 0x5a, 0xe9, 0x73,
+ 0x36, 0xac, 0x1f, 0x85, 0x64, 0xfe, 0x4d, 0xd7, 0xc7, 0x5d,
+ 0xee, 0x74, 0x95, 0x0f, 0xbc, 0x26, 0x63, 0xf9, 0x4a, 0xd0,
+ 0x31, 0xab, 0x18, 0x82, 0x38, 0xa2, 0x11, 0x8b, 0x6a, 0xf0,
+ 0x43, 0xd9, 0x9c, 0x06, 0xb5, 0x2f, 0xce, 0x54, 0xe7, 0x7d,
+ 0x6d, 0xf7, 0x44, 0xde, 0x3f, 0xa5, 0x16, 0x8c, 0xc9, 0x53,
+ 0xe0, 0x7a, 0x9b, 0x01, 0xb2, 0x28, 0xdb, 0x41, 0xf2, 0x68,
+ 0x89, 0x13, 0xa0, 0x3a, 0x7f, 0xe5, 0x56, 0xcc, 0x2d, 0xb7,
+ 0x04, 0x9e, 0x8e, 0x14, 0xa7, 0x3d, 0xdc, 0x46, 0xf5, 0x6f,
+ 0x2a, 0xb0, 0x03, 0x99, 0x78, 0xe2, 0x51, 0xcb, 0x71, 0xeb,
+ 0x58, 0xc2, 0x23, 0xb9, 0x0a, 0x90, 0xd5, 0x4f, 0xfc, 0x66,
+ 0x87, 0x1d, 0xae, 0x34, 0x24, 0xbe, 0x0d, 0x97, 0x76, 0xec,
+ 0x5f, 0xc5, 0x80, 0x1a, 0xa9, 0x33, 0xd2, 0x48, 0xfb, 0x61,
+ 0x00, 0x9b, 0x2b, 0xb0, 0x56, 0xcd, 0x7d, 0xe6, 0xac, 0x37,
+ 0x87, 0x1c, 0xfa, 0x61, 0xd1, 0x4a, 0x45, 0xde, 0x6e, 0xf5,
+ 0x13, 0x88, 0x38, 0xa3, 0xe9, 0x72, 0xc2, 0x59, 0xbf, 0x24,
+ 0x94, 0x0f, 0x8a, 0x11, 0xa1, 0x3a, 0xdc, 0x47, 0xf7, 0x6c,
+ 0x26, 0xbd, 0x0d, 0x96, 0x70, 0xeb, 0x5b, 0xc0, 0xcf, 0x54,
+ 0xe4, 0x7f, 0x99, 0x02, 0xb2, 0x29, 0x63, 0xf8, 0x48, 0xd3,
+ 0x35, 0xae, 0x1e, 0x85, 0x09, 0x92, 0x22, 0xb9, 0x5f, 0xc4,
+ 0x74, 0xef, 0xa5, 0x3e, 0x8e, 0x15, 0xf3, 0x68, 0xd8, 0x43,
+ 0x4c, 0xd7, 0x67, 0xfc, 0x1a, 0x81, 0x31, 0xaa, 0xe0, 0x7b,
+ 0xcb, 0x50, 0xb6, 0x2d, 0x9d, 0x06, 0x83, 0x18, 0xa8, 0x33,
+ 0xd5, 0x4e, 0xfe, 0x65, 0x2f, 0xb4, 0x04, 0x9f, 0x79, 0xe2,
+ 0x52, 0xc9, 0xc6, 0x5d, 0xed, 0x76, 0x90, 0x0b, 0xbb, 0x20,
+ 0x6a, 0xf1, 0x41, 0xda, 0x3c, 0xa7, 0x17, 0x8c, 0x12, 0x89,
+ 0x39, 0xa2, 0x44, 0xdf, 0x6f, 0xf4, 0xbe, 0x25, 0x95, 0x0e,
+ 0xe8, 0x73, 0xc3, 0x58, 0x57, 0xcc, 0x7c, 0xe7, 0x01, 0x9a,
+ 0x2a, 0xb1, 0xfb, 0x60, 0xd0, 0x4b, 0xad, 0x36, 0x86, 0x1d,
+ 0x98, 0x03, 0xb3, 0x28, 0xce, 0x55, 0xe5, 0x7e, 0x34, 0xaf,
+ 0x1f, 0x84, 0x62, 0xf9, 0x49, 0xd2, 0xdd, 0x46, 0xf6, 0x6d,
+ 0x8b, 0x10, 0xa0, 0x3b, 0x71, 0xea, 0x5a, 0xc1, 0x27, 0xbc,
+ 0x0c, 0x97, 0x1b, 0x80, 0x30, 0xab, 0x4d, 0xd6, 0x66, 0xfd,
+ 0xb7, 0x2c, 0x9c, 0x07, 0xe1, 0x7a, 0xca, 0x51, 0x5e, 0xc5,
+ 0x75, 0xee, 0x08, 0x93, 0x23, 0xb8, 0xf2, 0x69, 0xd9, 0x42,
+ 0xa4, 0x3f, 0x8f, 0x14, 0x91, 0x0a, 0xba, 0x21, 0xc7, 0x5c,
+ 0xec, 0x77, 0x3d, 0xa6, 0x16, 0x8d, 0x6b, 0xf0, 0x40, 0xdb,
+ 0xd4, 0x4f, 0xff, 0x64, 0x82, 0x19, 0xa9, 0x32, 0x78, 0xe3,
+ 0x53, 0xc8, 0x2e, 0xb5, 0x05, 0x9e, 0x00, 0x9c, 0x25, 0xb9,
+ 0x4a, 0xd6, 0x6f, 0xf3, 0x94, 0x08, 0xb1, 0x2d, 0xde, 0x42,
+ 0xfb, 0x67, 0x35, 0xa9, 0x10, 0x8c, 0x7f, 0xe3, 0x5a, 0xc6,
+ 0xa1, 0x3d, 0x84, 0x18, 0xeb, 0x77, 0xce, 0x52, 0x6a, 0xf6,
+ 0x4f, 0xd3, 0x20, 0xbc, 0x05, 0x99, 0xfe, 0x62, 0xdb, 0x47,
+ 0xb4, 0x28, 0x91, 0x0d, 0x5f, 0xc3, 0x7a, 0xe6, 0x15, 0x89,
+ 0x30, 0xac, 0xcb, 0x57, 0xee, 0x72, 0x81, 0x1d, 0xa4, 0x38,
+ 0xd4, 0x48, 0xf1, 0x6d, 0x9e, 0x02, 0xbb, 0x27, 0x40, 0xdc,
+ 0x65, 0xf9, 0x0a, 0x96, 0x2f, 0xb3, 0xe1, 0x7d, 0xc4, 0x58,
+ 0xab, 0x37, 0x8e, 0x12, 0x75, 0xe9, 0x50, 0xcc, 0x3f, 0xa3,
+ 0x1a, 0x86, 0xbe, 0x22, 0x9b, 0x07, 0xf4, 0x68, 0xd1, 0x4d,
+ 0x2a, 0xb6, 0x0f, 0x93, 0x60, 0xfc, 0x45, 0xd9, 0x8b, 0x17,
+ 0xae, 0x32, 0xc1, 0x5d, 0xe4, 0x78, 0x1f, 0x83, 0x3a, 0xa6,
+ 0x55, 0xc9, 0x70, 0xec, 0xb5, 0x29, 0x90, 0x0c, 0xff, 0x63,
+ 0xda, 0x46, 0x21, 0xbd, 0x04, 0x98, 0x6b, 0xf7, 0x4e, 0xd2,
+ 0x80, 0x1c, 0xa5, 0x39, 0xca, 0x56, 0xef, 0x73, 0x14, 0x88,
+ 0x31, 0xad, 0x5e, 0xc2, 0x7b, 0xe7, 0xdf, 0x43, 0xfa, 0x66,
+ 0x95, 0x09, 0xb0, 0x2c, 0x4b, 0xd7, 0x6e, 0xf2, 0x01, 0x9d,
+ 0x24, 0xb8, 0xea, 0x76, 0xcf, 0x53, 0xa0, 0x3c, 0x85, 0x19,
+ 0x7e, 0xe2, 0x5b, 0xc7, 0x34, 0xa8, 0x11, 0x8d, 0x61, 0xfd,
+ 0x44, 0xd8, 0x2b, 0xb7, 0x0e, 0x92, 0xf5, 0x69, 0xd0, 0x4c,
+ 0xbf, 0x23, 0x9a, 0x06, 0x54, 0xc8, 0x71, 0xed, 0x1e, 0x82,
+ 0x3b, 0xa7, 0xc0, 0x5c, 0xe5, 0x79, 0x8a, 0x16, 0xaf, 0x33,
+ 0x0b, 0x97, 0x2e, 0xb2, 0x41, 0xdd, 0x64, 0xf8, 0x9f, 0x03,
+ 0xba, 0x26, 0xd5, 0x49, 0xf0, 0x6c, 0x3e, 0xa2, 0x1b, 0x87,
+ 0x74, 0xe8, 0x51, 0xcd, 0xaa, 0x36, 0x8f, 0x13, 0xe0, 0x7c,
+ 0xc5, 0x59, 0x00, 0x9d, 0x27, 0xba, 0x4e, 0xd3, 0x69, 0xf4,
+ 0x9c, 0x01, 0xbb, 0x26, 0xd2, 0x4f, 0xf5, 0x68, 0x25, 0xb8,
+ 0x02, 0x9f, 0x6b, 0xf6, 0x4c, 0xd1, 0xb9, 0x24, 0x9e, 0x03,
+ 0xf7, 0x6a, 0xd0, 0x4d, 0x4a, 0xd7, 0x6d, 0xf0, 0x04, 0x99,
+ 0x23, 0xbe, 0xd6, 0x4b, 0xf1, 0x6c, 0x98, 0x05, 0xbf, 0x22,
+ 0x6f, 0xf2, 0x48, 0xd5, 0x21, 0xbc, 0x06, 0x9b, 0xf3, 0x6e,
+ 0xd4, 0x49, 0xbd, 0x20, 0x9a, 0x07, 0x94, 0x09, 0xb3, 0x2e,
+ 0xda, 0x47, 0xfd, 0x60, 0x08, 0x95, 0x2f, 0xb2, 0x46, 0xdb,
+ 0x61, 0xfc, 0xb1, 0x2c, 0x96, 0x0b, 0xff, 0x62, 0xd8, 0x45,
+ 0x2d, 0xb0, 0x0a, 0x97, 0x63, 0xfe, 0x44, 0xd9, 0xde, 0x43,
+ 0xf9, 0x64, 0x90, 0x0d, 0xb7, 0x2a, 0x42, 0xdf, 0x65, 0xf8,
+ 0x0c, 0x91, 0x2b, 0xb6, 0xfb, 0x66, 0xdc, 0x41, 0xb5, 0x28,
+ 0x92, 0x0f, 0x67, 0xfa, 0x40, 0xdd, 0x29, 0xb4, 0x0e, 0x93,
+ 0x35, 0xa8, 0x12, 0x8f, 0x7b, 0xe6, 0x5c, 0xc1, 0xa9, 0x34,
+ 0x8e, 0x13, 0xe7, 0x7a, 0xc0, 0x5d, 0x10, 0x8d, 0x37, 0xaa,
+ 0x5e, 0xc3, 0x79, 0xe4, 0x8c, 0x11, 0xab, 0x36, 0xc2, 0x5f,
+ 0xe5, 0x78, 0x7f, 0xe2, 0x58, 0xc5, 0x31, 0xac, 0x16, 0x8b,
+ 0xe3, 0x7e, 0xc4, 0x59, 0xad, 0x30, 0x8a, 0x17, 0x5a, 0xc7,
+ 0x7d, 0xe0, 0x14, 0x89, 0x33, 0xae, 0xc6, 0x5b, 0xe1, 0x7c,
+ 0x88, 0x15, 0xaf, 0x32, 0xa1, 0x3c, 0x86, 0x1b, 0xef, 0x72,
+ 0xc8, 0x55, 0x3d, 0xa0, 0x1a, 0x87, 0x73, 0xee, 0x54, 0xc9,
+ 0x84, 0x19, 0xa3, 0x3e, 0xca, 0x57, 0xed, 0x70, 0x18, 0x85,
+ 0x3f, 0xa2, 0x56, 0xcb, 0x71, 0xec, 0xeb, 0x76, 0xcc, 0x51,
+ 0xa5, 0x38, 0x82, 0x1f, 0x77, 0xea, 0x50, 0xcd, 0x39, 0xa4,
+ 0x1e, 0x83, 0xce, 0x53, 0xe9, 0x74, 0x80, 0x1d, 0xa7, 0x3a,
+ 0x52, 0xcf, 0x75, 0xe8, 0x1c, 0x81, 0x3b, 0xa6, 0x00, 0x9e,
+ 0x21, 0xbf, 0x42, 0xdc, 0x63, 0xfd, 0x84, 0x1a, 0xa5, 0x3b,
+ 0xc6, 0x58, 0xe7, 0x79, 0x15, 0x8b, 0x34, 0xaa, 0x57, 0xc9,
+ 0x76, 0xe8, 0x91, 0x0f, 0xb0, 0x2e, 0xd3, 0x4d, 0xf2, 0x6c,
+ 0x2a, 0xb4, 0x0b, 0x95, 0x68, 0xf6, 0x49, 0xd7, 0xae, 0x30,
+ 0x8f, 0x11, 0xec, 0x72, 0xcd, 0x53, 0x3f, 0xa1, 0x1e, 0x80,
+ 0x7d, 0xe3, 0x5c, 0xc2, 0xbb, 0x25, 0x9a, 0x04, 0xf9, 0x67,
+ 0xd8, 0x46, 0x54, 0xca, 0x75, 0xeb, 0x16, 0x88, 0x37, 0xa9,
+ 0xd0, 0x4e, 0xf1, 0x6f, 0x92, 0x0c, 0xb3, 0x2d, 0x41, 0xdf,
+ 0x60, 0xfe, 0x03, 0x9d, 0x22, 0xbc, 0xc5, 0x5b, 0xe4, 0x7a,
+ 0x87, 0x19, 0xa6, 0x38, 0x7e, 0xe0, 0x5f, 0xc1, 0x3c, 0xa2,
+ 0x1d, 0x83, 0xfa, 0x64, 0xdb, 0x45, 0xb8, 0x26, 0x99, 0x07,
+ 0x6b, 0xf5, 0x4a, 0xd4, 0x29, 0xb7, 0x08, 0x96, 0xef, 0x71,
+ 0xce, 0x50, 0xad, 0x33, 0x8c, 0x12, 0xa8, 0x36, 0x89, 0x17,
+ 0xea, 0x74, 0xcb, 0x55, 0x2c, 0xb2, 0x0d, 0x93, 0x6e, 0xf0,
+ 0x4f, 0xd1, 0xbd, 0x23, 0x9c, 0x02, 0xff, 0x61, 0xde, 0x40,
+ 0x39, 0xa7, 0x18, 0x86, 0x7b, 0xe5, 0x5a, 0xc4, 0x82, 0x1c,
+ 0xa3, 0x3d, 0xc0, 0x5e, 0xe1, 0x7f, 0x06, 0x98, 0x27, 0xb9,
+ 0x44, 0xda, 0x65, 0xfb, 0x97, 0x09, 0xb6, 0x28, 0xd5, 0x4b,
+ 0xf4, 0x6a, 0x13, 0x8d, 0x32, 0xac, 0x51, 0xcf, 0x70, 0xee,
+ 0xfc, 0x62, 0xdd, 0x43, 0xbe, 0x20, 0x9f, 0x01, 0x78, 0xe6,
+ 0x59, 0xc7, 0x3a, 0xa4, 0x1b, 0x85, 0xe9, 0x77, 0xc8, 0x56,
+ 0xab, 0x35, 0x8a, 0x14, 0x6d, 0xf3, 0x4c, 0xd2, 0x2f, 0xb1,
+ 0x0e, 0x90, 0xd6, 0x48, 0xf7, 0x69, 0x94, 0x0a, 0xb5, 0x2b,
+ 0x52, 0xcc, 0x73, 0xed, 0x10, 0x8e, 0x31, 0xaf, 0xc3, 0x5d,
+ 0xe2, 0x7c, 0x81, 0x1f, 0xa0, 0x3e, 0x47, 0xd9, 0x66, 0xf8,
+ 0x05, 0x9b, 0x24, 0xba, 0x00, 0x9f, 0x23, 0xbc, 0x46, 0xd9,
+ 0x65, 0xfa, 0x8c, 0x13, 0xaf, 0x30, 0xca, 0x55, 0xe9, 0x76,
+ 0x05, 0x9a, 0x26, 0xb9, 0x43, 0xdc, 0x60, 0xff, 0x89, 0x16,
+ 0xaa, 0x35, 0xcf, 0x50, 0xec, 0x73, 0x0a, 0x95, 0x29, 0xb6,
+ 0x4c, 0xd3, 0x6f, 0xf0, 0x86, 0x19, 0xa5, 0x3a, 0xc0, 0x5f,
+ 0xe3, 0x7c, 0x0f, 0x90, 0x2c, 0xb3, 0x49, 0xd6, 0x6a, 0xf5,
+ 0x83, 0x1c, 0xa0, 0x3f, 0xc5, 0x5a, 0xe6, 0x79, 0x14, 0x8b,
+ 0x37, 0xa8, 0x52, 0xcd, 0x71, 0xee, 0x98, 0x07, 0xbb, 0x24,
+ 0xde, 0x41, 0xfd, 0x62, 0x11, 0x8e, 0x32, 0xad, 0x57, 0xc8,
+ 0x74, 0xeb, 0x9d, 0x02, 0xbe, 0x21, 0xdb, 0x44, 0xf8, 0x67,
+ 0x1e, 0x81, 0x3d, 0xa2, 0x58, 0xc7, 0x7b, 0xe4, 0x92, 0x0d,
+ 0xb1, 0x2e, 0xd4, 0x4b, 0xf7, 0x68, 0x1b, 0x84, 0x38, 0xa7,
+ 0x5d, 0xc2, 0x7e, 0xe1, 0x97, 0x08, 0xb4, 0x2b, 0xd1, 0x4e,
+ 0xf2, 0x6d, 0x28, 0xb7, 0x0b, 0x94, 0x6e, 0xf1, 0x4d, 0xd2,
+ 0xa4, 0x3b, 0x87, 0x18, 0xe2, 0x7d, 0xc1, 0x5e, 0x2d, 0xb2,
+ 0x0e, 0x91, 0x6b, 0xf4, 0x48, 0xd7, 0xa1, 0x3e, 0x82, 0x1d,
+ 0xe7, 0x78, 0xc4, 0x5b, 0x22, 0xbd, 0x01, 0x9e, 0x64, 0xfb,
+ 0x47, 0xd8, 0xae, 0x31, 0x8d, 0x12, 0xe8, 0x77, 0xcb, 0x54,
+ 0x27, 0xb8, 0x04, 0x9b, 0x61, 0xfe, 0x42, 0xdd, 0xab, 0x34,
+ 0x88, 0x17, 0xed, 0x72, 0xce, 0x51, 0x3c, 0xa3, 0x1f, 0x80,
+ 0x7a, 0xe5, 0x59, 0xc6, 0xb0, 0x2f, 0x93, 0x0c, 0xf6, 0x69,
+ 0xd5, 0x4a, 0x39, 0xa6, 0x1a, 0x85, 0x7f, 0xe0, 0x5c, 0xc3,
+ 0xb5, 0x2a, 0x96, 0x09, 0xf3, 0x6c, 0xd0, 0x4f, 0x36, 0xa9,
+ 0x15, 0x8a, 0x70, 0xef, 0x53, 0xcc, 0xba, 0x25, 0x99, 0x06,
+ 0xfc, 0x63, 0xdf, 0x40, 0x33, 0xac, 0x10, 0x8f, 0x75, 0xea,
+ 0x56, 0xc9, 0xbf, 0x20, 0x9c, 0x03, 0xf9, 0x66, 0xda, 0x45,
+ 0x00, 0xa0, 0x5d, 0xfd, 0xba, 0x1a, 0xe7, 0x47, 0x69, 0xc9,
+ 0x34, 0x94, 0xd3, 0x73, 0x8e, 0x2e, 0xd2, 0x72, 0x8f, 0x2f,
+ 0x68, 0xc8, 0x35, 0x95, 0xbb, 0x1b, 0xe6, 0x46, 0x01, 0xa1,
+ 0x5c, 0xfc, 0xb9, 0x19, 0xe4, 0x44, 0x03, 0xa3, 0x5e, 0xfe,
+ 0xd0, 0x70, 0x8d, 0x2d, 0x6a, 0xca, 0x37, 0x97, 0x6b, 0xcb,
+ 0x36, 0x96, 0xd1, 0x71, 0x8c, 0x2c, 0x02, 0xa2, 0x5f, 0xff,
+ 0xb8, 0x18, 0xe5, 0x45, 0x6f, 0xcf, 0x32, 0x92, 0xd5, 0x75,
+ 0x88, 0x28, 0x06, 0xa6, 0x5b, 0xfb, 0xbc, 0x1c, 0xe1, 0x41,
+ 0xbd, 0x1d, 0xe0, 0x40, 0x07, 0xa7, 0x5a, 0xfa, 0xd4, 0x74,
+ 0x89, 0x29, 0x6e, 0xce, 0x33, 0x93, 0xd6, 0x76, 0x8b, 0x2b,
+ 0x6c, 0xcc, 0x31, 0x91, 0xbf, 0x1f, 0xe2, 0x42, 0x05, 0xa5,
+ 0x58, 0xf8, 0x04, 0xa4, 0x59, 0xf9, 0xbe, 0x1e, 0xe3, 0x43,
+ 0x6d, 0xcd, 0x30, 0x90, 0xd7, 0x77, 0x8a, 0x2a, 0xde, 0x7e,
+ 0x83, 0x23, 0x64, 0xc4, 0x39, 0x99, 0xb7, 0x17, 0xea, 0x4a,
+ 0x0d, 0xad, 0x50, 0xf0, 0x0c, 0xac, 0x51, 0xf1, 0xb6, 0x16,
+ 0xeb, 0x4b, 0x65, 0xc5, 0x38, 0x98, 0xdf, 0x7f, 0x82, 0x22,
+ 0x67, 0xc7, 0x3a, 0x9a, 0xdd, 0x7d, 0x80, 0x20, 0x0e, 0xae,
+ 0x53, 0xf3, 0xb4, 0x14, 0xe9, 0x49, 0xb5, 0x15, 0xe8, 0x48,
+ 0x0f, 0xaf, 0x52, 0xf2, 0xdc, 0x7c, 0x81, 0x21, 0x66, 0xc6,
+ 0x3b, 0x9b, 0xb1, 0x11, 0xec, 0x4c, 0x0b, 0xab, 0x56, 0xf6,
+ 0xd8, 0x78, 0x85, 0x25, 0x62, 0xc2, 0x3f, 0x9f, 0x63, 0xc3,
+ 0x3e, 0x9e, 0xd9, 0x79, 0x84, 0x24, 0x0a, 0xaa, 0x57, 0xf7,
+ 0xb0, 0x10, 0xed, 0x4d, 0x08, 0xa8, 0x55, 0xf5, 0xb2, 0x12,
+ 0xef, 0x4f, 0x61, 0xc1, 0x3c, 0x9c, 0xdb, 0x7b, 0x86, 0x26,
+ 0xda, 0x7a, 0x87, 0x27, 0x60, 0xc0, 0x3d, 0x9d, 0xb3, 0x13,
+ 0xee, 0x4e, 0x09, 0xa9, 0x54, 0xf4, 0x00, 0xa1, 0x5f, 0xfe,
+ 0xbe, 0x1f, 0xe1, 0x40, 0x61, 0xc0, 0x3e, 0x9f, 0xdf, 0x7e,
+ 0x80, 0x21, 0xc2, 0x63, 0x9d, 0x3c, 0x7c, 0xdd, 0x23, 0x82,
+ 0xa3, 0x02, 0xfc, 0x5d, 0x1d, 0xbc, 0x42, 0xe3, 0x99, 0x38,
+ 0xc6, 0x67, 0x27, 0x86, 0x78, 0xd9, 0xf8, 0x59, 0xa7, 0x06,
+ 0x46, 0xe7, 0x19, 0xb8, 0x5b, 0xfa, 0x04, 0xa5, 0xe5, 0x44,
+ 0xba, 0x1b, 0x3a, 0x9b, 0x65, 0xc4, 0x84, 0x25, 0xdb, 0x7a,
+ 0x2f, 0x8e, 0x70, 0xd1, 0x91, 0x30, 0xce, 0x6f, 0x4e, 0xef,
+ 0x11, 0xb0, 0xf0, 0x51, 0xaf, 0x0e, 0xed, 0x4c, 0xb2, 0x13,
+ 0x53, 0xf2, 0x0c, 0xad, 0x8c, 0x2d, 0xd3, 0x72, 0x32, 0x93,
+ 0x6d, 0xcc, 0xb6, 0x17, 0xe9, 0x48, 0x08, 0xa9, 0x57, 0xf6,
+ 0xd7, 0x76, 0x88, 0x29, 0x69, 0xc8, 0x36, 0x97, 0x74, 0xd5,
+ 0x2b, 0x8a, 0xca, 0x6b, 0x95, 0x34, 0x15, 0xb4, 0x4a, 0xeb,
+ 0xab, 0x0a, 0xf4, 0x55, 0x5e, 0xff, 0x01, 0xa0, 0xe0, 0x41,
+ 0xbf, 0x1e, 0x3f, 0x9e, 0x60, 0xc1, 0x81, 0x20, 0xde, 0x7f,
+ 0x9c, 0x3d, 0xc3, 0x62, 0x22, 0x83, 0x7d, 0xdc, 0xfd, 0x5c,
+ 0xa2, 0x03, 0x43, 0xe2, 0x1c, 0xbd, 0xc7, 0x66, 0x98, 0x39,
+ 0x79, 0xd8, 0x26, 0x87, 0xa6, 0x07, 0xf9, 0x58, 0x18, 0xb9,
+ 0x47, 0xe6, 0x05, 0xa4, 0x5a, 0xfb, 0xbb, 0x1a, 0xe4, 0x45,
+ 0x64, 0xc5, 0x3b, 0x9a, 0xda, 0x7b, 0x85, 0x24, 0x71, 0xd0,
+ 0x2e, 0x8f, 0xcf, 0x6e, 0x90, 0x31, 0x10, 0xb1, 0x4f, 0xee,
+ 0xae, 0x0f, 0xf1, 0x50, 0xb3, 0x12, 0xec, 0x4d, 0x0d, 0xac,
+ 0x52, 0xf3, 0xd2, 0x73, 0x8d, 0x2c, 0x6c, 0xcd, 0x33, 0x92,
+ 0xe8, 0x49, 0xb7, 0x16, 0x56, 0xf7, 0x09, 0xa8, 0x89, 0x28,
+ 0xd6, 0x77, 0x37, 0x96, 0x68, 0xc9, 0x2a, 0x8b, 0x75, 0xd4,
+ 0x94, 0x35, 0xcb, 0x6a, 0x4b, 0xea, 0x14, 0xb5, 0xf5, 0x54,
+ 0xaa, 0x0b, 0x00, 0xa2, 0x59, 0xfb, 0xb2, 0x10, 0xeb, 0x49,
+ 0x79, 0xdb, 0x20, 0x82, 0xcb, 0x69, 0x92, 0x30, 0xf2, 0x50,
+ 0xab, 0x09, 0x40, 0xe2, 0x19, 0xbb, 0x8b, 0x29, 0xd2, 0x70,
+ 0x39, 0x9b, 0x60, 0xc2, 0xf9, 0x5b, 0xa0, 0x02, 0x4b, 0xe9,
+ 0x12, 0xb0, 0x80, 0x22, 0xd9, 0x7b, 0x32, 0x90, 0x6b, 0xc9,
+ 0x0b, 0xa9, 0x52, 0xf0, 0xb9, 0x1b, 0xe0, 0x42, 0x72, 0xd0,
+ 0x2b, 0x89, 0xc0, 0x62, 0x99, 0x3b, 0xef, 0x4d, 0xb6, 0x14,
+ 0x5d, 0xff, 0x04, 0xa6, 0x96, 0x34, 0xcf, 0x6d, 0x24, 0x86,
+ 0x7d, 0xdf, 0x1d, 0xbf, 0x44, 0xe6, 0xaf, 0x0d, 0xf6, 0x54,
+ 0x64, 0xc6, 0x3d, 0x9f, 0xd6, 0x74, 0x8f, 0x2d, 0x16, 0xb4,
+ 0x4f, 0xed, 0xa4, 0x06, 0xfd, 0x5f, 0x6f, 0xcd, 0x36, 0x94,
+ 0xdd, 0x7f, 0x84, 0x26, 0xe4, 0x46, 0xbd, 0x1f, 0x56, 0xf4,
+ 0x0f, 0xad, 0x9d, 0x3f, 0xc4, 0x66, 0x2f, 0x8d, 0x76, 0xd4,
+ 0xc3, 0x61, 0x9a, 0x38, 0x71, 0xd3, 0x28, 0x8a, 0xba, 0x18,
+ 0xe3, 0x41, 0x08, 0xaa, 0x51, 0xf3, 0x31, 0x93, 0x68, 0xca,
+ 0x83, 0x21, 0xda, 0x78, 0x48, 0xea, 0x11, 0xb3, 0xfa, 0x58,
+ 0xa3, 0x01, 0x3a, 0x98, 0x63, 0xc1, 0x88, 0x2a, 0xd1, 0x73,
+ 0x43, 0xe1, 0x1a, 0xb8, 0xf1, 0x53, 0xa8, 0x0a, 0xc8, 0x6a,
+ 0x91, 0x33, 0x7a, 0xd8, 0x23, 0x81, 0xb1, 0x13, 0xe8, 0x4a,
+ 0x03, 0xa1, 0x5a, 0xf8, 0x2c, 0x8e, 0x75, 0xd7, 0x9e, 0x3c,
+ 0xc7, 0x65, 0x55, 0xf7, 0x0c, 0xae, 0xe7, 0x45, 0xbe, 0x1c,
+ 0xde, 0x7c, 0x87, 0x25, 0x6c, 0xce, 0x35, 0x97, 0xa7, 0x05,
+ 0xfe, 0x5c, 0x15, 0xb7, 0x4c, 0xee, 0xd5, 0x77, 0x8c, 0x2e,
+ 0x67, 0xc5, 0x3e, 0x9c, 0xac, 0x0e, 0xf5, 0x57, 0x1e, 0xbc,
+ 0x47, 0xe5, 0x27, 0x85, 0x7e, 0xdc, 0x95, 0x37, 0xcc, 0x6e,
+ 0x5e, 0xfc, 0x07, 0xa5, 0xec, 0x4e, 0xb5, 0x17, 0x00, 0xa3,
+ 0x5b, 0xf8, 0xb6, 0x15, 0xed, 0x4e, 0x71, 0xd2, 0x2a, 0x89,
+ 0xc7, 0x64, 0x9c, 0x3f, 0xe2, 0x41, 0xb9, 0x1a, 0x54, 0xf7,
+ 0x0f, 0xac, 0x93, 0x30, 0xc8, 0x6b, 0x25, 0x86, 0x7e, 0xdd,
+ 0xd9, 0x7a, 0x82, 0x21, 0x6f, 0xcc, 0x34, 0x97, 0xa8, 0x0b,
+ 0xf3, 0x50, 0x1e, 0xbd, 0x45, 0xe6, 0x3b, 0x98, 0x60, 0xc3,
+ 0x8d, 0x2e, 0xd6, 0x75, 0x4a, 0xe9, 0x11, 0xb2, 0xfc, 0x5f,
+ 0xa7, 0x04, 0xaf, 0x0c, 0xf4, 0x57, 0x19, 0xba, 0x42, 0xe1,
+ 0xde, 0x7d, 0x85, 0x26, 0x68, 0xcb, 0x33, 0x90, 0x4d, 0xee,
+ 0x16, 0xb5, 0xfb, 0x58, 0xa0, 0x03, 0x3c, 0x9f, 0x67, 0xc4,
+ 0x8a, 0x29, 0xd1, 0x72, 0x76, 0xd5, 0x2d, 0x8e, 0xc0, 0x63,
+ 0x9b, 0x38, 0x07, 0xa4, 0x5c, 0xff, 0xb1, 0x12, 0xea, 0x49,
+ 0x94, 0x37, 0xcf, 0x6c, 0x22, 0x81, 0x79, 0xda, 0xe5, 0x46,
+ 0xbe, 0x1d, 0x53, 0xf0, 0x08, 0xab, 0x43, 0xe0, 0x18, 0xbb,
+ 0xf5, 0x56, 0xae, 0x0d, 0x32, 0x91, 0x69, 0xca, 0x84, 0x27,
+ 0xdf, 0x7c, 0xa1, 0x02, 0xfa, 0x59, 0x17, 0xb4, 0x4c, 0xef,
+ 0xd0, 0x73, 0x8b, 0x28, 0x66, 0xc5, 0x3d, 0x9e, 0x9a, 0x39,
+ 0xc1, 0x62, 0x2c, 0x8f, 0x77, 0xd4, 0xeb, 0x48, 0xb0, 0x13,
+ 0x5d, 0xfe, 0x06, 0xa5, 0x78, 0xdb, 0x23, 0x80, 0xce, 0x6d,
+ 0x95, 0x36, 0x09, 0xaa, 0x52, 0xf1, 0xbf, 0x1c, 0xe4, 0x47,
+ 0xec, 0x4f, 0xb7, 0x14, 0x5a, 0xf9, 0x01, 0xa2, 0x9d, 0x3e,
+ 0xc6, 0x65, 0x2b, 0x88, 0x70, 0xd3, 0x0e, 0xad, 0x55, 0xf6,
+ 0xb8, 0x1b, 0xe3, 0x40, 0x7f, 0xdc, 0x24, 0x87, 0xc9, 0x6a,
+ 0x92, 0x31, 0x35, 0x96, 0x6e, 0xcd, 0x83, 0x20, 0xd8, 0x7b,
+ 0x44, 0xe7, 0x1f, 0xbc, 0xf2, 0x51, 0xa9, 0x0a, 0xd7, 0x74,
+ 0x8c, 0x2f, 0x61, 0xc2, 0x3a, 0x99, 0xa6, 0x05, 0xfd, 0x5e,
+ 0x10, 0xb3, 0x4b, 0xe8, 0x00, 0xa4, 0x55, 0xf1, 0xaa, 0x0e,
+ 0xff, 0x5b, 0x49, 0xed, 0x1c, 0xb8, 0xe3, 0x47, 0xb6, 0x12,
+ 0x92, 0x36, 0xc7, 0x63, 0x38, 0x9c, 0x6d, 0xc9, 0xdb, 0x7f,
+ 0x8e, 0x2a, 0x71, 0xd5, 0x24, 0x80, 0x39, 0x9d, 0x6c, 0xc8,
+ 0x93, 0x37, 0xc6, 0x62, 0x70, 0xd4, 0x25, 0x81, 0xda, 0x7e,
+ 0x8f, 0x2b, 0xab, 0x0f, 0xfe, 0x5a, 0x01, 0xa5, 0x54, 0xf0,
+ 0xe2, 0x46, 0xb7, 0x13, 0x48, 0xec, 0x1d, 0xb9, 0x72, 0xd6,
+ 0x27, 0x83, 0xd8, 0x7c, 0x8d, 0x29, 0x3b, 0x9f, 0x6e, 0xca,
+ 0x91, 0x35, 0xc4, 0x60, 0xe0, 0x44, 0xb5, 0x11, 0x4a, 0xee,
+ 0x1f, 0xbb, 0xa9, 0x0d, 0xfc, 0x58, 0x03, 0xa7, 0x56, 0xf2,
+ 0x4b, 0xef, 0x1e, 0xba, 0xe1, 0x45, 0xb4, 0x10, 0x02, 0xa6,
+ 0x57, 0xf3, 0xa8, 0x0c, 0xfd, 0x59, 0xd9, 0x7d, 0x8c, 0x28,
+ 0x73, 0xd7, 0x26, 0x82, 0x90, 0x34, 0xc5, 0x61, 0x3a, 0x9e,
+ 0x6f, 0xcb, 0xe4, 0x40, 0xb1, 0x15, 0x4e, 0xea, 0x1b, 0xbf,
+ 0xad, 0x09, 0xf8, 0x5c, 0x07, 0xa3, 0x52, 0xf6, 0x76, 0xd2,
+ 0x23, 0x87, 0xdc, 0x78, 0x89, 0x2d, 0x3f, 0x9b, 0x6a, 0xce,
+ 0x95, 0x31, 0xc0, 0x64, 0xdd, 0x79, 0x88, 0x2c, 0x77, 0xd3,
+ 0x22, 0x86, 0x94, 0x30, 0xc1, 0x65, 0x3e, 0x9a, 0x6b, 0xcf,
+ 0x4f, 0xeb, 0x1a, 0xbe, 0xe5, 0x41, 0xb0, 0x14, 0x06, 0xa2,
+ 0x53, 0xf7, 0xac, 0x08, 0xf9, 0x5d, 0x96, 0x32, 0xc3, 0x67,
+ 0x3c, 0x98, 0x69, 0xcd, 0xdf, 0x7b, 0x8a, 0x2e, 0x75, 0xd1,
+ 0x20, 0x84, 0x04, 0xa0, 0x51, 0xf5, 0xae, 0x0a, 0xfb, 0x5f,
+ 0x4d, 0xe9, 0x18, 0xbc, 0xe7, 0x43, 0xb2, 0x16, 0xaf, 0x0b,
+ 0xfa, 0x5e, 0x05, 0xa1, 0x50, 0xf4, 0xe6, 0x42, 0xb3, 0x17,
+ 0x4c, 0xe8, 0x19, 0xbd, 0x3d, 0x99, 0x68, 0xcc, 0x97, 0x33,
+ 0xc2, 0x66, 0x74, 0xd0, 0x21, 0x85, 0xde, 0x7a, 0x8b, 0x2f,
+ 0x00, 0xa5, 0x57, 0xf2, 0xae, 0x0b, 0xf9, 0x5c, 0x41, 0xe4,
+ 0x16, 0xb3, 0xef, 0x4a, 0xb8, 0x1d, 0x82, 0x27, 0xd5, 0x70,
+ 0x2c, 0x89, 0x7b, 0xde, 0xc3, 0x66, 0x94, 0x31, 0x6d, 0xc8,
+ 0x3a, 0x9f, 0x19, 0xbc, 0x4e, 0xeb, 0xb7, 0x12, 0xe0, 0x45,
+ 0x58, 0xfd, 0x0f, 0xaa, 0xf6, 0x53, 0xa1, 0x04, 0x9b, 0x3e,
+ 0xcc, 0x69, 0x35, 0x90, 0x62, 0xc7, 0xda, 0x7f, 0x8d, 0x28,
+ 0x74, 0xd1, 0x23, 0x86, 0x32, 0x97, 0x65, 0xc0, 0x9c, 0x39,
+ 0xcb, 0x6e, 0x73, 0xd6, 0x24, 0x81, 0xdd, 0x78, 0x8a, 0x2f,
+ 0xb0, 0x15, 0xe7, 0x42, 0x1e, 0xbb, 0x49, 0xec, 0xf1, 0x54,
+ 0xa6, 0x03, 0x5f, 0xfa, 0x08, 0xad, 0x2b, 0x8e, 0x7c, 0xd9,
+ 0x85, 0x20, 0xd2, 0x77, 0x6a, 0xcf, 0x3d, 0x98, 0xc4, 0x61,
+ 0x93, 0x36, 0xa9, 0x0c, 0xfe, 0x5b, 0x07, 0xa2, 0x50, 0xf5,
+ 0xe8, 0x4d, 0xbf, 0x1a, 0x46, 0xe3, 0x11, 0xb4, 0x64, 0xc1,
+ 0x33, 0x96, 0xca, 0x6f, 0x9d, 0x38, 0x25, 0x80, 0x72, 0xd7,
+ 0x8b, 0x2e, 0xdc, 0x79, 0xe6, 0x43, 0xb1, 0x14, 0x48, 0xed,
+ 0x1f, 0xba, 0xa7, 0x02, 0xf0, 0x55, 0x09, 0xac, 0x5e, 0xfb,
+ 0x7d, 0xd8, 0x2a, 0x8f, 0xd3, 0x76, 0x84, 0x21, 0x3c, 0x99,
+ 0x6b, 0xce, 0x92, 0x37, 0xc5, 0x60, 0xff, 0x5a, 0xa8, 0x0d,
+ 0x51, 0xf4, 0x06, 0xa3, 0xbe, 0x1b, 0xe9, 0x4c, 0x10, 0xb5,
+ 0x47, 0xe2, 0x56, 0xf3, 0x01, 0xa4, 0xf8, 0x5d, 0xaf, 0x0a,
+ 0x17, 0xb2, 0x40, 0xe5, 0xb9, 0x1c, 0xee, 0x4b, 0xd4, 0x71,
+ 0x83, 0x26, 0x7a, 0xdf, 0x2d, 0x88, 0x95, 0x30, 0xc2, 0x67,
+ 0x3b, 0x9e, 0x6c, 0xc9, 0x4f, 0xea, 0x18, 0xbd, 0xe1, 0x44,
+ 0xb6, 0x13, 0x0e, 0xab, 0x59, 0xfc, 0xa0, 0x05, 0xf7, 0x52,
+ 0xcd, 0x68, 0x9a, 0x3f, 0x63, 0xc6, 0x34, 0x91, 0x8c, 0x29,
+ 0xdb, 0x7e, 0x22, 0x87, 0x75, 0xd0, 0x00, 0xa6, 0x51, 0xf7,
+ 0xa2, 0x04, 0xf3, 0x55, 0x59, 0xff, 0x08, 0xae, 0xfb, 0x5d,
+ 0xaa, 0x0c, 0xb2, 0x14, 0xe3, 0x45, 0x10, 0xb6, 0x41, 0xe7,
+ 0xeb, 0x4d, 0xba, 0x1c, 0x49, 0xef, 0x18, 0xbe, 0x79, 0xdf,
+ 0x28, 0x8e, 0xdb, 0x7d, 0x8a, 0x2c, 0x20, 0x86, 0x71, 0xd7,
+ 0x82, 0x24, 0xd3, 0x75, 0xcb, 0x6d, 0x9a, 0x3c, 0x69, 0xcf,
+ 0x38, 0x9e, 0x92, 0x34, 0xc3, 0x65, 0x30, 0x96, 0x61, 0xc7,
+ 0xf2, 0x54, 0xa3, 0x05, 0x50, 0xf6, 0x01, 0xa7, 0xab, 0x0d,
+ 0xfa, 0x5c, 0x09, 0xaf, 0x58, 0xfe, 0x40, 0xe6, 0x11, 0xb7,
+ 0xe2, 0x44, 0xb3, 0x15, 0x19, 0xbf, 0x48, 0xee, 0xbb, 0x1d,
+ 0xea, 0x4c, 0x8b, 0x2d, 0xda, 0x7c, 0x29, 0x8f, 0x78, 0xde,
+ 0xd2, 0x74, 0x83, 0x25, 0x70, 0xd6, 0x21, 0x87, 0x39, 0x9f,
+ 0x68, 0xce, 0x9b, 0x3d, 0xca, 0x6c, 0x60, 0xc6, 0x31, 0x97,
+ 0xc2, 0x64, 0x93, 0x35, 0xf9, 0x5f, 0xa8, 0x0e, 0x5b, 0xfd,
+ 0x0a, 0xac, 0xa0, 0x06, 0xf1, 0x57, 0x02, 0xa4, 0x53, 0xf5,
+ 0x4b, 0xed, 0x1a, 0xbc, 0xe9, 0x4f, 0xb8, 0x1e, 0x12, 0xb4,
+ 0x43, 0xe5, 0xb0, 0x16, 0xe1, 0x47, 0x80, 0x26, 0xd1, 0x77,
+ 0x22, 0x84, 0x73, 0xd5, 0xd9, 0x7f, 0x88, 0x2e, 0x7b, 0xdd,
+ 0x2a, 0x8c, 0x32, 0x94, 0x63, 0xc5, 0x90, 0x36, 0xc1, 0x67,
+ 0x6b, 0xcd, 0x3a, 0x9c, 0xc9, 0x6f, 0x98, 0x3e, 0x0b, 0xad,
+ 0x5a, 0xfc, 0xa9, 0x0f, 0xf8, 0x5e, 0x52, 0xf4, 0x03, 0xa5,
+ 0xf0, 0x56, 0xa1, 0x07, 0xb9, 0x1f, 0xe8, 0x4e, 0x1b, 0xbd,
+ 0x4a, 0xec, 0xe0, 0x46, 0xb1, 0x17, 0x42, 0xe4, 0x13, 0xb5,
+ 0x72, 0xd4, 0x23, 0x85, 0xd0, 0x76, 0x81, 0x27, 0x2b, 0x8d,
+ 0x7a, 0xdc, 0x89, 0x2f, 0xd8, 0x7e, 0xc0, 0x66, 0x91, 0x37,
+ 0x62, 0xc4, 0x33, 0x95, 0x99, 0x3f, 0xc8, 0x6e, 0x3b, 0x9d,
+ 0x6a, 0xcc, 0x00, 0xa7, 0x53, 0xf4, 0xa6, 0x01, 0xf5, 0x52,
+ 0x51, 0xf6, 0x02, 0xa5, 0xf7, 0x50, 0xa4, 0x03, 0xa2, 0x05,
+ 0xf1, 0x56, 0x04, 0xa3, 0x57, 0xf0, 0xf3, 0x54, 0xa0, 0x07,
+ 0x55, 0xf2, 0x06, 0xa1, 0x59, 0xfe, 0x0a, 0xad, 0xff, 0x58,
+ 0xac, 0x0b, 0x08, 0xaf, 0x5b, 0xfc, 0xae, 0x09, 0xfd, 0x5a,
+ 0xfb, 0x5c, 0xa8, 0x0f, 0x5d, 0xfa, 0x0e, 0xa9, 0xaa, 0x0d,
+ 0xf9, 0x5e, 0x0c, 0xab, 0x5f, 0xf8, 0xb2, 0x15, 0xe1, 0x46,
+ 0x14, 0xb3, 0x47, 0xe0, 0xe3, 0x44, 0xb0, 0x17, 0x45, 0xe2,
+ 0x16, 0xb1, 0x10, 0xb7, 0x43, 0xe4, 0xb6, 0x11, 0xe5, 0x42,
+ 0x41, 0xe6, 0x12, 0xb5, 0xe7, 0x40, 0xb4, 0x13, 0xeb, 0x4c,
+ 0xb8, 0x1f, 0x4d, 0xea, 0x1e, 0xb9, 0xba, 0x1d, 0xe9, 0x4e,
+ 0x1c, 0xbb, 0x4f, 0xe8, 0x49, 0xee, 0x1a, 0xbd, 0xef, 0x48,
+ 0xbc, 0x1b, 0x18, 0xbf, 0x4b, 0xec, 0xbe, 0x19, 0xed, 0x4a,
+ 0x79, 0xde, 0x2a, 0x8d, 0xdf, 0x78, 0x8c, 0x2b, 0x28, 0x8f,
+ 0x7b, 0xdc, 0x8e, 0x29, 0xdd, 0x7a, 0xdb, 0x7c, 0x88, 0x2f,
+ 0x7d, 0xda, 0x2e, 0x89, 0x8a, 0x2d, 0xd9, 0x7e, 0x2c, 0x8b,
+ 0x7f, 0xd8, 0x20, 0x87, 0x73, 0xd4, 0x86, 0x21, 0xd5, 0x72,
+ 0x71, 0xd6, 0x22, 0x85, 0xd7, 0x70, 0x84, 0x23, 0x82, 0x25,
+ 0xd1, 0x76, 0x24, 0x83, 0x77, 0xd0, 0xd3, 0x74, 0x80, 0x27,
+ 0x75, 0xd2, 0x26, 0x81, 0xcb, 0x6c, 0x98, 0x3f, 0x6d, 0xca,
+ 0x3e, 0x99, 0x9a, 0x3d, 0xc9, 0x6e, 0x3c, 0x9b, 0x6f, 0xc8,
+ 0x69, 0xce, 0x3a, 0x9d, 0xcf, 0x68, 0x9c, 0x3b, 0x38, 0x9f,
+ 0x6b, 0xcc, 0x9e, 0x39, 0xcd, 0x6a, 0x92, 0x35, 0xc1, 0x66,
+ 0x34, 0x93, 0x67, 0xc0, 0xc3, 0x64, 0x90, 0x37, 0x65, 0xc2,
+ 0x36, 0x91, 0x30, 0x97, 0x63, 0xc4, 0x96, 0x31, 0xc5, 0x62,
+ 0x61, 0xc6, 0x32, 0x95, 0xc7, 0x60, 0x94, 0x33, 0x00, 0xa8,
+ 0x4d, 0xe5, 0x9a, 0x32, 0xd7, 0x7f, 0x29, 0x81, 0x64, 0xcc,
+ 0xb3, 0x1b, 0xfe, 0x56, 0x52, 0xfa, 0x1f, 0xb7, 0xc8, 0x60,
+ 0x85, 0x2d, 0x7b, 0xd3, 0x36, 0x9e, 0xe1, 0x49, 0xac, 0x04,
+ 0xa4, 0x0c, 0xe9, 0x41, 0x3e, 0x96, 0x73, 0xdb, 0x8d, 0x25,
+ 0xc0, 0x68, 0x17, 0xbf, 0x5a, 0xf2, 0xf6, 0x5e, 0xbb, 0x13,
+ 0x6c, 0xc4, 0x21, 0x89, 0xdf, 0x77, 0x92, 0x3a, 0x45, 0xed,
+ 0x08, 0xa0, 0x55, 0xfd, 0x18, 0xb0, 0xcf, 0x67, 0x82, 0x2a,
+ 0x7c, 0xd4, 0x31, 0x99, 0xe6, 0x4e, 0xab, 0x03, 0x07, 0xaf,
+ 0x4a, 0xe2, 0x9d, 0x35, 0xd0, 0x78, 0x2e, 0x86, 0x63, 0xcb,
+ 0xb4, 0x1c, 0xf9, 0x51, 0xf1, 0x59, 0xbc, 0x14, 0x6b, 0xc3,
+ 0x26, 0x8e, 0xd8, 0x70, 0x95, 0x3d, 0x42, 0xea, 0x0f, 0xa7,
+ 0xa3, 0x0b, 0xee, 0x46, 0x39, 0x91, 0x74, 0xdc, 0x8a, 0x22,
+ 0xc7, 0x6f, 0x10, 0xb8, 0x5d, 0xf5, 0xaa, 0x02, 0xe7, 0x4f,
+ 0x30, 0x98, 0x7d, 0xd5, 0x83, 0x2b, 0xce, 0x66, 0x19, 0xb1,
+ 0x54, 0xfc, 0xf8, 0x50, 0xb5, 0x1d, 0x62, 0xca, 0x2f, 0x87,
+ 0xd1, 0x79, 0x9c, 0x34, 0x4b, 0xe3, 0x06, 0xae, 0x0e, 0xa6,
+ 0x43, 0xeb, 0x94, 0x3c, 0xd9, 0x71, 0x27, 0x8f, 0x6a, 0xc2,
+ 0xbd, 0x15, 0xf0, 0x58, 0x5c, 0xf4, 0x11, 0xb9, 0xc6, 0x6e,
+ 0x8b, 0x23, 0x75, 0xdd, 0x38, 0x90, 0xef, 0x47, 0xa2, 0x0a,
+ 0xff, 0x57, 0xb2, 0x1a, 0x65, 0xcd, 0x28, 0x80, 0xd6, 0x7e,
+ 0x9b, 0x33, 0x4c, 0xe4, 0x01, 0xa9, 0xad, 0x05, 0xe0, 0x48,
+ 0x37, 0x9f, 0x7a, 0xd2, 0x84, 0x2c, 0xc9, 0x61, 0x1e, 0xb6,
+ 0x53, 0xfb, 0x5b, 0xf3, 0x16, 0xbe, 0xc1, 0x69, 0x8c, 0x24,
+ 0x72, 0xda, 0x3f, 0x97, 0xe8, 0x40, 0xa5, 0x0d, 0x09, 0xa1,
+ 0x44, 0xec, 0x93, 0x3b, 0xde, 0x76, 0x20, 0x88, 0x6d, 0xc5,
+ 0xba, 0x12, 0xf7, 0x5f, 0x00, 0xa9, 0x4f, 0xe6, 0x9e, 0x37,
+ 0xd1, 0x78, 0x21, 0x88, 0x6e, 0xc7, 0xbf, 0x16, 0xf0, 0x59,
+ 0x42, 0xeb, 0x0d, 0xa4, 0xdc, 0x75, 0x93, 0x3a, 0x63, 0xca,
+ 0x2c, 0x85, 0xfd, 0x54, 0xb2, 0x1b, 0x84, 0x2d, 0xcb, 0x62,
+ 0x1a, 0xb3, 0x55, 0xfc, 0xa5, 0x0c, 0xea, 0x43, 0x3b, 0x92,
+ 0x74, 0xdd, 0xc6, 0x6f, 0x89, 0x20, 0x58, 0xf1, 0x17, 0xbe,
+ 0xe7, 0x4e, 0xa8, 0x01, 0x79, 0xd0, 0x36, 0x9f, 0x15, 0xbc,
+ 0x5a, 0xf3, 0x8b, 0x22, 0xc4, 0x6d, 0x34, 0x9d, 0x7b, 0xd2,
+ 0xaa, 0x03, 0xe5, 0x4c, 0x57, 0xfe, 0x18, 0xb1, 0xc9, 0x60,
+ 0x86, 0x2f, 0x76, 0xdf, 0x39, 0x90, 0xe8, 0x41, 0xa7, 0x0e,
+ 0x91, 0x38, 0xde, 0x77, 0x0f, 0xa6, 0x40, 0xe9, 0xb0, 0x19,
+ 0xff, 0x56, 0x2e, 0x87, 0x61, 0xc8, 0xd3, 0x7a, 0x9c, 0x35,
+ 0x4d, 0xe4, 0x02, 0xab, 0xf2, 0x5b, 0xbd, 0x14, 0x6c, 0xc5,
+ 0x23, 0x8a, 0x2a, 0x83, 0x65, 0xcc, 0xb4, 0x1d, 0xfb, 0x52,
+ 0x0b, 0xa2, 0x44, 0xed, 0x95, 0x3c, 0xda, 0x73, 0x68, 0xc1,
+ 0x27, 0x8e, 0xf6, 0x5f, 0xb9, 0x10, 0x49, 0xe0, 0x06, 0xaf,
+ 0xd7, 0x7e, 0x98, 0x31, 0xae, 0x07, 0xe1, 0x48, 0x30, 0x99,
+ 0x7f, 0xd6, 0x8f, 0x26, 0xc0, 0x69, 0x11, 0xb8, 0x5e, 0xf7,
+ 0xec, 0x45, 0xa3, 0x0a, 0x72, 0xdb, 0x3d, 0x94, 0xcd, 0x64,
+ 0x82, 0x2b, 0x53, 0xfa, 0x1c, 0xb5, 0x3f, 0x96, 0x70, 0xd9,
+ 0xa1, 0x08, 0xee, 0x47, 0x1e, 0xb7, 0x51, 0xf8, 0x80, 0x29,
+ 0xcf, 0x66, 0x7d, 0xd4, 0x32, 0x9b, 0xe3, 0x4a, 0xac, 0x05,
+ 0x5c, 0xf5, 0x13, 0xba, 0xc2, 0x6b, 0x8d, 0x24, 0xbb, 0x12,
+ 0xf4, 0x5d, 0x25, 0x8c, 0x6a, 0xc3, 0x9a, 0x33, 0xd5, 0x7c,
+ 0x04, 0xad, 0x4b, 0xe2, 0xf9, 0x50, 0xb6, 0x1f, 0x67, 0xce,
+ 0x28, 0x81, 0xd8, 0x71, 0x97, 0x3e, 0x46, 0xef, 0x09, 0xa0,
+ 0x00, 0xaa, 0x49, 0xe3, 0x92, 0x38, 0xdb, 0x71, 0x39, 0x93,
+ 0x70, 0xda, 0xab, 0x01, 0xe2, 0x48, 0x72, 0xd8, 0x3b, 0x91,
+ 0xe0, 0x4a, 0xa9, 0x03, 0x4b, 0xe1, 0x02, 0xa8, 0xd9, 0x73,
+ 0x90, 0x3a, 0xe4, 0x4e, 0xad, 0x07, 0x76, 0xdc, 0x3f, 0x95,
+ 0xdd, 0x77, 0x94, 0x3e, 0x4f, 0xe5, 0x06, 0xac, 0x96, 0x3c,
+ 0xdf, 0x75, 0x04, 0xae, 0x4d, 0xe7, 0xaf, 0x05, 0xe6, 0x4c,
+ 0x3d, 0x97, 0x74, 0xde, 0xd5, 0x7f, 0x9c, 0x36, 0x47, 0xed,
+ 0x0e, 0xa4, 0xec, 0x46, 0xa5, 0x0f, 0x7e, 0xd4, 0x37, 0x9d,
+ 0xa7, 0x0d, 0xee, 0x44, 0x35, 0x9f, 0x7c, 0xd6, 0x9e, 0x34,
+ 0xd7, 0x7d, 0x0c, 0xa6, 0x45, 0xef, 0x31, 0x9b, 0x78, 0xd2,
+ 0xa3, 0x09, 0xea, 0x40, 0x08, 0xa2, 0x41, 0xeb, 0x9a, 0x30,
+ 0xd3, 0x79, 0x43, 0xe9, 0x0a, 0xa0, 0xd1, 0x7b, 0x98, 0x32,
+ 0x7a, 0xd0, 0x33, 0x99, 0xe8, 0x42, 0xa1, 0x0b, 0xb7, 0x1d,
+ 0xfe, 0x54, 0x25, 0x8f, 0x6c, 0xc6, 0x8e, 0x24, 0xc7, 0x6d,
+ 0x1c, 0xb6, 0x55, 0xff, 0xc5, 0x6f, 0x8c, 0x26, 0x57, 0xfd,
+ 0x1e, 0xb4, 0xfc, 0x56, 0xb5, 0x1f, 0x6e, 0xc4, 0x27, 0x8d,
+ 0x53, 0xf9, 0x1a, 0xb0, 0xc1, 0x6b, 0x88, 0x22, 0x6a, 0xc0,
+ 0x23, 0x89, 0xf8, 0x52, 0xb1, 0x1b, 0x21, 0x8b, 0x68, 0xc2,
+ 0xb3, 0x19, 0xfa, 0x50, 0x18, 0xb2, 0x51, 0xfb, 0x8a, 0x20,
+ 0xc3, 0x69, 0x62, 0xc8, 0x2b, 0x81, 0xf0, 0x5a, 0xb9, 0x13,
+ 0x5b, 0xf1, 0x12, 0xb8, 0xc9, 0x63, 0x80, 0x2a, 0x10, 0xba,
+ 0x59, 0xf3, 0x82, 0x28, 0xcb, 0x61, 0x29, 0x83, 0x60, 0xca,
+ 0xbb, 0x11, 0xf2, 0x58, 0x86, 0x2c, 0xcf, 0x65, 0x14, 0xbe,
+ 0x5d, 0xf7, 0xbf, 0x15, 0xf6, 0x5c, 0x2d, 0x87, 0x64, 0xce,
+ 0xf4, 0x5e, 0xbd, 0x17, 0x66, 0xcc, 0x2f, 0x85, 0xcd, 0x67,
+ 0x84, 0x2e, 0x5f, 0xf5, 0x16, 0xbc, 0x00, 0xab, 0x4b, 0xe0,
+ 0x96, 0x3d, 0xdd, 0x76, 0x31, 0x9a, 0x7a, 0xd1, 0xa7, 0x0c,
+ 0xec, 0x47, 0x62, 0xc9, 0x29, 0x82, 0xf4, 0x5f, 0xbf, 0x14,
+ 0x53, 0xf8, 0x18, 0xb3, 0xc5, 0x6e, 0x8e, 0x25, 0xc4, 0x6f,
+ 0x8f, 0x24, 0x52, 0xf9, 0x19, 0xb2, 0xf5, 0x5e, 0xbe, 0x15,
+ 0x63, 0xc8, 0x28, 0x83, 0xa6, 0x0d, 0xed, 0x46, 0x30, 0x9b,
+ 0x7b, 0xd0, 0x97, 0x3c, 0xdc, 0x77, 0x01, 0xaa, 0x4a, 0xe1,
+ 0x95, 0x3e, 0xde, 0x75, 0x03, 0xa8, 0x48, 0xe3, 0xa4, 0x0f,
+ 0xef, 0x44, 0x32, 0x99, 0x79, 0xd2, 0xf7, 0x5c, 0xbc, 0x17,
+ 0x61, 0xca, 0x2a, 0x81, 0xc6, 0x6d, 0x8d, 0x26, 0x50, 0xfb,
+ 0x1b, 0xb0, 0x51, 0xfa, 0x1a, 0xb1, 0xc7, 0x6c, 0x8c, 0x27,
+ 0x60, 0xcb, 0x2b, 0x80, 0xf6, 0x5d, 0xbd, 0x16, 0x33, 0x98,
+ 0x78, 0xd3, 0xa5, 0x0e, 0xee, 0x45, 0x02, 0xa9, 0x49, 0xe2,
+ 0x94, 0x3f, 0xdf, 0x74, 0x37, 0x9c, 0x7c, 0xd7, 0xa1, 0x0a,
+ 0xea, 0x41, 0x06, 0xad, 0x4d, 0xe6, 0x90, 0x3b, 0xdb, 0x70,
+ 0x55, 0xfe, 0x1e, 0xb5, 0xc3, 0x68, 0x88, 0x23, 0x64, 0xcf,
+ 0x2f, 0x84, 0xf2, 0x59, 0xb9, 0x12, 0xf3, 0x58, 0xb8, 0x13,
+ 0x65, 0xce, 0x2e, 0x85, 0xc2, 0x69, 0x89, 0x22, 0x54, 0xff,
+ 0x1f, 0xb4, 0x91, 0x3a, 0xda, 0x71, 0x07, 0xac, 0x4c, 0xe7,
+ 0xa0, 0x0b, 0xeb, 0x40, 0x36, 0x9d, 0x7d, 0xd6, 0xa2, 0x09,
+ 0xe9, 0x42, 0x34, 0x9f, 0x7f, 0xd4, 0x93, 0x38, 0xd8, 0x73,
+ 0x05, 0xae, 0x4e, 0xe5, 0xc0, 0x6b, 0x8b, 0x20, 0x56, 0xfd,
+ 0x1d, 0xb6, 0xf1, 0x5a, 0xba, 0x11, 0x67, 0xcc, 0x2c, 0x87,
+ 0x66, 0xcd, 0x2d, 0x86, 0xf0, 0x5b, 0xbb, 0x10, 0x57, 0xfc,
+ 0x1c, 0xb7, 0xc1, 0x6a, 0x8a, 0x21, 0x04, 0xaf, 0x4f, 0xe4,
+ 0x92, 0x39, 0xd9, 0x72, 0x35, 0x9e, 0x7e, 0xd5, 0xa3, 0x08,
+ 0xe8, 0x43, 0x00, 0xac, 0x45, 0xe9, 0x8a, 0x26, 0xcf, 0x63,
+ 0x09, 0xa5, 0x4c, 0xe0, 0x83, 0x2f, 0xc6, 0x6a, 0x12, 0xbe,
+ 0x57, 0xfb, 0x98, 0x34, 0xdd, 0x71, 0x1b, 0xb7, 0x5e, 0xf2,
+ 0x91, 0x3d, 0xd4, 0x78, 0x24, 0x88, 0x61, 0xcd, 0xae, 0x02,
+ 0xeb, 0x47, 0x2d, 0x81, 0x68, 0xc4, 0xa7, 0x0b, 0xe2, 0x4e,
+ 0x36, 0x9a, 0x73, 0xdf, 0xbc, 0x10, 0xf9, 0x55, 0x3f, 0x93,
+ 0x7a, 0xd6, 0xb5, 0x19, 0xf0, 0x5c, 0x48, 0xe4, 0x0d, 0xa1,
+ 0xc2, 0x6e, 0x87, 0x2b, 0x41, 0xed, 0x04, 0xa8, 0xcb, 0x67,
+ 0x8e, 0x22, 0x5a, 0xf6, 0x1f, 0xb3, 0xd0, 0x7c, 0x95, 0x39,
+ 0x53, 0xff, 0x16, 0xba, 0xd9, 0x75, 0x9c, 0x30, 0x6c, 0xc0,
+ 0x29, 0x85, 0xe6, 0x4a, 0xa3, 0x0f, 0x65, 0xc9, 0x20, 0x8c,
+ 0xef, 0x43, 0xaa, 0x06, 0x7e, 0xd2, 0x3b, 0x97, 0xf4, 0x58,
+ 0xb1, 0x1d, 0x77, 0xdb, 0x32, 0x9e, 0xfd, 0x51, 0xb8, 0x14,
+ 0x90, 0x3c, 0xd5, 0x79, 0x1a, 0xb6, 0x5f, 0xf3, 0x99, 0x35,
+ 0xdc, 0x70, 0x13, 0xbf, 0x56, 0xfa, 0x82, 0x2e, 0xc7, 0x6b,
+ 0x08, 0xa4, 0x4d, 0xe1, 0x8b, 0x27, 0xce, 0x62, 0x01, 0xad,
+ 0x44, 0xe8, 0xb4, 0x18, 0xf1, 0x5d, 0x3e, 0x92, 0x7b, 0xd7,
+ 0xbd, 0x11, 0xf8, 0x54, 0x37, 0x9b, 0x72, 0xde, 0xa6, 0x0a,
+ 0xe3, 0x4f, 0x2c, 0x80, 0x69, 0xc5, 0xaf, 0x03, 0xea, 0x46,
+ 0x25, 0x89, 0x60, 0xcc, 0xd8, 0x74, 0x9d, 0x31, 0x52, 0xfe,
+ 0x17, 0xbb, 0xd1, 0x7d, 0x94, 0x38, 0x5b, 0xf7, 0x1e, 0xb2,
+ 0xca, 0x66, 0x8f, 0x23, 0x40, 0xec, 0x05, 0xa9, 0xc3, 0x6f,
+ 0x86, 0x2a, 0x49, 0xe5, 0x0c, 0xa0, 0xfc, 0x50, 0xb9, 0x15,
+ 0x76, 0xda, 0x33, 0x9f, 0xf5, 0x59, 0xb0, 0x1c, 0x7f, 0xd3,
+ 0x3a, 0x96, 0xee, 0x42, 0xab, 0x07, 0x64, 0xc8, 0x21, 0x8d,
+ 0xe7, 0x4b, 0xa2, 0x0e, 0x6d, 0xc1, 0x28, 0x84, 0x00, 0xad,
+ 0x47, 0xea, 0x8e, 0x23, 0xc9, 0x64, 0x01, 0xac, 0x46, 0xeb,
+ 0x8f, 0x22, 0xc8, 0x65, 0x02, 0xaf, 0x45, 0xe8, 0x8c, 0x21,
+ 0xcb, 0x66, 0x03, 0xae, 0x44, 0xe9, 0x8d, 0x20, 0xca, 0x67,
+ 0x04, 0xa9, 0x43, 0xee, 0x8a, 0x27, 0xcd, 0x60, 0x05, 0xa8,
+ 0x42, 0xef, 0x8b, 0x26, 0xcc, 0x61, 0x06, 0xab, 0x41, 0xec,
+ 0x88, 0x25, 0xcf, 0x62, 0x07, 0xaa, 0x40, 0xed, 0x89, 0x24,
+ 0xce, 0x63, 0x08, 0xa5, 0x4f, 0xe2, 0x86, 0x2b, 0xc1, 0x6c,
+ 0x09, 0xa4, 0x4e, 0xe3, 0x87, 0x2a, 0xc0, 0x6d, 0x0a, 0xa7,
+ 0x4d, 0xe0, 0x84, 0x29, 0xc3, 0x6e, 0x0b, 0xa6, 0x4c, 0xe1,
+ 0x85, 0x28, 0xc2, 0x6f, 0x0c, 0xa1, 0x4b, 0xe6, 0x82, 0x2f,
+ 0xc5, 0x68, 0x0d, 0xa0, 0x4a, 0xe7, 0x83, 0x2e, 0xc4, 0x69,
+ 0x0e, 0xa3, 0x49, 0xe4, 0x80, 0x2d, 0xc7, 0x6a, 0x0f, 0xa2,
+ 0x48, 0xe5, 0x81, 0x2c, 0xc6, 0x6b, 0x10, 0xbd, 0x57, 0xfa,
+ 0x9e, 0x33, 0xd9, 0x74, 0x11, 0xbc, 0x56, 0xfb, 0x9f, 0x32,
+ 0xd8, 0x75, 0x12, 0xbf, 0x55, 0xf8, 0x9c, 0x31, 0xdb, 0x76,
+ 0x13, 0xbe, 0x54, 0xf9, 0x9d, 0x30, 0xda, 0x77, 0x14, 0xb9,
+ 0x53, 0xfe, 0x9a, 0x37, 0xdd, 0x70, 0x15, 0xb8, 0x52, 0xff,
+ 0x9b, 0x36, 0xdc, 0x71, 0x16, 0xbb, 0x51, 0xfc, 0x98, 0x35,
+ 0xdf, 0x72, 0x17, 0xba, 0x50, 0xfd, 0x99, 0x34, 0xde, 0x73,
+ 0x18, 0xb5, 0x5f, 0xf2, 0x96, 0x3b, 0xd1, 0x7c, 0x19, 0xb4,
+ 0x5e, 0xf3, 0x97, 0x3a, 0xd0, 0x7d, 0x1a, 0xb7, 0x5d, 0xf0,
+ 0x94, 0x39, 0xd3, 0x7e, 0x1b, 0xb6, 0x5c, 0xf1, 0x95, 0x38,
+ 0xd2, 0x7f, 0x1c, 0xb1, 0x5b, 0xf6, 0x92, 0x3f, 0xd5, 0x78,
+ 0x1d, 0xb0, 0x5a, 0xf7, 0x93, 0x3e, 0xd4, 0x79, 0x1e, 0xb3,
+ 0x59, 0xf4, 0x90, 0x3d, 0xd7, 0x7a, 0x1f, 0xb2, 0x58, 0xf5,
+ 0x91, 0x3c, 0xd6, 0x7b, 0x00, 0xae, 0x41, 0xef, 0x82, 0x2c,
+ 0xc3, 0x6d, 0x19, 0xb7, 0x58, 0xf6, 0x9b, 0x35, 0xda, 0x74,
+ 0x32, 0x9c, 0x73, 0xdd, 0xb0, 0x1e, 0xf1, 0x5f, 0x2b, 0x85,
+ 0x6a, 0xc4, 0xa9, 0x07, 0xe8, 0x46, 0x64, 0xca, 0x25, 0x8b,
+ 0xe6, 0x48, 0xa7, 0x09, 0x7d, 0xd3, 0x3c, 0x92, 0xff, 0x51,
+ 0xbe, 0x10, 0x56, 0xf8, 0x17, 0xb9, 0xd4, 0x7a, 0x95, 0x3b,
+ 0x4f, 0xe1, 0x0e, 0xa0, 0xcd, 0x63, 0x8c, 0x22, 0xc8, 0x66,
+ 0x89, 0x27, 0x4a, 0xe4, 0x0b, 0xa5, 0xd1, 0x7f, 0x90, 0x3e,
+ 0x53, 0xfd, 0x12, 0xbc, 0xfa, 0x54, 0xbb, 0x15, 0x78, 0xd6,
+ 0x39, 0x97, 0xe3, 0x4d, 0xa2, 0x0c, 0x61, 0xcf, 0x20, 0x8e,
+ 0xac, 0x02, 0xed, 0x43, 0x2e, 0x80, 0x6f, 0xc1, 0xb5, 0x1b,
+ 0xf4, 0x5a, 0x37, 0x99, 0x76, 0xd8, 0x9e, 0x30, 0xdf, 0x71,
+ 0x1c, 0xb2, 0x5d, 0xf3, 0x87, 0x29, 0xc6, 0x68, 0x05, 0xab,
+ 0x44, 0xea, 0x8d, 0x23, 0xcc, 0x62, 0x0f, 0xa1, 0x4e, 0xe0,
+ 0x94, 0x3a, 0xd5, 0x7b, 0x16, 0xb8, 0x57, 0xf9, 0xbf, 0x11,
+ 0xfe, 0x50, 0x3d, 0x93, 0x7c, 0xd2, 0xa6, 0x08, 0xe7, 0x49,
+ 0x24, 0x8a, 0x65, 0xcb, 0xe9, 0x47, 0xa8, 0x06, 0x6b, 0xc5,
+ 0x2a, 0x84, 0xf0, 0x5e, 0xb1, 0x1f, 0x72, 0xdc, 0x33, 0x9d,
+ 0xdb, 0x75, 0x9a, 0x34, 0x59, 0xf7, 0x18, 0xb6, 0xc2, 0x6c,
+ 0x83, 0x2d, 0x40, 0xee, 0x01, 0xaf, 0x45, 0xeb, 0x04, 0xaa,
+ 0xc7, 0x69, 0x86, 0x28, 0x5c, 0xf2, 0x1d, 0xb3, 0xde, 0x70,
+ 0x9f, 0x31, 0x77, 0xd9, 0x36, 0x98, 0xf5, 0x5b, 0xb4, 0x1a,
+ 0x6e, 0xc0, 0x2f, 0x81, 0xec, 0x42, 0xad, 0x03, 0x21, 0x8f,
+ 0x60, 0xce, 0xa3, 0x0d, 0xe2, 0x4c, 0x38, 0x96, 0x79, 0xd7,
+ 0xba, 0x14, 0xfb, 0x55, 0x13, 0xbd, 0x52, 0xfc, 0x91, 0x3f,
+ 0xd0, 0x7e, 0x0a, 0xa4, 0x4b, 0xe5, 0x88, 0x26, 0xc9, 0x67,
+ 0x00, 0xaf, 0x43, 0xec, 0x86, 0x29, 0xc5, 0x6a, 0x11, 0xbe,
+ 0x52, 0xfd, 0x97, 0x38, 0xd4, 0x7b, 0x22, 0x8d, 0x61, 0xce,
+ 0xa4, 0x0b, 0xe7, 0x48, 0x33, 0x9c, 0x70, 0xdf, 0xb5, 0x1a,
+ 0xf6, 0x59, 0x44, 0xeb, 0x07, 0xa8, 0xc2, 0x6d, 0x81, 0x2e,
+ 0x55, 0xfa, 0x16, 0xb9, 0xd3, 0x7c, 0x90, 0x3f, 0x66, 0xc9,
+ 0x25, 0x8a, 0xe0, 0x4f, 0xa3, 0x0c, 0x77, 0xd8, 0x34, 0x9b,
+ 0xf1, 0x5e, 0xb2, 0x1d, 0x88, 0x27, 0xcb, 0x64, 0x0e, 0xa1,
+ 0x4d, 0xe2, 0x99, 0x36, 0xda, 0x75, 0x1f, 0xb0, 0x5c, 0xf3,
+ 0xaa, 0x05, 0xe9, 0x46, 0x2c, 0x83, 0x6f, 0xc0, 0xbb, 0x14,
+ 0xf8, 0x57, 0x3d, 0x92, 0x7e, 0xd1, 0xcc, 0x63, 0x8f, 0x20,
+ 0x4a, 0xe5, 0x09, 0xa6, 0xdd, 0x72, 0x9e, 0x31, 0x5b, 0xf4,
+ 0x18, 0xb7, 0xee, 0x41, 0xad, 0x02, 0x68, 0xc7, 0x2b, 0x84,
+ 0xff, 0x50, 0xbc, 0x13, 0x79, 0xd6, 0x3a, 0x95, 0x0d, 0xa2,
+ 0x4e, 0xe1, 0x8b, 0x24, 0xc8, 0x67, 0x1c, 0xb3, 0x5f, 0xf0,
+ 0x9a, 0x35, 0xd9, 0x76, 0x2f, 0x80, 0x6c, 0xc3, 0xa9, 0x06,
+ 0xea, 0x45, 0x3e, 0x91, 0x7d, 0xd2, 0xb8, 0x17, 0xfb, 0x54,
+ 0x49, 0xe6, 0x0a, 0xa5, 0xcf, 0x60, 0x8c, 0x23, 0x58, 0xf7,
+ 0x1b, 0xb4, 0xde, 0x71, 0x9d, 0x32, 0x6b, 0xc4, 0x28, 0x87,
+ 0xed, 0x42, 0xae, 0x01, 0x7a, 0xd5, 0x39, 0x96, 0xfc, 0x53,
+ 0xbf, 0x10, 0x85, 0x2a, 0xc6, 0x69, 0x03, 0xac, 0x40, 0xef,
+ 0x94, 0x3b, 0xd7, 0x78, 0x12, 0xbd, 0x51, 0xfe, 0xa7, 0x08,
+ 0xe4, 0x4b, 0x21, 0x8e, 0x62, 0xcd, 0xb6, 0x19, 0xf5, 0x5a,
+ 0x30, 0x9f, 0x73, 0xdc, 0xc1, 0x6e, 0x82, 0x2d, 0x47, 0xe8,
+ 0x04, 0xab, 0xd0, 0x7f, 0x93, 0x3c, 0x56, 0xf9, 0x15, 0xba,
+ 0xe3, 0x4c, 0xa0, 0x0f, 0x65, 0xca, 0x26, 0x89, 0xf2, 0x5d,
+ 0xb1, 0x1e, 0x74, 0xdb, 0x37, 0x98, 0x00, 0xb0, 0x7d, 0xcd,
+ 0xfa, 0x4a, 0x87, 0x37, 0xe9, 0x59, 0x94, 0x24, 0x13, 0xa3,
+ 0x6e, 0xde, 0xcf, 0x7f, 0xb2, 0x02, 0x35, 0x85, 0x48, 0xf8,
+ 0x26, 0x96, 0x5b, 0xeb, 0xdc, 0x6c, 0xa1, 0x11, 0x83, 0x33,
+ 0xfe, 0x4e, 0x79, 0xc9, 0x04, 0xb4, 0x6a, 0xda, 0x17, 0xa7,
+ 0x90, 0x20, 0xed, 0x5d, 0x4c, 0xfc, 0x31, 0x81, 0xb6, 0x06,
+ 0xcb, 0x7b, 0xa5, 0x15, 0xd8, 0x68, 0x5f, 0xef, 0x22, 0x92,
+ 0x1b, 0xab, 0x66, 0xd6, 0xe1, 0x51, 0x9c, 0x2c, 0xf2, 0x42,
+ 0x8f, 0x3f, 0x08, 0xb8, 0x75, 0xc5, 0xd4, 0x64, 0xa9, 0x19,
+ 0x2e, 0x9e, 0x53, 0xe3, 0x3d, 0x8d, 0x40, 0xf0, 0xc7, 0x77,
+ 0xba, 0x0a, 0x98, 0x28, 0xe5, 0x55, 0x62, 0xd2, 0x1f, 0xaf,
+ 0x71, 0xc1, 0x0c, 0xbc, 0x8b, 0x3b, 0xf6, 0x46, 0x57, 0xe7,
+ 0x2a, 0x9a, 0xad, 0x1d, 0xd0, 0x60, 0xbe, 0x0e, 0xc3, 0x73,
+ 0x44, 0xf4, 0x39, 0x89, 0x36, 0x86, 0x4b, 0xfb, 0xcc, 0x7c,
+ 0xb1, 0x01, 0xdf, 0x6f, 0xa2, 0x12, 0x25, 0x95, 0x58, 0xe8,
+ 0xf9, 0x49, 0x84, 0x34, 0x03, 0xb3, 0x7e, 0xce, 0x10, 0xa0,
+ 0x6d, 0xdd, 0xea, 0x5a, 0x97, 0x27, 0xb5, 0x05, 0xc8, 0x78,
+ 0x4f, 0xff, 0x32, 0x82, 0x5c, 0xec, 0x21, 0x91, 0xa6, 0x16,
+ 0xdb, 0x6b, 0x7a, 0xca, 0x07, 0xb7, 0x80, 0x30, 0xfd, 0x4d,
+ 0x93, 0x23, 0xee, 0x5e, 0x69, 0xd9, 0x14, 0xa4, 0x2d, 0x9d,
+ 0x50, 0xe0, 0xd7, 0x67, 0xaa, 0x1a, 0xc4, 0x74, 0xb9, 0x09,
+ 0x3e, 0x8e, 0x43, 0xf3, 0xe2, 0x52, 0x9f, 0x2f, 0x18, 0xa8,
+ 0x65, 0xd5, 0x0b, 0xbb, 0x76, 0xc6, 0xf1, 0x41, 0x8c, 0x3c,
+ 0xae, 0x1e, 0xd3, 0x63, 0x54, 0xe4, 0x29, 0x99, 0x47, 0xf7,
+ 0x3a, 0x8a, 0xbd, 0x0d, 0xc0, 0x70, 0x61, 0xd1, 0x1c, 0xac,
+ 0x9b, 0x2b, 0xe6, 0x56, 0x88, 0x38, 0xf5, 0x45, 0x72, 0xc2,
+ 0x0f, 0xbf, 0x00, 0xb1, 0x7f, 0xce, 0xfe, 0x4f, 0x81, 0x30,
+ 0xe1, 0x50, 0x9e, 0x2f, 0x1f, 0xae, 0x60, 0xd1, 0xdf, 0x6e,
+ 0xa0, 0x11, 0x21, 0x90, 0x5e, 0xef, 0x3e, 0x8f, 0x41, 0xf0,
+ 0xc0, 0x71, 0xbf, 0x0e, 0xa3, 0x12, 0xdc, 0x6d, 0x5d, 0xec,
+ 0x22, 0x93, 0x42, 0xf3, 0x3d, 0x8c, 0xbc, 0x0d, 0xc3, 0x72,
+ 0x7c, 0xcd, 0x03, 0xb2, 0x82, 0x33, 0xfd, 0x4c, 0x9d, 0x2c,
+ 0xe2, 0x53, 0x63, 0xd2, 0x1c, 0xad, 0x5b, 0xea, 0x24, 0x95,
+ 0xa5, 0x14, 0xda, 0x6b, 0xba, 0x0b, 0xc5, 0x74, 0x44, 0xf5,
+ 0x3b, 0x8a, 0x84, 0x35, 0xfb, 0x4a, 0x7a, 0xcb, 0x05, 0xb4,
+ 0x65, 0xd4, 0x1a, 0xab, 0x9b, 0x2a, 0xe4, 0x55, 0xf8, 0x49,
+ 0x87, 0x36, 0x06, 0xb7, 0x79, 0xc8, 0x19, 0xa8, 0x66, 0xd7,
+ 0xe7, 0x56, 0x98, 0x29, 0x27, 0x96, 0x58, 0xe9, 0xd9, 0x68,
+ 0xa6, 0x17, 0xc6, 0x77, 0xb9, 0x08, 0x38, 0x89, 0x47, 0xf6,
+ 0xb6, 0x07, 0xc9, 0x78, 0x48, 0xf9, 0x37, 0x86, 0x57, 0xe6,
+ 0x28, 0x99, 0xa9, 0x18, 0xd6, 0x67, 0x69, 0xd8, 0x16, 0xa7,
+ 0x97, 0x26, 0xe8, 0x59, 0x88, 0x39, 0xf7, 0x46, 0x76, 0xc7,
+ 0x09, 0xb8, 0x15, 0xa4, 0x6a, 0xdb, 0xeb, 0x5a, 0x94, 0x25,
+ 0xf4, 0x45, 0x8b, 0x3a, 0x0a, 0xbb, 0x75, 0xc4, 0xca, 0x7b,
+ 0xb5, 0x04, 0x34, 0x85, 0x4b, 0xfa, 0x2b, 0x9a, 0x54, 0xe5,
+ 0xd5, 0x64, 0xaa, 0x1b, 0xed, 0x5c, 0x92, 0x23, 0x13, 0xa2,
+ 0x6c, 0xdd, 0x0c, 0xbd, 0x73, 0xc2, 0xf2, 0x43, 0x8d, 0x3c,
+ 0x32, 0x83, 0x4d, 0xfc, 0xcc, 0x7d, 0xb3, 0x02, 0xd3, 0x62,
+ 0xac, 0x1d, 0x2d, 0x9c, 0x52, 0xe3, 0x4e, 0xff, 0x31, 0x80,
+ 0xb0, 0x01, 0xcf, 0x7e, 0xaf, 0x1e, 0xd0, 0x61, 0x51, 0xe0,
+ 0x2e, 0x9f, 0x91, 0x20, 0xee, 0x5f, 0x6f, 0xde, 0x10, 0xa1,
+ 0x70, 0xc1, 0x0f, 0xbe, 0x8e, 0x3f, 0xf1, 0x40, 0x00, 0xb2,
+ 0x79, 0xcb, 0xf2, 0x40, 0x8b, 0x39, 0xf9, 0x4b, 0x80, 0x32,
+ 0x0b, 0xb9, 0x72, 0xc0, 0xef, 0x5d, 0x96, 0x24, 0x1d, 0xaf,
+ 0x64, 0xd6, 0x16, 0xa4, 0x6f, 0xdd, 0xe4, 0x56, 0x9d, 0x2f,
+ 0xc3, 0x71, 0xba, 0x08, 0x31, 0x83, 0x48, 0xfa, 0x3a, 0x88,
+ 0x43, 0xf1, 0xc8, 0x7a, 0xb1, 0x03, 0x2c, 0x9e, 0x55, 0xe7,
+ 0xde, 0x6c, 0xa7, 0x15, 0xd5, 0x67, 0xac, 0x1e, 0x27, 0x95,
+ 0x5e, 0xec, 0x9b, 0x29, 0xe2, 0x50, 0x69, 0xdb, 0x10, 0xa2,
+ 0x62, 0xd0, 0x1b, 0xa9, 0x90, 0x22, 0xe9, 0x5b, 0x74, 0xc6,
+ 0x0d, 0xbf, 0x86, 0x34, 0xff, 0x4d, 0x8d, 0x3f, 0xf4, 0x46,
+ 0x7f, 0xcd, 0x06, 0xb4, 0x58, 0xea, 0x21, 0x93, 0xaa, 0x18,
+ 0xd3, 0x61, 0xa1, 0x13, 0xd8, 0x6a, 0x53, 0xe1, 0x2a, 0x98,
+ 0xb7, 0x05, 0xce, 0x7c, 0x45, 0xf7, 0x3c, 0x8e, 0x4e, 0xfc,
+ 0x37, 0x85, 0xbc, 0x0e, 0xc5, 0x77, 0x2b, 0x99, 0x52, 0xe0,
+ 0xd9, 0x6b, 0xa0, 0x12, 0xd2, 0x60, 0xab, 0x19, 0x20, 0x92,
+ 0x59, 0xeb, 0xc4, 0x76, 0xbd, 0x0f, 0x36, 0x84, 0x4f, 0xfd,
+ 0x3d, 0x8f, 0x44, 0xf6, 0xcf, 0x7d, 0xb6, 0x04, 0xe8, 0x5a,
+ 0x91, 0x23, 0x1a, 0xa8, 0x63, 0xd1, 0x11, 0xa3, 0x68, 0xda,
+ 0xe3, 0x51, 0x9a, 0x28, 0x07, 0xb5, 0x7e, 0xcc, 0xf5, 0x47,
+ 0x8c, 0x3e, 0xfe, 0x4c, 0x87, 0x35, 0x0c, 0xbe, 0x75, 0xc7,
+ 0xb0, 0x02, 0xc9, 0x7b, 0x42, 0xf0, 0x3b, 0x89, 0x49, 0xfb,
+ 0x30, 0x82, 0xbb, 0x09, 0xc2, 0x70, 0x5f, 0xed, 0x26, 0x94,
+ 0xad, 0x1f, 0xd4, 0x66, 0xa6, 0x14, 0xdf, 0x6d, 0x54, 0xe6,
+ 0x2d, 0x9f, 0x73, 0xc1, 0x0a, 0xb8, 0x81, 0x33, 0xf8, 0x4a,
+ 0x8a, 0x38, 0xf3, 0x41, 0x78, 0xca, 0x01, 0xb3, 0x9c, 0x2e,
+ 0xe5, 0x57, 0x6e, 0xdc, 0x17, 0xa5, 0x65, 0xd7, 0x1c, 0xae,
+ 0x97, 0x25, 0xee, 0x5c, 0x00, 0xb3, 0x7b, 0xc8, 0xf6, 0x45,
+ 0x8d, 0x3e, 0xf1, 0x42, 0x8a, 0x39, 0x07, 0xb4, 0x7c, 0xcf,
+ 0xff, 0x4c, 0x84, 0x37, 0x09, 0xba, 0x72, 0xc1, 0x0e, 0xbd,
+ 0x75, 0xc6, 0xf8, 0x4b, 0x83, 0x30, 0xe3, 0x50, 0x98, 0x2b,
+ 0x15, 0xa6, 0x6e, 0xdd, 0x12, 0xa1, 0x69, 0xda, 0xe4, 0x57,
+ 0x9f, 0x2c, 0x1c, 0xaf, 0x67, 0xd4, 0xea, 0x59, 0x91, 0x22,
+ 0xed, 0x5e, 0x96, 0x25, 0x1b, 0xa8, 0x60, 0xd3, 0xdb, 0x68,
+ 0xa0, 0x13, 0x2d, 0x9e, 0x56, 0xe5, 0x2a, 0x99, 0x51, 0xe2,
+ 0xdc, 0x6f, 0xa7, 0x14, 0x24, 0x97, 0x5f, 0xec, 0xd2, 0x61,
+ 0xa9, 0x1a, 0xd5, 0x66, 0xae, 0x1d, 0x23, 0x90, 0x58, 0xeb,
+ 0x38, 0x8b, 0x43, 0xf0, 0xce, 0x7d, 0xb5, 0x06, 0xc9, 0x7a,
+ 0xb2, 0x01, 0x3f, 0x8c, 0x44, 0xf7, 0xc7, 0x74, 0xbc, 0x0f,
+ 0x31, 0x82, 0x4a, 0xf9, 0x36, 0x85, 0x4d, 0xfe, 0xc0, 0x73,
+ 0xbb, 0x08, 0xab, 0x18, 0xd0, 0x63, 0x5d, 0xee, 0x26, 0x95,
+ 0x5a, 0xe9, 0x21, 0x92, 0xac, 0x1f, 0xd7, 0x64, 0x54, 0xe7,
+ 0x2f, 0x9c, 0xa2, 0x11, 0xd9, 0x6a, 0xa5, 0x16, 0xde, 0x6d,
+ 0x53, 0xe0, 0x28, 0x9b, 0x48, 0xfb, 0x33, 0x80, 0xbe, 0x0d,
+ 0xc5, 0x76, 0xb9, 0x0a, 0xc2, 0x71, 0x4f, 0xfc, 0x34, 0x87,
+ 0xb7, 0x04, 0xcc, 0x7f, 0x41, 0xf2, 0x3a, 0x89, 0x46, 0xf5,
+ 0x3d, 0x8e, 0xb0, 0x03, 0xcb, 0x78, 0x70, 0xc3, 0x0b, 0xb8,
+ 0x86, 0x35, 0xfd, 0x4e, 0x81, 0x32, 0xfa, 0x49, 0x77, 0xc4,
+ 0x0c, 0xbf, 0x8f, 0x3c, 0xf4, 0x47, 0x79, 0xca, 0x02, 0xb1,
+ 0x7e, 0xcd, 0x05, 0xb6, 0x88, 0x3b, 0xf3, 0x40, 0x93, 0x20,
+ 0xe8, 0x5b, 0x65, 0xd6, 0x1e, 0xad, 0x62, 0xd1, 0x19, 0xaa,
+ 0x94, 0x27, 0xef, 0x5c, 0x6c, 0xdf, 0x17, 0xa4, 0x9a, 0x29,
+ 0xe1, 0x52, 0x9d, 0x2e, 0xe6, 0x55, 0x6b, 0xd8, 0x10, 0xa3,
+ 0x00, 0xb4, 0x75, 0xc1, 0xea, 0x5e, 0x9f, 0x2b, 0xc9, 0x7d,
+ 0xbc, 0x08, 0x23, 0x97, 0x56, 0xe2, 0x8f, 0x3b, 0xfa, 0x4e,
+ 0x65, 0xd1, 0x10, 0xa4, 0x46, 0xf2, 0x33, 0x87, 0xac, 0x18,
+ 0xd9, 0x6d, 0x03, 0xb7, 0x76, 0xc2, 0xe9, 0x5d, 0x9c, 0x28,
+ 0xca, 0x7e, 0xbf, 0x0b, 0x20, 0x94, 0x55, 0xe1, 0x8c, 0x38,
+ 0xf9, 0x4d, 0x66, 0xd2, 0x13, 0xa7, 0x45, 0xf1, 0x30, 0x84,
+ 0xaf, 0x1b, 0xda, 0x6e, 0x06, 0xb2, 0x73, 0xc7, 0xec, 0x58,
+ 0x99, 0x2d, 0xcf, 0x7b, 0xba, 0x0e, 0x25, 0x91, 0x50, 0xe4,
+ 0x89, 0x3d, 0xfc, 0x48, 0x63, 0xd7, 0x16, 0xa2, 0x40, 0xf4,
+ 0x35, 0x81, 0xaa, 0x1e, 0xdf, 0x6b, 0x05, 0xb1, 0x70, 0xc4,
+ 0xef, 0x5b, 0x9a, 0x2e, 0xcc, 0x78, 0xb9, 0x0d, 0x26, 0x92,
+ 0x53, 0xe7, 0x8a, 0x3e, 0xff, 0x4b, 0x60, 0xd4, 0x15, 0xa1,
+ 0x43, 0xf7, 0x36, 0x82, 0xa9, 0x1d, 0xdc, 0x68, 0x0c, 0xb8,
+ 0x79, 0xcd, 0xe6, 0x52, 0x93, 0x27, 0xc5, 0x71, 0xb0, 0x04,
+ 0x2f, 0x9b, 0x5a, 0xee, 0x83, 0x37, 0xf6, 0x42, 0x69, 0xdd,
+ 0x1c, 0xa8, 0x4a, 0xfe, 0x3f, 0x8b, 0xa0, 0x14, 0xd5, 0x61,
+ 0x0f, 0xbb, 0x7a, 0xce, 0xe5, 0x51, 0x90, 0x24, 0xc6, 0x72,
+ 0xb3, 0x07, 0x2c, 0x98, 0x59, 0xed, 0x80, 0x34, 0xf5, 0x41,
+ 0x6a, 0xde, 0x1f, 0xab, 0x49, 0xfd, 0x3c, 0x88, 0xa3, 0x17,
+ 0xd6, 0x62, 0x0a, 0xbe, 0x7f, 0xcb, 0xe0, 0x54, 0x95, 0x21,
+ 0xc3, 0x77, 0xb6, 0x02, 0x29, 0x9d, 0x5c, 0xe8, 0x85, 0x31,
+ 0xf0, 0x44, 0x6f, 0xdb, 0x1a, 0xae, 0x4c, 0xf8, 0x39, 0x8d,
+ 0xa6, 0x12, 0xd3, 0x67, 0x09, 0xbd, 0x7c, 0xc8, 0xe3, 0x57,
+ 0x96, 0x22, 0xc0, 0x74, 0xb5, 0x01, 0x2a, 0x9e, 0x5f, 0xeb,
+ 0x86, 0x32, 0xf3, 0x47, 0x6c, 0xd8, 0x19, 0xad, 0x4f, 0xfb,
+ 0x3a, 0x8e, 0xa5, 0x11, 0xd0, 0x64, 0x00, 0xb5, 0x77, 0xc2,
+ 0xee, 0x5b, 0x99, 0x2c, 0xc1, 0x74, 0xb6, 0x03, 0x2f, 0x9a,
+ 0x58, 0xed, 0x9f, 0x2a, 0xe8, 0x5d, 0x71, 0xc4, 0x06, 0xb3,
+ 0x5e, 0xeb, 0x29, 0x9c, 0xb0, 0x05, 0xc7, 0x72, 0x23, 0x96,
+ 0x54, 0xe1, 0xcd, 0x78, 0xba, 0x0f, 0xe2, 0x57, 0x95, 0x20,
+ 0x0c, 0xb9, 0x7b, 0xce, 0xbc, 0x09, 0xcb, 0x7e, 0x52, 0xe7,
+ 0x25, 0x90, 0x7d, 0xc8, 0x0a, 0xbf, 0x93, 0x26, 0xe4, 0x51,
+ 0x46, 0xf3, 0x31, 0x84, 0xa8, 0x1d, 0xdf, 0x6a, 0x87, 0x32,
+ 0xf0, 0x45, 0x69, 0xdc, 0x1e, 0xab, 0xd9, 0x6c, 0xae, 0x1b,
+ 0x37, 0x82, 0x40, 0xf5, 0x18, 0xad, 0x6f, 0xda, 0xf6, 0x43,
+ 0x81, 0x34, 0x65, 0xd0, 0x12, 0xa7, 0x8b, 0x3e, 0xfc, 0x49,
+ 0xa4, 0x11, 0xd3, 0x66, 0x4a, 0xff, 0x3d, 0x88, 0xfa, 0x4f,
+ 0x8d, 0x38, 0x14, 0xa1, 0x63, 0xd6, 0x3b, 0x8e, 0x4c, 0xf9,
+ 0xd5, 0x60, 0xa2, 0x17, 0x8c, 0x39, 0xfb, 0x4e, 0x62, 0xd7,
+ 0x15, 0xa0, 0x4d, 0xf8, 0x3a, 0x8f, 0xa3, 0x16, 0xd4, 0x61,
+ 0x13, 0xa6, 0x64, 0xd1, 0xfd, 0x48, 0x8a, 0x3f, 0xd2, 0x67,
+ 0xa5, 0x10, 0x3c, 0x89, 0x4b, 0xfe, 0xaf, 0x1a, 0xd8, 0x6d,
+ 0x41, 0xf4, 0x36, 0x83, 0x6e, 0xdb, 0x19, 0xac, 0x80, 0x35,
+ 0xf7, 0x42, 0x30, 0x85, 0x47, 0xf2, 0xde, 0x6b, 0xa9, 0x1c,
+ 0xf1, 0x44, 0x86, 0x33, 0x1f, 0xaa, 0x68, 0xdd, 0xca, 0x7f,
+ 0xbd, 0x08, 0x24, 0x91, 0x53, 0xe6, 0x0b, 0xbe, 0x7c, 0xc9,
+ 0xe5, 0x50, 0x92, 0x27, 0x55, 0xe0, 0x22, 0x97, 0xbb, 0x0e,
+ 0xcc, 0x79, 0x94, 0x21, 0xe3, 0x56, 0x7a, 0xcf, 0x0d, 0xb8,
+ 0xe9, 0x5c, 0x9e, 0x2b, 0x07, 0xb2, 0x70, 0xc5, 0x28, 0x9d,
+ 0x5f, 0xea, 0xc6, 0x73, 0xb1, 0x04, 0x76, 0xc3, 0x01, 0xb4,
+ 0x98, 0x2d, 0xef, 0x5a, 0xb7, 0x02, 0xc0, 0x75, 0x59, 0xec,
+ 0x2e, 0x9b, 0x00, 0xb6, 0x71, 0xc7, 0xe2, 0x54, 0x93, 0x25,
+ 0xd9, 0x6f, 0xa8, 0x1e, 0x3b, 0x8d, 0x4a, 0xfc, 0xaf, 0x19,
+ 0xde, 0x68, 0x4d, 0xfb, 0x3c, 0x8a, 0x76, 0xc0, 0x07, 0xb1,
+ 0x94, 0x22, 0xe5, 0x53, 0x43, 0xf5, 0x32, 0x84, 0xa1, 0x17,
+ 0xd0, 0x66, 0x9a, 0x2c, 0xeb, 0x5d, 0x78, 0xce, 0x09, 0xbf,
+ 0xec, 0x5a, 0x9d, 0x2b, 0x0e, 0xb8, 0x7f, 0xc9, 0x35, 0x83,
+ 0x44, 0xf2, 0xd7, 0x61, 0xa6, 0x10, 0x86, 0x30, 0xf7, 0x41,
+ 0x64, 0xd2, 0x15, 0xa3, 0x5f, 0xe9, 0x2e, 0x98, 0xbd, 0x0b,
+ 0xcc, 0x7a, 0x29, 0x9f, 0x58, 0xee, 0xcb, 0x7d, 0xba, 0x0c,
+ 0xf0, 0x46, 0x81, 0x37, 0x12, 0xa4, 0x63, 0xd5, 0xc5, 0x73,
+ 0xb4, 0x02, 0x27, 0x91, 0x56, 0xe0, 0x1c, 0xaa, 0x6d, 0xdb,
+ 0xfe, 0x48, 0x8f, 0x39, 0x6a, 0xdc, 0x1b, 0xad, 0x88, 0x3e,
+ 0xf9, 0x4f, 0xb3, 0x05, 0xc2, 0x74, 0x51, 0xe7, 0x20, 0x96,
+ 0x11, 0xa7, 0x60, 0xd6, 0xf3, 0x45, 0x82, 0x34, 0xc8, 0x7e,
+ 0xb9, 0x0f, 0x2a, 0x9c, 0x5b, 0xed, 0xbe, 0x08, 0xcf, 0x79,
+ 0x5c, 0xea, 0x2d, 0x9b, 0x67, 0xd1, 0x16, 0xa0, 0x85, 0x33,
+ 0xf4, 0x42, 0x52, 0xe4, 0x23, 0x95, 0xb0, 0x06, 0xc1, 0x77,
+ 0x8b, 0x3d, 0xfa, 0x4c, 0x69, 0xdf, 0x18, 0xae, 0xfd, 0x4b,
+ 0x8c, 0x3a, 0x1f, 0xa9, 0x6e, 0xd8, 0x24, 0x92, 0x55, 0xe3,
+ 0xc6, 0x70, 0xb7, 0x01, 0x97, 0x21, 0xe6, 0x50, 0x75, 0xc3,
+ 0x04, 0xb2, 0x4e, 0xf8, 0x3f, 0x89, 0xac, 0x1a, 0xdd, 0x6b,
+ 0x38, 0x8e, 0x49, 0xff, 0xda, 0x6c, 0xab, 0x1d, 0xe1, 0x57,
+ 0x90, 0x26, 0x03, 0xb5, 0x72, 0xc4, 0xd4, 0x62, 0xa5, 0x13,
+ 0x36, 0x80, 0x47, 0xf1, 0x0d, 0xbb, 0x7c, 0xca, 0xef, 0x59,
+ 0x9e, 0x28, 0x7b, 0xcd, 0x0a, 0xbc, 0x99, 0x2f, 0xe8, 0x5e,
+ 0xa2, 0x14, 0xd3, 0x65, 0x40, 0xf6, 0x31, 0x87, 0x00, 0xb7,
+ 0x73, 0xc4, 0xe6, 0x51, 0x95, 0x22, 0xd1, 0x66, 0xa2, 0x15,
+ 0x37, 0x80, 0x44, 0xf3, 0xbf, 0x08, 0xcc, 0x7b, 0x59, 0xee,
+ 0x2a, 0x9d, 0x6e, 0xd9, 0x1d, 0xaa, 0x88, 0x3f, 0xfb, 0x4c,
+ 0x63, 0xd4, 0x10, 0xa7, 0x85, 0x32, 0xf6, 0x41, 0xb2, 0x05,
+ 0xc1, 0x76, 0x54, 0xe3, 0x27, 0x90, 0xdc, 0x6b, 0xaf, 0x18,
+ 0x3a, 0x8d, 0x49, 0xfe, 0x0d, 0xba, 0x7e, 0xc9, 0xeb, 0x5c,
+ 0x98, 0x2f, 0xc6, 0x71, 0xb5, 0x02, 0x20, 0x97, 0x53, 0xe4,
+ 0x17, 0xa0, 0x64, 0xd3, 0xf1, 0x46, 0x82, 0x35, 0x79, 0xce,
+ 0x0a, 0xbd, 0x9f, 0x28, 0xec, 0x5b, 0xa8, 0x1f, 0xdb, 0x6c,
+ 0x4e, 0xf9, 0x3d, 0x8a, 0xa5, 0x12, 0xd6, 0x61, 0x43, 0xf4,
+ 0x30, 0x87, 0x74, 0xc3, 0x07, 0xb0, 0x92, 0x25, 0xe1, 0x56,
+ 0x1a, 0xad, 0x69, 0xde, 0xfc, 0x4b, 0x8f, 0x38, 0xcb, 0x7c,
+ 0xb8, 0x0f, 0x2d, 0x9a, 0x5e, 0xe9, 0x91, 0x26, 0xe2, 0x55,
+ 0x77, 0xc0, 0x04, 0xb3, 0x40, 0xf7, 0x33, 0x84, 0xa6, 0x11,
+ 0xd5, 0x62, 0x2e, 0x99, 0x5d, 0xea, 0xc8, 0x7f, 0xbb, 0x0c,
+ 0xff, 0x48, 0x8c, 0x3b, 0x19, 0xae, 0x6a, 0xdd, 0xf2, 0x45,
+ 0x81, 0x36, 0x14, 0xa3, 0x67, 0xd0, 0x23, 0x94, 0x50, 0xe7,
+ 0xc5, 0x72, 0xb6, 0x01, 0x4d, 0xfa, 0x3e, 0x89, 0xab, 0x1c,
+ 0xd8, 0x6f, 0x9c, 0x2b, 0xef, 0x58, 0x7a, 0xcd, 0x09, 0xbe,
+ 0x57, 0xe0, 0x24, 0x93, 0xb1, 0x06, 0xc2, 0x75, 0x86, 0x31,
+ 0xf5, 0x42, 0x60, 0xd7, 0x13, 0xa4, 0xe8, 0x5f, 0x9b, 0x2c,
+ 0x0e, 0xb9, 0x7d, 0xca, 0x39, 0x8e, 0x4a, 0xfd, 0xdf, 0x68,
+ 0xac, 0x1b, 0x34, 0x83, 0x47, 0xf0, 0xd2, 0x65, 0xa1, 0x16,
+ 0xe5, 0x52, 0x96, 0x21, 0x03, 0xb4, 0x70, 0xc7, 0x8b, 0x3c,
+ 0xf8, 0x4f, 0x6d, 0xda, 0x1e, 0xa9, 0x5a, 0xed, 0x29, 0x9e,
+ 0xbc, 0x0b, 0xcf, 0x78, 0x00, 0xb8, 0x6d, 0xd5, 0xda, 0x62,
+ 0xb7, 0x0f, 0xa9, 0x11, 0xc4, 0x7c, 0x73, 0xcb, 0x1e, 0xa6,
+ 0x4f, 0xf7, 0x22, 0x9a, 0x95, 0x2d, 0xf8, 0x40, 0xe6, 0x5e,
+ 0x8b, 0x33, 0x3c, 0x84, 0x51, 0xe9, 0x9e, 0x26, 0xf3, 0x4b,
+ 0x44, 0xfc, 0x29, 0x91, 0x37, 0x8f, 0x5a, 0xe2, 0xed, 0x55,
+ 0x80, 0x38, 0xd1, 0x69, 0xbc, 0x04, 0x0b, 0xb3, 0x66, 0xde,
+ 0x78, 0xc0, 0x15, 0xad, 0xa2, 0x1a, 0xcf, 0x77, 0x21, 0x99,
+ 0x4c, 0xf4, 0xfb, 0x43, 0x96, 0x2e, 0x88, 0x30, 0xe5, 0x5d,
+ 0x52, 0xea, 0x3f, 0x87, 0x6e, 0xd6, 0x03, 0xbb, 0xb4, 0x0c,
+ 0xd9, 0x61, 0xc7, 0x7f, 0xaa, 0x12, 0x1d, 0xa5, 0x70, 0xc8,
+ 0xbf, 0x07, 0xd2, 0x6a, 0x65, 0xdd, 0x08, 0xb0, 0x16, 0xae,
+ 0x7b, 0xc3, 0xcc, 0x74, 0xa1, 0x19, 0xf0, 0x48, 0x9d, 0x25,
+ 0x2a, 0x92, 0x47, 0xff, 0x59, 0xe1, 0x34, 0x8c, 0x83, 0x3b,
+ 0xee, 0x56, 0x42, 0xfa, 0x2f, 0x97, 0x98, 0x20, 0xf5, 0x4d,
+ 0xeb, 0x53, 0x86, 0x3e, 0x31, 0x89, 0x5c, 0xe4, 0x0d, 0xb5,
+ 0x60, 0xd8, 0xd7, 0x6f, 0xba, 0x02, 0xa4, 0x1c, 0xc9, 0x71,
+ 0x7e, 0xc6, 0x13, 0xab, 0xdc, 0x64, 0xb1, 0x09, 0x06, 0xbe,
+ 0x6b, 0xd3, 0x75, 0xcd, 0x18, 0xa0, 0xaf, 0x17, 0xc2, 0x7a,
+ 0x93, 0x2b, 0xfe, 0x46, 0x49, 0xf1, 0x24, 0x9c, 0x3a, 0x82,
+ 0x57, 0xef, 0xe0, 0x58, 0x8d, 0x35, 0x63, 0xdb, 0x0e, 0xb6,
+ 0xb9, 0x01, 0xd4, 0x6c, 0xca, 0x72, 0xa7, 0x1f, 0x10, 0xa8,
+ 0x7d, 0xc5, 0x2c, 0x94, 0x41, 0xf9, 0xf6, 0x4e, 0x9b, 0x23,
+ 0x85, 0x3d, 0xe8, 0x50, 0x5f, 0xe7, 0x32, 0x8a, 0xfd, 0x45,
+ 0x90, 0x28, 0x27, 0x9f, 0x4a, 0xf2, 0x54, 0xec, 0x39, 0x81,
+ 0x8e, 0x36, 0xe3, 0x5b, 0xb2, 0x0a, 0xdf, 0x67, 0x68, 0xd0,
+ 0x05, 0xbd, 0x1b, 0xa3, 0x76, 0xce, 0xc1, 0x79, 0xac, 0x14,
+ 0x00, 0xb9, 0x6f, 0xd6, 0xde, 0x67, 0xb1, 0x08, 0xa1, 0x18,
+ 0xce, 0x77, 0x7f, 0xc6, 0x10, 0xa9, 0x5f, 0xe6, 0x30, 0x89,
+ 0x81, 0x38, 0xee, 0x57, 0xfe, 0x47, 0x91, 0x28, 0x20, 0x99,
+ 0x4f, 0xf6, 0xbe, 0x07, 0xd1, 0x68, 0x60, 0xd9, 0x0f, 0xb6,
+ 0x1f, 0xa6, 0x70, 0xc9, 0xc1, 0x78, 0xae, 0x17, 0xe1, 0x58,
+ 0x8e, 0x37, 0x3f, 0x86, 0x50, 0xe9, 0x40, 0xf9, 0x2f, 0x96,
+ 0x9e, 0x27, 0xf1, 0x48, 0x61, 0xd8, 0x0e, 0xb7, 0xbf, 0x06,
+ 0xd0, 0x69, 0xc0, 0x79, 0xaf, 0x16, 0x1e, 0xa7, 0x71, 0xc8,
+ 0x3e, 0x87, 0x51, 0xe8, 0xe0, 0x59, 0x8f, 0x36, 0x9f, 0x26,
+ 0xf0, 0x49, 0x41, 0xf8, 0x2e, 0x97, 0xdf, 0x66, 0xb0, 0x09,
+ 0x01, 0xb8, 0x6e, 0xd7, 0x7e, 0xc7, 0x11, 0xa8, 0xa0, 0x19,
+ 0xcf, 0x76, 0x80, 0x39, 0xef, 0x56, 0x5e, 0xe7, 0x31, 0x88,
+ 0x21, 0x98, 0x4e, 0xf7, 0xff, 0x46, 0x90, 0x29, 0xc2, 0x7b,
+ 0xad, 0x14, 0x1c, 0xa5, 0x73, 0xca, 0x63, 0xda, 0x0c, 0xb5,
+ 0xbd, 0x04, 0xd2, 0x6b, 0x9d, 0x24, 0xf2, 0x4b, 0x43, 0xfa,
+ 0x2c, 0x95, 0x3c, 0x85, 0x53, 0xea, 0xe2, 0x5b, 0x8d, 0x34,
+ 0x7c, 0xc5, 0x13, 0xaa, 0xa2, 0x1b, 0xcd, 0x74, 0xdd, 0x64,
+ 0xb2, 0x0b, 0x03, 0xba, 0x6c, 0xd5, 0x23, 0x9a, 0x4c, 0xf5,
+ 0xfd, 0x44, 0x92, 0x2b, 0x82, 0x3b, 0xed, 0x54, 0x5c, 0xe5,
+ 0x33, 0x8a, 0xa3, 0x1a, 0xcc, 0x75, 0x7d, 0xc4, 0x12, 0xab,
+ 0x02, 0xbb, 0x6d, 0xd4, 0xdc, 0x65, 0xb3, 0x0a, 0xfc, 0x45,
+ 0x93, 0x2a, 0x22, 0x9b, 0x4d, 0xf4, 0x5d, 0xe4, 0x32, 0x8b,
+ 0x83, 0x3a, 0xec, 0x55, 0x1d, 0xa4, 0x72, 0xcb, 0xc3, 0x7a,
+ 0xac, 0x15, 0xbc, 0x05, 0xd3, 0x6a, 0x62, 0xdb, 0x0d, 0xb4,
+ 0x42, 0xfb, 0x2d, 0x94, 0x9c, 0x25, 0xf3, 0x4a, 0xe3, 0x5a,
+ 0x8c, 0x35, 0x3d, 0x84, 0x52, 0xeb, 0x00, 0xba, 0x69, 0xd3,
+ 0xd2, 0x68, 0xbb, 0x01, 0xb9, 0x03, 0xd0, 0x6a, 0x6b, 0xd1,
+ 0x02, 0xb8, 0x6f, 0xd5, 0x06, 0xbc, 0xbd, 0x07, 0xd4, 0x6e,
+ 0xd6, 0x6c, 0xbf, 0x05, 0x04, 0xbe, 0x6d, 0xd7, 0xde, 0x64,
+ 0xb7, 0x0d, 0x0c, 0xb6, 0x65, 0xdf, 0x67, 0xdd, 0x0e, 0xb4,
+ 0xb5, 0x0f, 0xdc, 0x66, 0xb1, 0x0b, 0xd8, 0x62, 0x63, 0xd9,
+ 0x0a, 0xb0, 0x08, 0xb2, 0x61, 0xdb, 0xda, 0x60, 0xb3, 0x09,
+ 0xa1, 0x1b, 0xc8, 0x72, 0x73, 0xc9, 0x1a, 0xa0, 0x18, 0xa2,
+ 0x71, 0xcb, 0xca, 0x70, 0xa3, 0x19, 0xce, 0x74, 0xa7, 0x1d,
+ 0x1c, 0xa6, 0x75, 0xcf, 0x77, 0xcd, 0x1e, 0xa4, 0xa5, 0x1f,
+ 0xcc, 0x76, 0x7f, 0xc5, 0x16, 0xac, 0xad, 0x17, 0xc4, 0x7e,
+ 0xc6, 0x7c, 0xaf, 0x15, 0x14, 0xae, 0x7d, 0xc7, 0x10, 0xaa,
+ 0x79, 0xc3, 0xc2, 0x78, 0xab, 0x11, 0xa9, 0x13, 0xc0, 0x7a,
+ 0x7b, 0xc1, 0x12, 0xa8, 0x5f, 0xe5, 0x36, 0x8c, 0x8d, 0x37,
+ 0xe4, 0x5e, 0xe6, 0x5c, 0x8f, 0x35, 0x34, 0x8e, 0x5d, 0xe7,
+ 0x30, 0x8a, 0x59, 0xe3, 0xe2, 0x58, 0x8b, 0x31, 0x89, 0x33,
+ 0xe0, 0x5a, 0x5b, 0xe1, 0x32, 0x88, 0x81, 0x3b, 0xe8, 0x52,
+ 0x53, 0xe9, 0x3a, 0x80, 0x38, 0x82, 0x51, 0xeb, 0xea, 0x50,
+ 0x83, 0x39, 0xee, 0x54, 0x87, 0x3d, 0x3c, 0x86, 0x55, 0xef,
+ 0x57, 0xed, 0x3e, 0x84, 0x85, 0x3f, 0xec, 0x56, 0xfe, 0x44,
+ 0x97, 0x2d, 0x2c, 0x96, 0x45, 0xff, 0x47, 0xfd, 0x2e, 0x94,
+ 0x95, 0x2f, 0xfc, 0x46, 0x91, 0x2b, 0xf8, 0x42, 0x43, 0xf9,
+ 0x2a, 0x90, 0x28, 0x92, 0x41, 0xfb, 0xfa, 0x40, 0x93, 0x29,
+ 0x20, 0x9a, 0x49, 0xf3, 0xf2, 0x48, 0x9b, 0x21, 0x99, 0x23,
+ 0xf0, 0x4a, 0x4b, 0xf1, 0x22, 0x98, 0x4f, 0xf5, 0x26, 0x9c,
+ 0x9d, 0x27, 0xf4, 0x4e, 0xf6, 0x4c, 0x9f, 0x25, 0x24, 0x9e,
+ 0x4d, 0xf7, 0x00, 0xbb, 0x6b, 0xd0, 0xd6, 0x6d, 0xbd, 0x06,
+ 0xb1, 0x0a, 0xda, 0x61, 0x67, 0xdc, 0x0c, 0xb7, 0x7f, 0xc4,
+ 0x14, 0xaf, 0xa9, 0x12, 0xc2, 0x79, 0xce, 0x75, 0xa5, 0x1e,
+ 0x18, 0xa3, 0x73, 0xc8, 0xfe, 0x45, 0x95, 0x2e, 0x28, 0x93,
+ 0x43, 0xf8, 0x4f, 0xf4, 0x24, 0x9f, 0x99, 0x22, 0xf2, 0x49,
+ 0x81, 0x3a, 0xea, 0x51, 0x57, 0xec, 0x3c, 0x87, 0x30, 0x8b,
+ 0x5b, 0xe0, 0xe6, 0x5d, 0x8d, 0x36, 0xe1, 0x5a, 0x8a, 0x31,
+ 0x37, 0x8c, 0x5c, 0xe7, 0x50, 0xeb, 0x3b, 0x80, 0x86, 0x3d,
+ 0xed, 0x56, 0x9e, 0x25, 0xf5, 0x4e, 0x48, 0xf3, 0x23, 0x98,
+ 0x2f, 0x94, 0x44, 0xff, 0xf9, 0x42, 0x92, 0x29, 0x1f, 0xa4,
+ 0x74, 0xcf, 0xc9, 0x72, 0xa2, 0x19, 0xae, 0x15, 0xc5, 0x7e,
+ 0x78, 0xc3, 0x13, 0xa8, 0x60, 0xdb, 0x0b, 0xb0, 0xb6, 0x0d,
+ 0xdd, 0x66, 0xd1, 0x6a, 0xba, 0x01, 0x07, 0xbc, 0x6c, 0xd7,
+ 0xdf, 0x64, 0xb4, 0x0f, 0x09, 0xb2, 0x62, 0xd9, 0x6e, 0xd5,
+ 0x05, 0xbe, 0xb8, 0x03, 0xd3, 0x68, 0xa0, 0x1b, 0xcb, 0x70,
+ 0x76, 0xcd, 0x1d, 0xa6, 0x11, 0xaa, 0x7a, 0xc1, 0xc7, 0x7c,
+ 0xac, 0x17, 0x21, 0x9a, 0x4a, 0xf1, 0xf7, 0x4c, 0x9c, 0x27,
+ 0x90, 0x2b, 0xfb, 0x40, 0x46, 0xfd, 0x2d, 0x96, 0x5e, 0xe5,
+ 0x35, 0x8e, 0x88, 0x33, 0xe3, 0x58, 0xef, 0x54, 0x84, 0x3f,
+ 0x39, 0x82, 0x52, 0xe9, 0x3e, 0x85, 0x55, 0xee, 0xe8, 0x53,
+ 0x83, 0x38, 0x8f, 0x34, 0xe4, 0x5f, 0x59, 0xe2, 0x32, 0x89,
+ 0x41, 0xfa, 0x2a, 0x91, 0x97, 0x2c, 0xfc, 0x47, 0xf0, 0x4b,
+ 0x9b, 0x20, 0x26, 0x9d, 0x4d, 0xf6, 0xc0, 0x7b, 0xab, 0x10,
+ 0x16, 0xad, 0x7d, 0xc6, 0x71, 0xca, 0x1a, 0xa1, 0xa7, 0x1c,
+ 0xcc, 0x77, 0xbf, 0x04, 0xd4, 0x6f, 0x69, 0xd2, 0x02, 0xb9,
+ 0x0e, 0xb5, 0x65, 0xde, 0xd8, 0x63, 0xb3, 0x08, 0x00, 0xbc,
+ 0x65, 0xd9, 0xca, 0x76, 0xaf, 0x13, 0x89, 0x35, 0xec, 0x50,
+ 0x43, 0xff, 0x26, 0x9a, 0x0f, 0xb3, 0x6a, 0xd6, 0xc5, 0x79,
+ 0xa0, 0x1c, 0x86, 0x3a, 0xe3, 0x5f, 0x4c, 0xf0, 0x29, 0x95,
+ 0x1e, 0xa2, 0x7b, 0xc7, 0xd4, 0x68, 0xb1, 0x0d, 0x97, 0x2b,
+ 0xf2, 0x4e, 0x5d, 0xe1, 0x38, 0x84, 0x11, 0xad, 0x74, 0xc8,
+ 0xdb, 0x67, 0xbe, 0x02, 0x98, 0x24, 0xfd, 0x41, 0x52, 0xee,
+ 0x37, 0x8b, 0x3c, 0x80, 0x59, 0xe5, 0xf6, 0x4a, 0x93, 0x2f,
+ 0xb5, 0x09, 0xd0, 0x6c, 0x7f, 0xc3, 0x1a, 0xa6, 0x33, 0x8f,
+ 0x56, 0xea, 0xf9, 0x45, 0x9c, 0x20, 0xba, 0x06, 0xdf, 0x63,
+ 0x70, 0xcc, 0x15, 0xa9, 0x22, 0x9e, 0x47, 0xfb, 0xe8, 0x54,
+ 0x8d, 0x31, 0xab, 0x17, 0xce, 0x72, 0x61, 0xdd, 0x04, 0xb8,
+ 0x2d, 0x91, 0x48, 0xf4, 0xe7, 0x5b, 0x82, 0x3e, 0xa4, 0x18,
+ 0xc1, 0x7d, 0x6e, 0xd2, 0x0b, 0xb7, 0x78, 0xc4, 0x1d, 0xa1,
+ 0xb2, 0x0e, 0xd7, 0x6b, 0xf1, 0x4d, 0x94, 0x28, 0x3b, 0x87,
+ 0x5e, 0xe2, 0x77, 0xcb, 0x12, 0xae, 0xbd, 0x01, 0xd8, 0x64,
+ 0xfe, 0x42, 0x9b, 0x27, 0x34, 0x88, 0x51, 0xed, 0x66, 0xda,
+ 0x03, 0xbf, 0xac, 0x10, 0xc9, 0x75, 0xef, 0x53, 0x8a, 0x36,
+ 0x25, 0x99, 0x40, 0xfc, 0x69, 0xd5, 0x0c, 0xb0, 0xa3, 0x1f,
+ 0xc6, 0x7a, 0xe0, 0x5c, 0x85, 0x39, 0x2a, 0x96, 0x4f, 0xf3,
+ 0x44, 0xf8, 0x21, 0x9d, 0x8e, 0x32, 0xeb, 0x57, 0xcd, 0x71,
+ 0xa8, 0x14, 0x07, 0xbb, 0x62, 0xde, 0x4b, 0xf7, 0x2e, 0x92,
+ 0x81, 0x3d, 0xe4, 0x58, 0xc2, 0x7e, 0xa7, 0x1b, 0x08, 0xb4,
+ 0x6d, 0xd1, 0x5a, 0xe6, 0x3f, 0x83, 0x90, 0x2c, 0xf5, 0x49,
+ 0xd3, 0x6f, 0xb6, 0x0a, 0x19, 0xa5, 0x7c, 0xc0, 0x55, 0xe9,
+ 0x30, 0x8c, 0x9f, 0x23, 0xfa, 0x46, 0xdc, 0x60, 0xb9, 0x05,
+ 0x16, 0xaa, 0x73, 0xcf, 0x00, 0xbd, 0x67, 0xda, 0xce, 0x73,
+ 0xa9, 0x14, 0x81, 0x3c, 0xe6, 0x5b, 0x4f, 0xf2, 0x28, 0x95,
+ 0x1f, 0xa2, 0x78, 0xc5, 0xd1, 0x6c, 0xb6, 0x0b, 0x9e, 0x23,
+ 0xf9, 0x44, 0x50, 0xed, 0x37, 0x8a, 0x3e, 0x83, 0x59, 0xe4,
+ 0xf0, 0x4d, 0x97, 0x2a, 0xbf, 0x02, 0xd8, 0x65, 0x71, 0xcc,
+ 0x16, 0xab, 0x21, 0x9c, 0x46, 0xfb, 0xef, 0x52, 0x88, 0x35,
+ 0xa0, 0x1d, 0xc7, 0x7a, 0x6e, 0xd3, 0x09, 0xb4, 0x7c, 0xc1,
+ 0x1b, 0xa6, 0xb2, 0x0f, 0xd5, 0x68, 0xfd, 0x40, 0x9a, 0x27,
+ 0x33, 0x8e, 0x54, 0xe9, 0x63, 0xde, 0x04, 0xb9, 0xad, 0x10,
+ 0xca, 0x77, 0xe2, 0x5f, 0x85, 0x38, 0x2c, 0x91, 0x4b, 0xf6,
+ 0x42, 0xff, 0x25, 0x98, 0x8c, 0x31, 0xeb, 0x56, 0xc3, 0x7e,
+ 0xa4, 0x19, 0x0d, 0xb0, 0x6a, 0xd7, 0x5d, 0xe0, 0x3a, 0x87,
+ 0x93, 0x2e, 0xf4, 0x49, 0xdc, 0x61, 0xbb, 0x06, 0x12, 0xaf,
+ 0x75, 0xc8, 0xf8, 0x45, 0x9f, 0x22, 0x36, 0x8b, 0x51, 0xec,
+ 0x79, 0xc4, 0x1e, 0xa3, 0xb7, 0x0a, 0xd0, 0x6d, 0xe7, 0x5a,
+ 0x80, 0x3d, 0x29, 0x94, 0x4e, 0xf3, 0x66, 0xdb, 0x01, 0xbc,
+ 0xa8, 0x15, 0xcf, 0x72, 0xc6, 0x7b, 0xa1, 0x1c, 0x08, 0xb5,
+ 0x6f, 0xd2, 0x47, 0xfa, 0x20, 0x9d, 0x89, 0x34, 0xee, 0x53,
+ 0xd9, 0x64, 0xbe, 0x03, 0x17, 0xaa, 0x70, 0xcd, 0x58, 0xe5,
+ 0x3f, 0x82, 0x96, 0x2b, 0xf1, 0x4c, 0x84, 0x39, 0xe3, 0x5e,
+ 0x4a, 0xf7, 0x2d, 0x90, 0x05, 0xb8, 0x62, 0xdf, 0xcb, 0x76,
+ 0xac, 0x11, 0x9b, 0x26, 0xfc, 0x41, 0x55, 0xe8, 0x32, 0x8f,
+ 0x1a, 0xa7, 0x7d, 0xc0, 0xd4, 0x69, 0xb3, 0x0e, 0xba, 0x07,
+ 0xdd, 0x60, 0x74, 0xc9, 0x13, 0xae, 0x3b, 0x86, 0x5c, 0xe1,
+ 0xf5, 0x48, 0x92, 0x2f, 0xa5, 0x18, 0xc2, 0x7f, 0x6b, 0xd6,
+ 0x0c, 0xb1, 0x24, 0x99, 0x43, 0xfe, 0xea, 0x57, 0x8d, 0x30,
+ 0x00, 0xbe, 0x61, 0xdf, 0xc2, 0x7c, 0xa3, 0x1d, 0x99, 0x27,
+ 0xf8, 0x46, 0x5b, 0xe5, 0x3a, 0x84, 0x2f, 0x91, 0x4e, 0xf0,
+ 0xed, 0x53, 0x8c, 0x32, 0xb6, 0x08, 0xd7, 0x69, 0x74, 0xca,
+ 0x15, 0xab, 0x5e, 0xe0, 0x3f, 0x81, 0x9c, 0x22, 0xfd, 0x43,
+ 0xc7, 0x79, 0xa6, 0x18, 0x05, 0xbb, 0x64, 0xda, 0x71, 0xcf,
+ 0x10, 0xae, 0xb3, 0x0d, 0xd2, 0x6c, 0xe8, 0x56, 0x89, 0x37,
+ 0x2a, 0x94, 0x4b, 0xf5, 0xbc, 0x02, 0xdd, 0x63, 0x7e, 0xc0,
+ 0x1f, 0xa1, 0x25, 0x9b, 0x44, 0xfa, 0xe7, 0x59, 0x86, 0x38,
+ 0x93, 0x2d, 0xf2, 0x4c, 0x51, 0xef, 0x30, 0x8e, 0x0a, 0xb4,
+ 0x6b, 0xd5, 0xc8, 0x76, 0xa9, 0x17, 0xe2, 0x5c, 0x83, 0x3d,
+ 0x20, 0x9e, 0x41, 0xff, 0x7b, 0xc5, 0x1a, 0xa4, 0xb9, 0x07,
+ 0xd8, 0x66, 0xcd, 0x73, 0xac, 0x12, 0x0f, 0xb1, 0x6e, 0xd0,
+ 0x54, 0xea, 0x35, 0x8b, 0x96, 0x28, 0xf7, 0x49, 0x65, 0xdb,
+ 0x04, 0xba, 0xa7, 0x19, 0xc6, 0x78, 0xfc, 0x42, 0x9d, 0x23,
+ 0x3e, 0x80, 0x5f, 0xe1, 0x4a, 0xf4, 0x2b, 0x95, 0x88, 0x36,
+ 0xe9, 0x57, 0xd3, 0x6d, 0xb2, 0x0c, 0x11, 0xaf, 0x70, 0xce,
+ 0x3b, 0x85, 0x5a, 0xe4, 0xf9, 0x47, 0x98, 0x26, 0xa2, 0x1c,
+ 0xc3, 0x7d, 0x60, 0xde, 0x01, 0xbf, 0x14, 0xaa, 0x75, 0xcb,
+ 0xd6, 0x68, 0xb7, 0x09, 0x8d, 0x33, 0xec, 0x52, 0x4f, 0xf1,
+ 0x2e, 0x90, 0xd9, 0x67, 0xb8, 0x06, 0x1b, 0xa5, 0x7a, 0xc4,
+ 0x40, 0xfe, 0x21, 0x9f, 0x82, 0x3c, 0xe3, 0x5d, 0xf6, 0x48,
+ 0x97, 0x29, 0x34, 0x8a, 0x55, 0xeb, 0x6f, 0xd1, 0x0e, 0xb0,
+ 0xad, 0x13, 0xcc, 0x72, 0x87, 0x39, 0xe6, 0x58, 0x45, 0xfb,
+ 0x24, 0x9a, 0x1e, 0xa0, 0x7f, 0xc1, 0xdc, 0x62, 0xbd, 0x03,
+ 0xa8, 0x16, 0xc9, 0x77, 0x6a, 0xd4, 0x0b, 0xb5, 0x31, 0x8f,
+ 0x50, 0xee, 0xf3, 0x4d, 0x92, 0x2c, 0x00, 0xbf, 0x63, 0xdc,
+ 0xc6, 0x79, 0xa5, 0x1a, 0x91, 0x2e, 0xf2, 0x4d, 0x57, 0xe8,
+ 0x34, 0x8b, 0x3f, 0x80, 0x5c, 0xe3, 0xf9, 0x46, 0x9a, 0x25,
+ 0xae, 0x11, 0xcd, 0x72, 0x68, 0xd7, 0x0b, 0xb4, 0x7e, 0xc1,
+ 0x1d, 0xa2, 0xb8, 0x07, 0xdb, 0x64, 0xef, 0x50, 0x8c, 0x33,
+ 0x29, 0x96, 0x4a, 0xf5, 0x41, 0xfe, 0x22, 0x9d, 0x87, 0x38,
+ 0xe4, 0x5b, 0xd0, 0x6f, 0xb3, 0x0c, 0x16, 0xa9, 0x75, 0xca,
+ 0xfc, 0x43, 0x9f, 0x20, 0x3a, 0x85, 0x59, 0xe6, 0x6d, 0xd2,
+ 0x0e, 0xb1, 0xab, 0x14, 0xc8, 0x77, 0xc3, 0x7c, 0xa0, 0x1f,
+ 0x05, 0xba, 0x66, 0xd9, 0x52, 0xed, 0x31, 0x8e, 0x94, 0x2b,
+ 0xf7, 0x48, 0x82, 0x3d, 0xe1, 0x5e, 0x44, 0xfb, 0x27, 0x98,
+ 0x13, 0xac, 0x70, 0xcf, 0xd5, 0x6a, 0xb6, 0x09, 0xbd, 0x02,
+ 0xde, 0x61, 0x7b, 0xc4, 0x18, 0xa7, 0x2c, 0x93, 0x4f, 0xf0,
+ 0xea, 0x55, 0x89, 0x36, 0xe5, 0x5a, 0x86, 0x39, 0x23, 0x9c,
+ 0x40, 0xff, 0x74, 0xcb, 0x17, 0xa8, 0xb2, 0x0d, 0xd1, 0x6e,
+ 0xda, 0x65, 0xb9, 0x06, 0x1c, 0xa3, 0x7f, 0xc0, 0x4b, 0xf4,
+ 0x28, 0x97, 0x8d, 0x32, 0xee, 0x51, 0x9b, 0x24, 0xf8, 0x47,
+ 0x5d, 0xe2, 0x3e, 0x81, 0x0a, 0xb5, 0x69, 0xd6, 0xcc, 0x73,
+ 0xaf, 0x10, 0xa4, 0x1b, 0xc7, 0x78, 0x62, 0xdd, 0x01, 0xbe,
+ 0x35, 0x8a, 0x56, 0xe9, 0xf3, 0x4c, 0x90, 0x2f, 0x19, 0xa6,
+ 0x7a, 0xc5, 0xdf, 0x60, 0xbc, 0x03, 0x88, 0x37, 0xeb, 0x54,
+ 0x4e, 0xf1, 0x2d, 0x92, 0x26, 0x99, 0x45, 0xfa, 0xe0, 0x5f,
+ 0x83, 0x3c, 0xb7, 0x08, 0xd4, 0x6b, 0x71, 0xce, 0x12, 0xad,
+ 0x67, 0xd8, 0x04, 0xbb, 0xa1, 0x1e, 0xc2, 0x7d, 0xf6, 0x49,
+ 0x95, 0x2a, 0x30, 0x8f, 0x53, 0xec, 0x58, 0xe7, 0x3b, 0x84,
+ 0x9e, 0x21, 0xfd, 0x42, 0xc9, 0x76, 0xaa, 0x15, 0x0f, 0xb0,
+ 0x6c, 0xd3, 0x00, 0xc0, 0x9d, 0x5d, 0x27, 0xe7, 0xba, 0x7a,
+ 0x4e, 0x8e, 0xd3, 0x13, 0x69, 0xa9, 0xf4, 0x34, 0x9c, 0x5c,
+ 0x01, 0xc1, 0xbb, 0x7b, 0x26, 0xe6, 0xd2, 0x12, 0x4f, 0x8f,
+ 0xf5, 0x35, 0x68, 0xa8, 0x25, 0xe5, 0xb8, 0x78, 0x02, 0xc2,
+ 0x9f, 0x5f, 0x6b, 0xab, 0xf6, 0x36, 0x4c, 0x8c, 0xd1, 0x11,
+ 0xb9, 0x79, 0x24, 0xe4, 0x9e, 0x5e, 0x03, 0xc3, 0xf7, 0x37,
+ 0x6a, 0xaa, 0xd0, 0x10, 0x4d, 0x8d, 0x4a, 0x8a, 0xd7, 0x17,
+ 0x6d, 0xad, 0xf0, 0x30, 0x04, 0xc4, 0x99, 0x59, 0x23, 0xe3,
+ 0xbe, 0x7e, 0xd6, 0x16, 0x4b, 0x8b, 0xf1, 0x31, 0x6c, 0xac,
+ 0x98, 0x58, 0x05, 0xc5, 0xbf, 0x7f, 0x22, 0xe2, 0x6f, 0xaf,
+ 0xf2, 0x32, 0x48, 0x88, 0xd5, 0x15, 0x21, 0xe1, 0xbc, 0x7c,
+ 0x06, 0xc6, 0x9b, 0x5b, 0xf3, 0x33, 0x6e, 0xae, 0xd4, 0x14,
+ 0x49, 0x89, 0xbd, 0x7d, 0x20, 0xe0, 0x9a, 0x5a, 0x07, 0xc7,
+ 0x94, 0x54, 0x09, 0xc9, 0xb3, 0x73, 0x2e, 0xee, 0xda, 0x1a,
+ 0x47, 0x87, 0xfd, 0x3d, 0x60, 0xa0, 0x08, 0xc8, 0x95, 0x55,
+ 0x2f, 0xef, 0xb2, 0x72, 0x46, 0x86, 0xdb, 0x1b, 0x61, 0xa1,
+ 0xfc, 0x3c, 0xb1, 0x71, 0x2c, 0xec, 0x96, 0x56, 0x0b, 0xcb,
+ 0xff, 0x3f, 0x62, 0xa2, 0xd8, 0x18, 0x45, 0x85, 0x2d, 0xed,
+ 0xb0, 0x70, 0x0a, 0xca, 0x97, 0x57, 0x63, 0xa3, 0xfe, 0x3e,
+ 0x44, 0x84, 0xd9, 0x19, 0xde, 0x1e, 0x43, 0x83, 0xf9, 0x39,
+ 0x64, 0xa4, 0x90, 0x50, 0x0d, 0xcd, 0xb7, 0x77, 0x2a, 0xea,
+ 0x42, 0x82, 0xdf, 0x1f, 0x65, 0xa5, 0xf8, 0x38, 0x0c, 0xcc,
+ 0x91, 0x51, 0x2b, 0xeb, 0xb6, 0x76, 0xfb, 0x3b, 0x66, 0xa6,
+ 0xdc, 0x1c, 0x41, 0x81, 0xb5, 0x75, 0x28, 0xe8, 0x92, 0x52,
+ 0x0f, 0xcf, 0x67, 0xa7, 0xfa, 0x3a, 0x40, 0x80, 0xdd, 0x1d,
+ 0x29, 0xe9, 0xb4, 0x74, 0x0e, 0xce, 0x93, 0x53, 0x00, 0xc1,
+ 0x9f, 0x5e, 0x23, 0xe2, 0xbc, 0x7d, 0x46, 0x87, 0xd9, 0x18,
+ 0x65, 0xa4, 0xfa, 0x3b, 0x8c, 0x4d, 0x13, 0xd2, 0xaf, 0x6e,
+ 0x30, 0xf1, 0xca, 0x0b, 0x55, 0x94, 0xe9, 0x28, 0x76, 0xb7,
+ 0x05, 0xc4, 0x9a, 0x5b, 0x26, 0xe7, 0xb9, 0x78, 0x43, 0x82,
+ 0xdc, 0x1d, 0x60, 0xa1, 0xff, 0x3e, 0x89, 0x48, 0x16, 0xd7,
+ 0xaa, 0x6b, 0x35, 0xf4, 0xcf, 0x0e, 0x50, 0x91, 0xec, 0x2d,
+ 0x73, 0xb2, 0x0a, 0xcb, 0x95, 0x54, 0x29, 0xe8, 0xb6, 0x77,
+ 0x4c, 0x8d, 0xd3, 0x12, 0x6f, 0xae, 0xf0, 0x31, 0x86, 0x47,
+ 0x19, 0xd8, 0xa5, 0x64, 0x3a, 0xfb, 0xc0, 0x01, 0x5f, 0x9e,
+ 0xe3, 0x22, 0x7c, 0xbd, 0x0f, 0xce, 0x90, 0x51, 0x2c, 0xed,
+ 0xb3, 0x72, 0x49, 0x88, 0xd6, 0x17, 0x6a, 0xab, 0xf5, 0x34,
+ 0x83, 0x42, 0x1c, 0xdd, 0xa0, 0x61, 0x3f, 0xfe, 0xc5, 0x04,
+ 0x5a, 0x9b, 0xe6, 0x27, 0x79, 0xb8, 0x14, 0xd5, 0x8b, 0x4a,
+ 0x37, 0xf6, 0xa8, 0x69, 0x52, 0x93, 0xcd, 0x0c, 0x71, 0xb0,
+ 0xee, 0x2f, 0x98, 0x59, 0x07, 0xc6, 0xbb, 0x7a, 0x24, 0xe5,
+ 0xde, 0x1f, 0x41, 0x80, 0xfd, 0x3c, 0x62, 0xa3, 0x11, 0xd0,
+ 0x8e, 0x4f, 0x32, 0xf3, 0xad, 0x6c, 0x57, 0x96, 0xc8, 0x09,
+ 0x74, 0xb5, 0xeb, 0x2a, 0x9d, 0x5c, 0x02, 0xc3, 0xbe, 0x7f,
+ 0x21, 0xe0, 0xdb, 0x1a, 0x44, 0x85, 0xf8, 0x39, 0x67, 0xa6,
+ 0x1e, 0xdf, 0x81, 0x40, 0x3d, 0xfc, 0xa2, 0x63, 0x58, 0x99,
+ 0xc7, 0x06, 0x7b, 0xba, 0xe4, 0x25, 0x92, 0x53, 0x0d, 0xcc,
+ 0xb1, 0x70, 0x2e, 0xef, 0xd4, 0x15, 0x4b, 0x8a, 0xf7, 0x36,
+ 0x68, 0xa9, 0x1b, 0xda, 0x84, 0x45, 0x38, 0xf9, 0xa7, 0x66,
+ 0x5d, 0x9c, 0xc2, 0x03, 0x7e, 0xbf, 0xe1, 0x20, 0x97, 0x56,
+ 0x08, 0xc9, 0xb4, 0x75, 0x2b, 0xea, 0xd1, 0x10, 0x4e, 0x8f,
+ 0xf2, 0x33, 0x6d, 0xac, 0x00, 0xc2, 0x99, 0x5b, 0x2f, 0xed,
+ 0xb6, 0x74, 0x5e, 0x9c, 0xc7, 0x05, 0x71, 0xb3, 0xe8, 0x2a,
+ 0xbc, 0x7e, 0x25, 0xe7, 0x93, 0x51, 0x0a, 0xc8, 0xe2, 0x20,
+ 0x7b, 0xb9, 0xcd, 0x0f, 0x54, 0x96, 0x65, 0xa7, 0xfc, 0x3e,
+ 0x4a, 0x88, 0xd3, 0x11, 0x3b, 0xf9, 0xa2, 0x60, 0x14, 0xd6,
+ 0x8d, 0x4f, 0xd9, 0x1b, 0x40, 0x82, 0xf6, 0x34, 0x6f, 0xad,
+ 0x87, 0x45, 0x1e, 0xdc, 0xa8, 0x6a, 0x31, 0xf3, 0xca, 0x08,
+ 0x53, 0x91, 0xe5, 0x27, 0x7c, 0xbe, 0x94, 0x56, 0x0d, 0xcf,
+ 0xbb, 0x79, 0x22, 0xe0, 0x76, 0xb4, 0xef, 0x2d, 0x59, 0x9b,
+ 0xc0, 0x02, 0x28, 0xea, 0xb1, 0x73, 0x07, 0xc5, 0x9e, 0x5c,
+ 0xaf, 0x6d, 0x36, 0xf4, 0x80, 0x42, 0x19, 0xdb, 0xf1, 0x33,
+ 0x68, 0xaa, 0xde, 0x1c, 0x47, 0x85, 0x13, 0xd1, 0x8a, 0x48,
+ 0x3c, 0xfe, 0xa5, 0x67, 0x4d, 0x8f, 0xd4, 0x16, 0x62, 0xa0,
+ 0xfb, 0x39, 0x89, 0x4b, 0x10, 0xd2, 0xa6, 0x64, 0x3f, 0xfd,
+ 0xd7, 0x15, 0x4e, 0x8c, 0xf8, 0x3a, 0x61, 0xa3, 0x35, 0xf7,
+ 0xac, 0x6e, 0x1a, 0xd8, 0x83, 0x41, 0x6b, 0xa9, 0xf2, 0x30,
+ 0x44, 0x86, 0xdd, 0x1f, 0xec, 0x2e, 0x75, 0xb7, 0xc3, 0x01,
+ 0x5a, 0x98, 0xb2, 0x70, 0x2b, 0xe9, 0x9d, 0x5f, 0x04, 0xc6,
+ 0x50, 0x92, 0xc9, 0x0b, 0x7f, 0xbd, 0xe6, 0x24, 0x0e, 0xcc,
+ 0x97, 0x55, 0x21, 0xe3, 0xb8, 0x7a, 0x43, 0x81, 0xda, 0x18,
+ 0x6c, 0xae, 0xf5, 0x37, 0x1d, 0xdf, 0x84, 0x46, 0x32, 0xf0,
+ 0xab, 0x69, 0xff, 0x3d, 0x66, 0xa4, 0xd0, 0x12, 0x49, 0x8b,
+ 0xa1, 0x63, 0x38, 0xfa, 0x8e, 0x4c, 0x17, 0xd5, 0x26, 0xe4,
+ 0xbf, 0x7d, 0x09, 0xcb, 0x90, 0x52, 0x78, 0xba, 0xe1, 0x23,
+ 0x57, 0x95, 0xce, 0x0c, 0x9a, 0x58, 0x03, 0xc1, 0xb5, 0x77,
+ 0x2c, 0xee, 0xc4, 0x06, 0x5d, 0x9f, 0xeb, 0x29, 0x72, 0xb0,
+ 0x00, 0xc3, 0x9b, 0x58, 0x2b, 0xe8, 0xb0, 0x73, 0x56, 0x95,
+ 0xcd, 0x0e, 0x7d, 0xbe, 0xe6, 0x25, 0xac, 0x6f, 0x37, 0xf4,
+ 0x87, 0x44, 0x1c, 0xdf, 0xfa, 0x39, 0x61, 0xa2, 0xd1, 0x12,
+ 0x4a, 0x89, 0x45, 0x86, 0xde, 0x1d, 0x6e, 0xad, 0xf5, 0x36,
+ 0x13, 0xd0, 0x88, 0x4b, 0x38, 0xfb, 0xa3, 0x60, 0xe9, 0x2a,
+ 0x72, 0xb1, 0xc2, 0x01, 0x59, 0x9a, 0xbf, 0x7c, 0x24, 0xe7,
+ 0x94, 0x57, 0x0f, 0xcc, 0x8a, 0x49, 0x11, 0xd2, 0xa1, 0x62,
+ 0x3a, 0xf9, 0xdc, 0x1f, 0x47, 0x84, 0xf7, 0x34, 0x6c, 0xaf,
+ 0x26, 0xe5, 0xbd, 0x7e, 0x0d, 0xce, 0x96, 0x55, 0x70, 0xb3,
+ 0xeb, 0x28, 0x5b, 0x98, 0xc0, 0x03, 0xcf, 0x0c, 0x54, 0x97,
+ 0xe4, 0x27, 0x7f, 0xbc, 0x99, 0x5a, 0x02, 0xc1, 0xb2, 0x71,
+ 0x29, 0xea, 0x63, 0xa0, 0xf8, 0x3b, 0x48, 0x8b, 0xd3, 0x10,
+ 0x35, 0xf6, 0xae, 0x6d, 0x1e, 0xdd, 0x85, 0x46, 0x09, 0xca,
+ 0x92, 0x51, 0x22, 0xe1, 0xb9, 0x7a, 0x5f, 0x9c, 0xc4, 0x07,
+ 0x74, 0xb7, 0xef, 0x2c, 0xa5, 0x66, 0x3e, 0xfd, 0x8e, 0x4d,
+ 0x15, 0xd6, 0xf3, 0x30, 0x68, 0xab, 0xd8, 0x1b, 0x43, 0x80,
+ 0x4c, 0x8f, 0xd7, 0x14, 0x67, 0xa4, 0xfc, 0x3f, 0x1a, 0xd9,
+ 0x81, 0x42, 0x31, 0xf2, 0xaa, 0x69, 0xe0, 0x23, 0x7b, 0xb8,
+ 0xcb, 0x08, 0x50, 0x93, 0xb6, 0x75, 0x2d, 0xee, 0x9d, 0x5e,
+ 0x06, 0xc5, 0x83, 0x40, 0x18, 0xdb, 0xa8, 0x6b, 0x33, 0xf0,
+ 0xd5, 0x16, 0x4e, 0x8d, 0xfe, 0x3d, 0x65, 0xa6, 0x2f, 0xec,
+ 0xb4, 0x77, 0x04, 0xc7, 0x9f, 0x5c, 0x79, 0xba, 0xe2, 0x21,
+ 0x52, 0x91, 0xc9, 0x0a, 0xc6, 0x05, 0x5d, 0x9e, 0xed, 0x2e,
+ 0x76, 0xb5, 0x90, 0x53, 0x0b, 0xc8, 0xbb, 0x78, 0x20, 0xe3,
+ 0x6a, 0xa9, 0xf1, 0x32, 0x41, 0x82, 0xda, 0x19, 0x3c, 0xff,
+ 0xa7, 0x64, 0x17, 0xd4, 0x8c, 0x4f, 0x00, 0xc4, 0x95, 0x51,
+ 0x37, 0xf3, 0xa2, 0x66, 0x6e, 0xaa, 0xfb, 0x3f, 0x59, 0x9d,
+ 0xcc, 0x08, 0xdc, 0x18, 0x49, 0x8d, 0xeb, 0x2f, 0x7e, 0xba,
+ 0xb2, 0x76, 0x27, 0xe3, 0x85, 0x41, 0x10, 0xd4, 0xa5, 0x61,
+ 0x30, 0xf4, 0x92, 0x56, 0x07, 0xc3, 0xcb, 0x0f, 0x5e, 0x9a,
+ 0xfc, 0x38, 0x69, 0xad, 0x79, 0xbd, 0xec, 0x28, 0x4e, 0x8a,
+ 0xdb, 0x1f, 0x17, 0xd3, 0x82, 0x46, 0x20, 0xe4, 0xb5, 0x71,
+ 0x57, 0x93, 0xc2, 0x06, 0x60, 0xa4, 0xf5, 0x31, 0x39, 0xfd,
+ 0xac, 0x68, 0x0e, 0xca, 0x9b, 0x5f, 0x8b, 0x4f, 0x1e, 0xda,
+ 0xbc, 0x78, 0x29, 0xed, 0xe5, 0x21, 0x70, 0xb4, 0xd2, 0x16,
+ 0x47, 0x83, 0xf2, 0x36, 0x67, 0xa3, 0xc5, 0x01, 0x50, 0x94,
+ 0x9c, 0x58, 0x09, 0xcd, 0xab, 0x6f, 0x3e, 0xfa, 0x2e, 0xea,
+ 0xbb, 0x7f, 0x19, 0xdd, 0x8c, 0x48, 0x40, 0x84, 0xd5, 0x11,
+ 0x77, 0xb3, 0xe2, 0x26, 0xae, 0x6a, 0x3b, 0xff, 0x99, 0x5d,
+ 0x0c, 0xc8, 0xc0, 0x04, 0x55, 0x91, 0xf7, 0x33, 0x62, 0xa6,
+ 0x72, 0xb6, 0xe7, 0x23, 0x45, 0x81, 0xd0, 0x14, 0x1c, 0xd8,
+ 0x89, 0x4d, 0x2b, 0xef, 0xbe, 0x7a, 0x0b, 0xcf, 0x9e, 0x5a,
+ 0x3c, 0xf8, 0xa9, 0x6d, 0x65, 0xa1, 0xf0, 0x34, 0x52, 0x96,
+ 0xc7, 0x03, 0xd7, 0x13, 0x42, 0x86, 0xe0, 0x24, 0x75, 0xb1,
+ 0xb9, 0x7d, 0x2c, 0xe8, 0x8e, 0x4a, 0x1b, 0xdf, 0xf9, 0x3d,
+ 0x6c, 0xa8, 0xce, 0x0a, 0x5b, 0x9f, 0x97, 0x53, 0x02, 0xc6,
+ 0xa0, 0x64, 0x35, 0xf1, 0x25, 0xe1, 0xb0, 0x74, 0x12, 0xd6,
+ 0x87, 0x43, 0x4b, 0x8f, 0xde, 0x1a, 0x7c, 0xb8, 0xe9, 0x2d,
+ 0x5c, 0x98, 0xc9, 0x0d, 0x6b, 0xaf, 0xfe, 0x3a, 0x32, 0xf6,
+ 0xa7, 0x63, 0x05, 0xc1, 0x90, 0x54, 0x80, 0x44, 0x15, 0xd1,
+ 0xb7, 0x73, 0x22, 0xe6, 0xee, 0x2a, 0x7b, 0xbf, 0xd9, 0x1d,
+ 0x4c, 0x88, 0x00, 0xc5, 0x97, 0x52, 0x33, 0xf6, 0xa4, 0x61,
+ 0x66, 0xa3, 0xf1, 0x34, 0x55, 0x90, 0xc2, 0x07, 0xcc, 0x09,
+ 0x5b, 0x9e, 0xff, 0x3a, 0x68, 0xad, 0xaa, 0x6f, 0x3d, 0xf8,
+ 0x99, 0x5c, 0x0e, 0xcb, 0x85, 0x40, 0x12, 0xd7, 0xb6, 0x73,
+ 0x21, 0xe4, 0xe3, 0x26, 0x74, 0xb1, 0xd0, 0x15, 0x47, 0x82,
+ 0x49, 0x8c, 0xde, 0x1b, 0x7a, 0xbf, 0xed, 0x28, 0x2f, 0xea,
+ 0xb8, 0x7d, 0x1c, 0xd9, 0x8b, 0x4e, 0x17, 0xd2, 0x80, 0x45,
+ 0x24, 0xe1, 0xb3, 0x76, 0x71, 0xb4, 0xe6, 0x23, 0x42, 0x87,
+ 0xd5, 0x10, 0xdb, 0x1e, 0x4c, 0x89, 0xe8, 0x2d, 0x7f, 0xba,
+ 0xbd, 0x78, 0x2a, 0xef, 0x8e, 0x4b, 0x19, 0xdc, 0x92, 0x57,
+ 0x05, 0xc0, 0xa1, 0x64, 0x36, 0xf3, 0xf4, 0x31, 0x63, 0xa6,
+ 0xc7, 0x02, 0x50, 0x95, 0x5e, 0x9b, 0xc9, 0x0c, 0x6d, 0xa8,
+ 0xfa, 0x3f, 0x38, 0xfd, 0xaf, 0x6a, 0x0b, 0xce, 0x9c, 0x59,
+ 0x2e, 0xeb, 0xb9, 0x7c, 0x1d, 0xd8, 0x8a, 0x4f, 0x48, 0x8d,
+ 0xdf, 0x1a, 0x7b, 0xbe, 0xec, 0x29, 0xe2, 0x27, 0x75, 0xb0,
+ 0xd1, 0x14, 0x46, 0x83, 0x84, 0x41, 0x13, 0xd6, 0xb7, 0x72,
+ 0x20, 0xe5, 0xab, 0x6e, 0x3c, 0xf9, 0x98, 0x5d, 0x0f, 0xca,
+ 0xcd, 0x08, 0x5a, 0x9f, 0xfe, 0x3b, 0x69, 0xac, 0x67, 0xa2,
+ 0xf0, 0x35, 0x54, 0x91, 0xc3, 0x06, 0x01, 0xc4, 0x96, 0x53,
+ 0x32, 0xf7, 0xa5, 0x60, 0x39, 0xfc, 0xae, 0x6b, 0x0a, 0xcf,
+ 0x9d, 0x58, 0x5f, 0x9a, 0xc8, 0x0d, 0x6c, 0xa9, 0xfb, 0x3e,
+ 0xf5, 0x30, 0x62, 0xa7, 0xc6, 0x03, 0x51, 0x94, 0x93, 0x56,
+ 0x04, 0xc1, 0xa0, 0x65, 0x37, 0xf2, 0xbc, 0x79, 0x2b, 0xee,
+ 0x8f, 0x4a, 0x18, 0xdd, 0xda, 0x1f, 0x4d, 0x88, 0xe9, 0x2c,
+ 0x7e, 0xbb, 0x70, 0xb5, 0xe7, 0x22, 0x43, 0x86, 0xd4, 0x11,
+ 0x16, 0xd3, 0x81, 0x44, 0x25, 0xe0, 0xb2, 0x77, 0x00, 0xc6,
+ 0x91, 0x57, 0x3f, 0xf9, 0xae, 0x68, 0x7e, 0xb8, 0xef, 0x29,
+ 0x41, 0x87, 0xd0, 0x16, 0xfc, 0x3a, 0x6d, 0xab, 0xc3, 0x05,
+ 0x52, 0x94, 0x82, 0x44, 0x13, 0xd5, 0xbd, 0x7b, 0x2c, 0xea,
+ 0xe5, 0x23, 0x74, 0xb2, 0xda, 0x1c, 0x4b, 0x8d, 0x9b, 0x5d,
+ 0x0a, 0xcc, 0xa4, 0x62, 0x35, 0xf3, 0x19, 0xdf, 0x88, 0x4e,
+ 0x26, 0xe0, 0xb7, 0x71, 0x67, 0xa1, 0xf6, 0x30, 0x58, 0x9e,
+ 0xc9, 0x0f, 0xd7, 0x11, 0x46, 0x80, 0xe8, 0x2e, 0x79, 0xbf,
+ 0xa9, 0x6f, 0x38, 0xfe, 0x96, 0x50, 0x07, 0xc1, 0x2b, 0xed,
+ 0xba, 0x7c, 0x14, 0xd2, 0x85, 0x43, 0x55, 0x93, 0xc4, 0x02,
+ 0x6a, 0xac, 0xfb, 0x3d, 0x32, 0xf4, 0xa3, 0x65, 0x0d, 0xcb,
+ 0x9c, 0x5a, 0x4c, 0x8a, 0xdd, 0x1b, 0x73, 0xb5, 0xe2, 0x24,
+ 0xce, 0x08, 0x5f, 0x99, 0xf1, 0x37, 0x60, 0xa6, 0xb0, 0x76,
+ 0x21, 0xe7, 0x8f, 0x49, 0x1e, 0xd8, 0xb3, 0x75, 0x22, 0xe4,
+ 0x8c, 0x4a, 0x1d, 0xdb, 0xcd, 0x0b, 0x5c, 0x9a, 0xf2, 0x34,
+ 0x63, 0xa5, 0x4f, 0x89, 0xde, 0x18, 0x70, 0xb6, 0xe1, 0x27,
+ 0x31, 0xf7, 0xa0, 0x66, 0x0e, 0xc8, 0x9f, 0x59, 0x56, 0x90,
+ 0xc7, 0x01, 0x69, 0xaf, 0xf8, 0x3e, 0x28, 0xee, 0xb9, 0x7f,
+ 0x17, 0xd1, 0x86, 0x40, 0xaa, 0x6c, 0x3b, 0xfd, 0x95, 0x53,
+ 0x04, 0xc2, 0xd4, 0x12, 0x45, 0x83, 0xeb, 0x2d, 0x7a, 0xbc,
+ 0x64, 0xa2, 0xf5, 0x33, 0x5b, 0x9d, 0xca, 0x0c, 0x1a, 0xdc,
+ 0x8b, 0x4d, 0x25, 0xe3, 0xb4, 0x72, 0x98, 0x5e, 0x09, 0xcf,
+ 0xa7, 0x61, 0x36, 0xf0, 0xe6, 0x20, 0x77, 0xb1, 0xd9, 0x1f,
+ 0x48, 0x8e, 0x81, 0x47, 0x10, 0xd6, 0xbe, 0x78, 0x2f, 0xe9,
+ 0xff, 0x39, 0x6e, 0xa8, 0xc0, 0x06, 0x51, 0x97, 0x7d, 0xbb,
+ 0xec, 0x2a, 0x42, 0x84, 0xd3, 0x15, 0x03, 0xc5, 0x92, 0x54,
+ 0x3c, 0xfa, 0xad, 0x6b, 0x00, 0xc7, 0x93, 0x54, 0x3b, 0xfc,
+ 0xa8, 0x6f, 0x76, 0xb1, 0xe5, 0x22, 0x4d, 0x8a, 0xde, 0x19,
+ 0xec, 0x2b, 0x7f, 0xb8, 0xd7, 0x10, 0x44, 0x83, 0x9a, 0x5d,
+ 0x09, 0xce, 0xa1, 0x66, 0x32, 0xf5, 0xc5, 0x02, 0x56, 0x91,
+ 0xfe, 0x39, 0x6d, 0xaa, 0xb3, 0x74, 0x20, 0xe7, 0x88, 0x4f,
+ 0x1b, 0xdc, 0x29, 0xee, 0xba, 0x7d, 0x12, 0xd5, 0x81, 0x46,
+ 0x5f, 0x98, 0xcc, 0x0b, 0x64, 0xa3, 0xf7, 0x30, 0x97, 0x50,
+ 0x04, 0xc3, 0xac, 0x6b, 0x3f, 0xf8, 0xe1, 0x26, 0x72, 0xb5,
+ 0xda, 0x1d, 0x49, 0x8e, 0x7b, 0xbc, 0xe8, 0x2f, 0x40, 0x87,
+ 0xd3, 0x14, 0x0d, 0xca, 0x9e, 0x59, 0x36, 0xf1, 0xa5, 0x62,
+ 0x52, 0x95, 0xc1, 0x06, 0x69, 0xae, 0xfa, 0x3d, 0x24, 0xe3,
+ 0xb7, 0x70, 0x1f, 0xd8, 0x8c, 0x4b, 0xbe, 0x79, 0x2d, 0xea,
+ 0x85, 0x42, 0x16, 0xd1, 0xc8, 0x0f, 0x5b, 0x9c, 0xf3, 0x34,
+ 0x60, 0xa7, 0x33, 0xf4, 0xa0, 0x67, 0x08, 0xcf, 0x9b, 0x5c,
+ 0x45, 0x82, 0xd6, 0x11, 0x7e, 0xb9, 0xed, 0x2a, 0xdf, 0x18,
+ 0x4c, 0x8b, 0xe4, 0x23, 0x77, 0xb0, 0xa9, 0x6e, 0x3a, 0xfd,
+ 0x92, 0x55, 0x01, 0xc6, 0xf6, 0x31, 0x65, 0xa2, 0xcd, 0x0a,
+ 0x5e, 0x99, 0x80, 0x47, 0x13, 0xd4, 0xbb, 0x7c, 0x28, 0xef,
+ 0x1a, 0xdd, 0x89, 0x4e, 0x21, 0xe6, 0xb2, 0x75, 0x6c, 0xab,
+ 0xff, 0x38, 0x57, 0x90, 0xc4, 0x03, 0xa4, 0x63, 0x37, 0xf0,
+ 0x9f, 0x58, 0x0c, 0xcb, 0xd2, 0x15, 0x41, 0x86, 0xe9, 0x2e,
+ 0x7a, 0xbd, 0x48, 0x8f, 0xdb, 0x1c, 0x73, 0xb4, 0xe0, 0x27,
+ 0x3e, 0xf9, 0xad, 0x6a, 0x05, 0xc2, 0x96, 0x51, 0x61, 0xa6,
+ 0xf2, 0x35, 0x5a, 0x9d, 0xc9, 0x0e, 0x17, 0xd0, 0x84, 0x43,
+ 0x2c, 0xeb, 0xbf, 0x78, 0x8d, 0x4a, 0x1e, 0xd9, 0xb6, 0x71,
+ 0x25, 0xe2, 0xfb, 0x3c, 0x68, 0xaf, 0xc0, 0x07, 0x53, 0x94,
+ 0x00, 0xc8, 0x8d, 0x45, 0x07, 0xcf, 0x8a, 0x42, 0x0e, 0xc6,
+ 0x83, 0x4b, 0x09, 0xc1, 0x84, 0x4c, 0x1c, 0xd4, 0x91, 0x59,
+ 0x1b, 0xd3, 0x96, 0x5e, 0x12, 0xda, 0x9f, 0x57, 0x15, 0xdd,
+ 0x98, 0x50, 0x38, 0xf0, 0xb5, 0x7d, 0x3f, 0xf7, 0xb2, 0x7a,
+ 0x36, 0xfe, 0xbb, 0x73, 0x31, 0xf9, 0xbc, 0x74, 0x24, 0xec,
+ 0xa9, 0x61, 0x23, 0xeb, 0xae, 0x66, 0x2a, 0xe2, 0xa7, 0x6f,
+ 0x2d, 0xe5, 0xa0, 0x68, 0x70, 0xb8, 0xfd, 0x35, 0x77, 0xbf,
+ 0xfa, 0x32, 0x7e, 0xb6, 0xf3, 0x3b, 0x79, 0xb1, 0xf4, 0x3c,
+ 0x6c, 0xa4, 0xe1, 0x29, 0x6b, 0xa3, 0xe6, 0x2e, 0x62, 0xaa,
+ 0xef, 0x27, 0x65, 0xad, 0xe8, 0x20, 0x48, 0x80, 0xc5, 0x0d,
+ 0x4f, 0x87, 0xc2, 0x0a, 0x46, 0x8e, 0xcb, 0x03, 0x41, 0x89,
+ 0xcc, 0x04, 0x54, 0x9c, 0xd9, 0x11, 0x53, 0x9b, 0xde, 0x16,
+ 0x5a, 0x92, 0xd7, 0x1f, 0x5d, 0x95, 0xd0, 0x18, 0xe0, 0x28,
+ 0x6d, 0xa5, 0xe7, 0x2f, 0x6a, 0xa2, 0xee, 0x26, 0x63, 0xab,
+ 0xe9, 0x21, 0x64, 0xac, 0xfc, 0x34, 0x71, 0xb9, 0xfb, 0x33,
+ 0x76, 0xbe, 0xf2, 0x3a, 0x7f, 0xb7, 0xf5, 0x3d, 0x78, 0xb0,
+ 0xd8, 0x10, 0x55, 0x9d, 0xdf, 0x17, 0x52, 0x9a, 0xd6, 0x1e,
+ 0x5b, 0x93, 0xd1, 0x19, 0x5c, 0x94, 0xc4, 0x0c, 0x49, 0x81,
+ 0xc3, 0x0b, 0x4e, 0x86, 0xca, 0x02, 0x47, 0x8f, 0xcd, 0x05,
+ 0x40, 0x88, 0x90, 0x58, 0x1d, 0xd5, 0x97, 0x5f, 0x1a, 0xd2,
+ 0x9e, 0x56, 0x13, 0xdb, 0x99, 0x51, 0x14, 0xdc, 0x8c, 0x44,
+ 0x01, 0xc9, 0x8b, 0x43, 0x06, 0xce, 0x82, 0x4a, 0x0f, 0xc7,
+ 0x85, 0x4d, 0x08, 0xc0, 0xa8, 0x60, 0x25, 0xed, 0xaf, 0x67,
+ 0x22, 0xea, 0xa6, 0x6e, 0x2b, 0xe3, 0xa1, 0x69, 0x2c, 0xe4,
+ 0xb4, 0x7c, 0x39, 0xf1, 0xb3, 0x7b, 0x3e, 0xf6, 0xba, 0x72,
+ 0x37, 0xff, 0xbd, 0x75, 0x30, 0xf8, 0x00, 0xc9, 0x8f, 0x46,
+ 0x03, 0xca, 0x8c, 0x45, 0x06, 0xcf, 0x89, 0x40, 0x05, 0xcc,
+ 0x8a, 0x43, 0x0c, 0xc5, 0x83, 0x4a, 0x0f, 0xc6, 0x80, 0x49,
+ 0x0a, 0xc3, 0x85, 0x4c, 0x09, 0xc0, 0x86, 0x4f, 0x18, 0xd1,
+ 0x97, 0x5e, 0x1b, 0xd2, 0x94, 0x5d, 0x1e, 0xd7, 0x91, 0x58,
+ 0x1d, 0xd4, 0x92, 0x5b, 0x14, 0xdd, 0x9b, 0x52, 0x17, 0xde,
+ 0x98, 0x51, 0x12, 0xdb, 0x9d, 0x54, 0x11, 0xd8, 0x9e, 0x57,
+ 0x30, 0xf9, 0xbf, 0x76, 0x33, 0xfa, 0xbc, 0x75, 0x36, 0xff,
+ 0xb9, 0x70, 0x35, 0xfc, 0xba, 0x73, 0x3c, 0xf5, 0xb3, 0x7a,
+ 0x3f, 0xf6, 0xb0, 0x79, 0x3a, 0xf3, 0xb5, 0x7c, 0x39, 0xf0,
+ 0xb6, 0x7f, 0x28, 0xe1, 0xa7, 0x6e, 0x2b, 0xe2, 0xa4, 0x6d,
+ 0x2e, 0xe7, 0xa1, 0x68, 0x2d, 0xe4, 0xa2, 0x6b, 0x24, 0xed,
+ 0xab, 0x62, 0x27, 0xee, 0xa8, 0x61, 0x22, 0xeb, 0xad, 0x64,
+ 0x21, 0xe8, 0xae, 0x67, 0x60, 0xa9, 0xef, 0x26, 0x63, 0xaa,
+ 0xec, 0x25, 0x66, 0xaf, 0xe9, 0x20, 0x65, 0xac, 0xea, 0x23,
+ 0x6c, 0xa5, 0xe3, 0x2a, 0x6f, 0xa6, 0xe0, 0x29, 0x6a, 0xa3,
+ 0xe5, 0x2c, 0x69, 0xa0, 0xe6, 0x2f, 0x78, 0xb1, 0xf7, 0x3e,
+ 0x7b, 0xb2, 0xf4, 0x3d, 0x7e, 0xb7, 0xf1, 0x38, 0x7d, 0xb4,
+ 0xf2, 0x3b, 0x74, 0xbd, 0xfb, 0x32, 0x77, 0xbe, 0xf8, 0x31,
+ 0x72, 0xbb, 0xfd, 0x34, 0x71, 0xb8, 0xfe, 0x37, 0x50, 0x99,
+ 0xdf, 0x16, 0x53, 0x9a, 0xdc, 0x15, 0x56, 0x9f, 0xd9, 0x10,
+ 0x55, 0x9c, 0xda, 0x13, 0x5c, 0x95, 0xd3, 0x1a, 0x5f, 0x96,
+ 0xd0, 0x19, 0x5a, 0x93, 0xd5, 0x1c, 0x59, 0x90, 0xd6, 0x1f,
+ 0x48, 0x81, 0xc7, 0x0e, 0x4b, 0x82, 0xc4, 0x0d, 0x4e, 0x87,
+ 0xc1, 0x08, 0x4d, 0x84, 0xc2, 0x0b, 0x44, 0x8d, 0xcb, 0x02,
+ 0x47, 0x8e, 0xc8, 0x01, 0x42, 0x8b, 0xcd, 0x04, 0x41, 0x88,
+ 0xce, 0x07, 0x00, 0xca, 0x89, 0x43, 0x0f, 0xc5, 0x86, 0x4c,
+ 0x1e, 0xd4, 0x97, 0x5d, 0x11, 0xdb, 0x98, 0x52, 0x3c, 0xf6,
+ 0xb5, 0x7f, 0x33, 0xf9, 0xba, 0x70, 0x22, 0xe8, 0xab, 0x61,
+ 0x2d, 0xe7, 0xa4, 0x6e, 0x78, 0xb2, 0xf1, 0x3b, 0x77, 0xbd,
+ 0xfe, 0x34, 0x66, 0xac, 0xef, 0x25, 0x69, 0xa3, 0xe0, 0x2a,
+ 0x44, 0x8e, 0xcd, 0x07, 0x4b, 0x81, 0xc2, 0x08, 0x5a, 0x90,
+ 0xd3, 0x19, 0x55, 0x9f, 0xdc, 0x16, 0xf0, 0x3a, 0x79, 0xb3,
+ 0xff, 0x35, 0x76, 0xbc, 0xee, 0x24, 0x67, 0xad, 0xe1, 0x2b,
+ 0x68, 0xa2, 0xcc, 0x06, 0x45, 0x8f, 0xc3, 0x09, 0x4a, 0x80,
+ 0xd2, 0x18, 0x5b, 0x91, 0xdd, 0x17, 0x54, 0x9e, 0x88, 0x42,
+ 0x01, 0xcb, 0x87, 0x4d, 0x0e, 0xc4, 0x96, 0x5c, 0x1f, 0xd5,
+ 0x99, 0x53, 0x10, 0xda, 0xb4, 0x7e, 0x3d, 0xf7, 0xbb, 0x71,
+ 0x32, 0xf8, 0xaa, 0x60, 0x23, 0xe9, 0xa5, 0x6f, 0x2c, 0xe6,
+ 0xfd, 0x37, 0x74, 0xbe, 0xf2, 0x38, 0x7b, 0xb1, 0xe3, 0x29,
+ 0x6a, 0xa0, 0xec, 0x26, 0x65, 0xaf, 0xc1, 0x0b, 0x48, 0x82,
+ 0xce, 0x04, 0x47, 0x8d, 0xdf, 0x15, 0x56, 0x9c, 0xd0, 0x1a,
+ 0x59, 0x93, 0x85, 0x4f, 0x0c, 0xc6, 0x8a, 0x40, 0x03, 0xc9,
+ 0x9b, 0x51, 0x12, 0xd8, 0x94, 0x5e, 0x1d, 0xd7, 0xb9, 0x73,
+ 0x30, 0xfa, 0xb6, 0x7c, 0x3f, 0xf5, 0xa7, 0x6d, 0x2e, 0xe4,
+ 0xa8, 0x62, 0x21, 0xeb, 0x0d, 0xc7, 0x84, 0x4e, 0x02, 0xc8,
+ 0x8b, 0x41, 0x13, 0xd9, 0x9a, 0x50, 0x1c, 0xd6, 0x95, 0x5f,
+ 0x31, 0xfb, 0xb8, 0x72, 0x3e, 0xf4, 0xb7, 0x7d, 0x2f, 0xe5,
+ 0xa6, 0x6c, 0x20, 0xea, 0xa9, 0x63, 0x75, 0xbf, 0xfc, 0x36,
+ 0x7a, 0xb0, 0xf3, 0x39, 0x6b, 0xa1, 0xe2, 0x28, 0x64, 0xae,
+ 0xed, 0x27, 0x49, 0x83, 0xc0, 0x0a, 0x46, 0x8c, 0xcf, 0x05,
+ 0x57, 0x9d, 0xde, 0x14, 0x58, 0x92, 0xd1, 0x1b, 0x00, 0xcb,
+ 0x8b, 0x40, 0x0b, 0xc0, 0x80, 0x4b, 0x16, 0xdd, 0x9d, 0x56,
+ 0x1d, 0xd6, 0x96, 0x5d, 0x2c, 0xe7, 0xa7, 0x6c, 0x27, 0xec,
+ 0xac, 0x67, 0x3a, 0xf1, 0xb1, 0x7a, 0x31, 0xfa, 0xba, 0x71,
+ 0x58, 0x93, 0xd3, 0x18, 0x53, 0x98, 0xd8, 0x13, 0x4e, 0x85,
+ 0xc5, 0x0e, 0x45, 0x8e, 0xce, 0x05, 0x74, 0xbf, 0xff, 0x34,
+ 0x7f, 0xb4, 0xf4, 0x3f, 0x62, 0xa9, 0xe9, 0x22, 0x69, 0xa2,
+ 0xe2, 0x29, 0xb0, 0x7b, 0x3b, 0xf0, 0xbb, 0x70, 0x30, 0xfb,
+ 0xa6, 0x6d, 0x2d, 0xe6, 0xad, 0x66, 0x26, 0xed, 0x9c, 0x57,
+ 0x17, 0xdc, 0x97, 0x5c, 0x1c, 0xd7, 0x8a, 0x41, 0x01, 0xca,
+ 0x81, 0x4a, 0x0a, 0xc1, 0xe8, 0x23, 0x63, 0xa8, 0xe3, 0x28,
+ 0x68, 0xa3, 0xfe, 0x35, 0x75, 0xbe, 0xf5, 0x3e, 0x7e, 0xb5,
+ 0xc4, 0x0f, 0x4f, 0x84, 0xcf, 0x04, 0x44, 0x8f, 0xd2, 0x19,
+ 0x59, 0x92, 0xd9, 0x12, 0x52, 0x99, 0x7d, 0xb6, 0xf6, 0x3d,
+ 0x76, 0xbd, 0xfd, 0x36, 0x6b, 0xa0, 0xe0, 0x2b, 0x60, 0xab,
+ 0xeb, 0x20, 0x51, 0x9a, 0xda, 0x11, 0x5a, 0x91, 0xd1, 0x1a,
+ 0x47, 0x8c, 0xcc, 0x07, 0x4c, 0x87, 0xc7, 0x0c, 0x25, 0xee,
+ 0xae, 0x65, 0x2e, 0xe5, 0xa5, 0x6e, 0x33, 0xf8, 0xb8, 0x73,
+ 0x38, 0xf3, 0xb3, 0x78, 0x09, 0xc2, 0x82, 0x49, 0x02, 0xc9,
+ 0x89, 0x42, 0x1f, 0xd4, 0x94, 0x5f, 0x14, 0xdf, 0x9f, 0x54,
+ 0xcd, 0x06, 0x46, 0x8d, 0xc6, 0x0d, 0x4d, 0x86, 0xdb, 0x10,
+ 0x50, 0x9b, 0xd0, 0x1b, 0x5b, 0x90, 0xe1, 0x2a, 0x6a, 0xa1,
+ 0xea, 0x21, 0x61, 0xaa, 0xf7, 0x3c, 0x7c, 0xb7, 0xfc, 0x37,
+ 0x77, 0xbc, 0x95, 0x5e, 0x1e, 0xd5, 0x9e, 0x55, 0x15, 0xde,
+ 0x83, 0x48, 0x08, 0xc3, 0x88, 0x43, 0x03, 0xc8, 0xb9, 0x72,
+ 0x32, 0xf9, 0xb2, 0x79, 0x39, 0xf2, 0xaf, 0x64, 0x24, 0xef,
+ 0xa4, 0x6f, 0x2f, 0xe4, 0x00, 0xcc, 0x85, 0x49, 0x17, 0xdb,
+ 0x92, 0x5e, 0x2e, 0xe2, 0xab, 0x67, 0x39, 0xf5, 0xbc, 0x70,
+ 0x5c, 0x90, 0xd9, 0x15, 0x4b, 0x87, 0xce, 0x02, 0x72, 0xbe,
+ 0xf7, 0x3b, 0x65, 0xa9, 0xe0, 0x2c, 0xb8, 0x74, 0x3d, 0xf1,
+ 0xaf, 0x63, 0x2a, 0xe6, 0x96, 0x5a, 0x13, 0xdf, 0x81, 0x4d,
+ 0x04, 0xc8, 0xe4, 0x28, 0x61, 0xad, 0xf3, 0x3f, 0x76, 0xba,
+ 0xca, 0x06, 0x4f, 0x83, 0xdd, 0x11, 0x58, 0x94, 0x6d, 0xa1,
+ 0xe8, 0x24, 0x7a, 0xb6, 0xff, 0x33, 0x43, 0x8f, 0xc6, 0x0a,
+ 0x54, 0x98, 0xd1, 0x1d, 0x31, 0xfd, 0xb4, 0x78, 0x26, 0xea,
+ 0xa3, 0x6f, 0x1f, 0xd3, 0x9a, 0x56, 0x08, 0xc4, 0x8d, 0x41,
+ 0xd5, 0x19, 0x50, 0x9c, 0xc2, 0x0e, 0x47, 0x8b, 0xfb, 0x37,
+ 0x7e, 0xb2, 0xec, 0x20, 0x69, 0xa5, 0x89, 0x45, 0x0c, 0xc0,
+ 0x9e, 0x52, 0x1b, 0xd7, 0xa7, 0x6b, 0x22, 0xee, 0xb0, 0x7c,
+ 0x35, 0xf9, 0xda, 0x16, 0x5f, 0x93, 0xcd, 0x01, 0x48, 0x84,
+ 0xf4, 0x38, 0x71, 0xbd, 0xe3, 0x2f, 0x66, 0xaa, 0x86, 0x4a,
+ 0x03, 0xcf, 0x91, 0x5d, 0x14, 0xd8, 0xa8, 0x64, 0x2d, 0xe1,
+ 0xbf, 0x73, 0x3a, 0xf6, 0x62, 0xae, 0xe7, 0x2b, 0x75, 0xb9,
+ 0xf0, 0x3c, 0x4c, 0x80, 0xc9, 0x05, 0x5b, 0x97, 0xde, 0x12,
+ 0x3e, 0xf2, 0xbb, 0x77, 0x29, 0xe5, 0xac, 0x60, 0x10, 0xdc,
+ 0x95, 0x59, 0x07, 0xcb, 0x82, 0x4e, 0xb7, 0x7b, 0x32, 0xfe,
+ 0xa0, 0x6c, 0x25, 0xe9, 0x99, 0x55, 0x1c, 0xd0, 0x8e, 0x42,
+ 0x0b, 0xc7, 0xeb, 0x27, 0x6e, 0xa2, 0xfc, 0x30, 0x79, 0xb5,
+ 0xc5, 0x09, 0x40, 0x8c, 0xd2, 0x1e, 0x57, 0x9b, 0x0f, 0xc3,
+ 0x8a, 0x46, 0x18, 0xd4, 0x9d, 0x51, 0x21, 0xed, 0xa4, 0x68,
+ 0x36, 0xfa, 0xb3, 0x7f, 0x53, 0x9f, 0xd6, 0x1a, 0x44, 0x88,
+ 0xc1, 0x0d, 0x7d, 0xb1, 0xf8, 0x34, 0x6a, 0xa6, 0xef, 0x23,
+ 0x00, 0xcd, 0x87, 0x4a, 0x13, 0xde, 0x94, 0x59, 0x26, 0xeb,
+ 0xa1, 0x6c, 0x35, 0xf8, 0xb2, 0x7f, 0x4c, 0x81, 0xcb, 0x06,
+ 0x5f, 0x92, 0xd8, 0x15, 0x6a, 0xa7, 0xed, 0x20, 0x79, 0xb4,
+ 0xfe, 0x33, 0x98, 0x55, 0x1f, 0xd2, 0x8b, 0x46, 0x0c, 0xc1,
+ 0xbe, 0x73, 0x39, 0xf4, 0xad, 0x60, 0x2a, 0xe7, 0xd4, 0x19,
+ 0x53, 0x9e, 0xc7, 0x0a, 0x40, 0x8d, 0xf2, 0x3f, 0x75, 0xb8,
+ 0xe1, 0x2c, 0x66, 0xab, 0x2d, 0xe0, 0xaa, 0x67, 0x3e, 0xf3,
+ 0xb9, 0x74, 0x0b, 0xc6, 0x8c, 0x41, 0x18, 0xd5, 0x9f, 0x52,
+ 0x61, 0xac, 0xe6, 0x2b, 0x72, 0xbf, 0xf5, 0x38, 0x47, 0x8a,
+ 0xc0, 0x0d, 0x54, 0x99, 0xd3, 0x1e, 0xb5, 0x78, 0x32, 0xff,
+ 0xa6, 0x6b, 0x21, 0xec, 0x93, 0x5e, 0x14, 0xd9, 0x80, 0x4d,
+ 0x07, 0xca, 0xf9, 0x34, 0x7e, 0xb3, 0xea, 0x27, 0x6d, 0xa0,
+ 0xdf, 0x12, 0x58, 0x95, 0xcc, 0x01, 0x4b, 0x86, 0x5a, 0x97,
+ 0xdd, 0x10, 0x49, 0x84, 0xce, 0x03, 0x7c, 0xb1, 0xfb, 0x36,
+ 0x6f, 0xa2, 0xe8, 0x25, 0x16, 0xdb, 0x91, 0x5c, 0x05, 0xc8,
+ 0x82, 0x4f, 0x30, 0xfd, 0xb7, 0x7a, 0x23, 0xee, 0xa4, 0x69,
+ 0xc2, 0x0f, 0x45, 0x88, 0xd1, 0x1c, 0x56, 0x9b, 0xe4, 0x29,
+ 0x63, 0xae, 0xf7, 0x3a, 0x70, 0xbd, 0x8e, 0x43, 0x09, 0xc4,
+ 0x9d, 0x50, 0x1a, 0xd7, 0xa8, 0x65, 0x2f, 0xe2, 0xbb, 0x76,
+ 0x3c, 0xf1, 0x77, 0xba, 0xf0, 0x3d, 0x64, 0xa9, 0xe3, 0x2e,
+ 0x51, 0x9c, 0xd6, 0x1b, 0x42, 0x8f, 0xc5, 0x08, 0x3b, 0xf6,
+ 0xbc, 0x71, 0x28, 0xe5, 0xaf, 0x62, 0x1d, 0xd0, 0x9a, 0x57,
+ 0x0e, 0xc3, 0x89, 0x44, 0xef, 0x22, 0x68, 0xa5, 0xfc, 0x31,
+ 0x7b, 0xb6, 0xc9, 0x04, 0x4e, 0x83, 0xda, 0x17, 0x5d, 0x90,
+ 0xa3, 0x6e, 0x24, 0xe9, 0xb0, 0x7d, 0x37, 0xfa, 0x85, 0x48,
+ 0x02, 0xcf, 0x96, 0x5b, 0x11, 0xdc, 0x00, 0xce, 0x81, 0x4f,
+ 0x1f, 0xd1, 0x9e, 0x50, 0x3e, 0xf0, 0xbf, 0x71, 0x21, 0xef,
+ 0xa0, 0x6e, 0x7c, 0xb2, 0xfd, 0x33, 0x63, 0xad, 0xe2, 0x2c,
+ 0x42, 0x8c, 0xc3, 0x0d, 0x5d, 0x93, 0xdc, 0x12, 0xf8, 0x36,
+ 0x79, 0xb7, 0xe7, 0x29, 0x66, 0xa8, 0xc6, 0x08, 0x47, 0x89,
+ 0xd9, 0x17, 0x58, 0x96, 0x84, 0x4a, 0x05, 0xcb, 0x9b, 0x55,
+ 0x1a, 0xd4, 0xba, 0x74, 0x3b, 0xf5, 0xa5, 0x6b, 0x24, 0xea,
+ 0xed, 0x23, 0x6c, 0xa2, 0xf2, 0x3c, 0x73, 0xbd, 0xd3, 0x1d,
+ 0x52, 0x9c, 0xcc, 0x02, 0x4d, 0x83, 0x91, 0x5f, 0x10, 0xde,
+ 0x8e, 0x40, 0x0f, 0xc1, 0xaf, 0x61, 0x2e, 0xe0, 0xb0, 0x7e,
+ 0x31, 0xff, 0x15, 0xdb, 0x94, 0x5a, 0x0a, 0xc4, 0x8b, 0x45,
+ 0x2b, 0xe5, 0xaa, 0x64, 0x34, 0xfa, 0xb5, 0x7b, 0x69, 0xa7,
+ 0xe8, 0x26, 0x76, 0xb8, 0xf7, 0x39, 0x57, 0x99, 0xd6, 0x18,
+ 0x48, 0x86, 0xc9, 0x07, 0xc7, 0x09, 0x46, 0x88, 0xd8, 0x16,
+ 0x59, 0x97, 0xf9, 0x37, 0x78, 0xb6, 0xe6, 0x28, 0x67, 0xa9,
+ 0xbb, 0x75, 0x3a, 0xf4, 0xa4, 0x6a, 0x25, 0xeb, 0x85, 0x4b,
+ 0x04, 0xca, 0x9a, 0x54, 0x1b, 0xd5, 0x3f, 0xf1, 0xbe, 0x70,
+ 0x20, 0xee, 0xa1, 0x6f, 0x01, 0xcf, 0x80, 0x4e, 0x1e, 0xd0,
+ 0x9f, 0x51, 0x43, 0x8d, 0xc2, 0x0c, 0x5c, 0x92, 0xdd, 0x13,
+ 0x7d, 0xb3, 0xfc, 0x32, 0x62, 0xac, 0xe3, 0x2d, 0x2a, 0xe4,
+ 0xab, 0x65, 0x35, 0xfb, 0xb4, 0x7a, 0x14, 0xda, 0x95, 0x5b,
+ 0x0b, 0xc5, 0x8a, 0x44, 0x56, 0x98, 0xd7, 0x19, 0x49, 0x87,
+ 0xc8, 0x06, 0x68, 0xa6, 0xe9, 0x27, 0x77, 0xb9, 0xf6, 0x38,
+ 0xd2, 0x1c, 0x53, 0x9d, 0xcd, 0x03, 0x4c, 0x82, 0xec, 0x22,
+ 0x6d, 0xa3, 0xf3, 0x3d, 0x72, 0xbc, 0xae, 0x60, 0x2f, 0xe1,
+ 0xb1, 0x7f, 0x30, 0xfe, 0x90, 0x5e, 0x11, 0xdf, 0x8f, 0x41,
+ 0x0e, 0xc0, 0x00, 0xcf, 0x83, 0x4c, 0x1b, 0xd4, 0x98, 0x57,
+ 0x36, 0xf9, 0xb5, 0x7a, 0x2d, 0xe2, 0xae, 0x61, 0x6c, 0xa3,
+ 0xef, 0x20, 0x77, 0xb8, 0xf4, 0x3b, 0x5a, 0x95, 0xd9, 0x16,
+ 0x41, 0x8e, 0xc2, 0x0d, 0xd8, 0x17, 0x5b, 0x94, 0xc3, 0x0c,
+ 0x40, 0x8f, 0xee, 0x21, 0x6d, 0xa2, 0xf5, 0x3a, 0x76, 0xb9,
+ 0xb4, 0x7b, 0x37, 0xf8, 0xaf, 0x60, 0x2c, 0xe3, 0x82, 0x4d,
+ 0x01, 0xce, 0x99, 0x56, 0x1a, 0xd5, 0xad, 0x62, 0x2e, 0xe1,
+ 0xb6, 0x79, 0x35, 0xfa, 0x9b, 0x54, 0x18, 0xd7, 0x80, 0x4f,
+ 0x03, 0xcc, 0xc1, 0x0e, 0x42, 0x8d, 0xda, 0x15, 0x59, 0x96,
+ 0xf7, 0x38, 0x74, 0xbb, 0xec, 0x23, 0x6f, 0xa0, 0x75, 0xba,
+ 0xf6, 0x39, 0x6e, 0xa1, 0xed, 0x22, 0x43, 0x8c, 0xc0, 0x0f,
+ 0x58, 0x97, 0xdb, 0x14, 0x19, 0xd6, 0x9a, 0x55, 0x02, 0xcd,
+ 0x81, 0x4e, 0x2f, 0xe0, 0xac, 0x63, 0x34, 0xfb, 0xb7, 0x78,
+ 0x47, 0x88, 0xc4, 0x0b, 0x5c, 0x93, 0xdf, 0x10, 0x71, 0xbe,
+ 0xf2, 0x3d, 0x6a, 0xa5, 0xe9, 0x26, 0x2b, 0xe4, 0xa8, 0x67,
+ 0x30, 0xff, 0xb3, 0x7c, 0x1d, 0xd2, 0x9e, 0x51, 0x06, 0xc9,
+ 0x85, 0x4a, 0x9f, 0x50, 0x1c, 0xd3, 0x84, 0x4b, 0x07, 0xc8,
+ 0xa9, 0x66, 0x2a, 0xe5, 0xb2, 0x7d, 0x31, 0xfe, 0xf3, 0x3c,
+ 0x70, 0xbf, 0xe8, 0x27, 0x6b, 0xa4, 0xc5, 0x0a, 0x46, 0x89,
+ 0xde, 0x11, 0x5d, 0x92, 0xea, 0x25, 0x69, 0xa6, 0xf1, 0x3e,
+ 0x72, 0xbd, 0xdc, 0x13, 0x5f, 0x90, 0xc7, 0x08, 0x44, 0x8b,
+ 0x86, 0x49, 0x05, 0xca, 0x9d, 0x52, 0x1e, 0xd1, 0xb0, 0x7f,
+ 0x33, 0xfc, 0xab, 0x64, 0x28, 0xe7, 0x32, 0xfd, 0xb1, 0x7e,
+ 0x29, 0xe6, 0xaa, 0x65, 0x04, 0xcb, 0x87, 0x48, 0x1f, 0xd0,
+ 0x9c, 0x53, 0x5e, 0x91, 0xdd, 0x12, 0x45, 0x8a, 0xc6, 0x09,
+ 0x68, 0xa7, 0xeb, 0x24, 0x73, 0xbc, 0xf0, 0x3f, 0x00, 0xd0,
+ 0xbd, 0x6d, 0x67, 0xb7, 0xda, 0x0a, 0xce, 0x1e, 0x73, 0xa3,
+ 0xa9, 0x79, 0x14, 0xc4, 0x81, 0x51, 0x3c, 0xec, 0xe6, 0x36,
+ 0x5b, 0x8b, 0x4f, 0x9f, 0xf2, 0x22, 0x28, 0xf8, 0x95, 0x45,
+ 0x1f, 0xcf, 0xa2, 0x72, 0x78, 0xa8, 0xc5, 0x15, 0xd1, 0x01,
+ 0x6c, 0xbc, 0xb6, 0x66, 0x0b, 0xdb, 0x9e, 0x4e, 0x23, 0xf3,
+ 0xf9, 0x29, 0x44, 0x94, 0x50, 0x80, 0xed, 0x3d, 0x37, 0xe7,
+ 0x8a, 0x5a, 0x3e, 0xee, 0x83, 0x53, 0x59, 0x89, 0xe4, 0x34,
+ 0xf0, 0x20, 0x4d, 0x9d, 0x97, 0x47, 0x2a, 0xfa, 0xbf, 0x6f,
+ 0x02, 0xd2, 0xd8, 0x08, 0x65, 0xb5, 0x71, 0xa1, 0xcc, 0x1c,
+ 0x16, 0xc6, 0xab, 0x7b, 0x21, 0xf1, 0x9c, 0x4c, 0x46, 0x96,
+ 0xfb, 0x2b, 0xef, 0x3f, 0x52, 0x82, 0x88, 0x58, 0x35, 0xe5,
+ 0xa0, 0x70, 0x1d, 0xcd, 0xc7, 0x17, 0x7a, 0xaa, 0x6e, 0xbe,
+ 0xd3, 0x03, 0x09, 0xd9, 0xb4, 0x64, 0x7c, 0xac, 0xc1, 0x11,
+ 0x1b, 0xcb, 0xa6, 0x76, 0xb2, 0x62, 0x0f, 0xdf, 0xd5, 0x05,
+ 0x68, 0xb8, 0xfd, 0x2d, 0x40, 0x90, 0x9a, 0x4a, 0x27, 0xf7,
+ 0x33, 0xe3, 0x8e, 0x5e, 0x54, 0x84, 0xe9, 0x39, 0x63, 0xb3,
+ 0xde, 0x0e, 0x04, 0xd4, 0xb9, 0x69, 0xad, 0x7d, 0x10, 0xc0,
+ 0xca, 0x1a, 0x77, 0xa7, 0xe2, 0x32, 0x5f, 0x8f, 0x85, 0x55,
+ 0x38, 0xe8, 0x2c, 0xfc, 0x91, 0x41, 0x4b, 0x9b, 0xf6, 0x26,
+ 0x42, 0x92, 0xff, 0x2f, 0x25, 0xf5, 0x98, 0x48, 0x8c, 0x5c,
+ 0x31, 0xe1, 0xeb, 0x3b, 0x56, 0x86, 0xc3, 0x13, 0x7e, 0xae,
+ 0xa4, 0x74, 0x19, 0xc9, 0x0d, 0xdd, 0xb0, 0x60, 0x6a, 0xba,
+ 0xd7, 0x07, 0x5d, 0x8d, 0xe0, 0x30, 0x3a, 0xea, 0x87, 0x57,
+ 0x93, 0x43, 0x2e, 0xfe, 0xf4, 0x24, 0x49, 0x99, 0xdc, 0x0c,
+ 0x61, 0xb1, 0xbb, 0x6b, 0x06, 0xd6, 0x12, 0xc2, 0xaf, 0x7f,
+ 0x75, 0xa5, 0xc8, 0x18, 0x00, 0xd1, 0xbf, 0x6e, 0x63, 0xb2,
+ 0xdc, 0x0d, 0xc6, 0x17, 0x79, 0xa8, 0xa5, 0x74, 0x1a, 0xcb,
+ 0x91, 0x40, 0x2e, 0xff, 0xf2, 0x23, 0x4d, 0x9c, 0x57, 0x86,
+ 0xe8, 0x39, 0x34, 0xe5, 0x8b, 0x5a, 0x3f, 0xee, 0x80, 0x51,
+ 0x5c, 0x8d, 0xe3, 0x32, 0xf9, 0x28, 0x46, 0x97, 0x9a, 0x4b,
+ 0x25, 0xf4, 0xae, 0x7f, 0x11, 0xc0, 0xcd, 0x1c, 0x72, 0xa3,
+ 0x68, 0xb9, 0xd7, 0x06, 0x0b, 0xda, 0xb4, 0x65, 0x7e, 0xaf,
+ 0xc1, 0x10, 0x1d, 0xcc, 0xa2, 0x73, 0xb8, 0x69, 0x07, 0xd6,
+ 0xdb, 0x0a, 0x64, 0xb5, 0xef, 0x3e, 0x50, 0x81, 0x8c, 0x5d,
+ 0x33, 0xe2, 0x29, 0xf8, 0x96, 0x47, 0x4a, 0x9b, 0xf5, 0x24,
+ 0x41, 0x90, 0xfe, 0x2f, 0x22, 0xf3, 0x9d, 0x4c, 0x87, 0x56,
+ 0x38, 0xe9, 0xe4, 0x35, 0x5b, 0x8a, 0xd0, 0x01, 0x6f, 0xbe,
+ 0xb3, 0x62, 0x0c, 0xdd, 0x16, 0xc7, 0xa9, 0x78, 0x75, 0xa4,
+ 0xca, 0x1b, 0xfc, 0x2d, 0x43, 0x92, 0x9f, 0x4e, 0x20, 0xf1,
+ 0x3a, 0xeb, 0x85, 0x54, 0x59, 0x88, 0xe6, 0x37, 0x6d, 0xbc,
+ 0xd2, 0x03, 0x0e, 0xdf, 0xb1, 0x60, 0xab, 0x7a, 0x14, 0xc5,
+ 0xc8, 0x19, 0x77, 0xa6, 0xc3, 0x12, 0x7c, 0xad, 0xa0, 0x71,
+ 0x1f, 0xce, 0x05, 0xd4, 0xba, 0x6b, 0x66, 0xb7, 0xd9, 0x08,
+ 0x52, 0x83, 0xed, 0x3c, 0x31, 0xe0, 0x8e, 0x5f, 0x94, 0x45,
+ 0x2b, 0xfa, 0xf7, 0x26, 0x48, 0x99, 0x82, 0x53, 0x3d, 0xec,
+ 0xe1, 0x30, 0x5e, 0x8f, 0x44, 0x95, 0xfb, 0x2a, 0x27, 0xf6,
+ 0x98, 0x49, 0x13, 0xc2, 0xac, 0x7d, 0x70, 0xa1, 0xcf, 0x1e,
+ 0xd5, 0x04, 0x6a, 0xbb, 0xb6, 0x67, 0x09, 0xd8, 0xbd, 0x6c,
+ 0x02, 0xd3, 0xde, 0x0f, 0x61, 0xb0, 0x7b, 0xaa, 0xc4, 0x15,
+ 0x18, 0xc9, 0xa7, 0x76, 0x2c, 0xfd, 0x93, 0x42, 0x4f, 0x9e,
+ 0xf0, 0x21, 0xea, 0x3b, 0x55, 0x84, 0x89, 0x58, 0x36, 0xe7,
+ 0x00, 0xd2, 0xb9, 0x6b, 0x6f, 0xbd, 0xd6, 0x04, 0xde, 0x0c,
+ 0x67, 0xb5, 0xb1, 0x63, 0x08, 0xda, 0xa1, 0x73, 0x18, 0xca,
+ 0xce, 0x1c, 0x77, 0xa5, 0x7f, 0xad, 0xc6, 0x14, 0x10, 0xc2,
+ 0xa9, 0x7b, 0x5f, 0x8d, 0xe6, 0x34, 0x30, 0xe2, 0x89, 0x5b,
+ 0x81, 0x53, 0x38, 0xea, 0xee, 0x3c, 0x57, 0x85, 0xfe, 0x2c,
+ 0x47, 0x95, 0x91, 0x43, 0x28, 0xfa, 0x20, 0xf2, 0x99, 0x4b,
+ 0x4f, 0x9d, 0xf6, 0x24, 0xbe, 0x6c, 0x07, 0xd5, 0xd1, 0x03,
+ 0x68, 0xba, 0x60, 0xb2, 0xd9, 0x0b, 0x0f, 0xdd, 0xb6, 0x64,
+ 0x1f, 0xcd, 0xa6, 0x74, 0x70, 0xa2, 0xc9, 0x1b, 0xc1, 0x13,
+ 0x78, 0xaa, 0xae, 0x7c, 0x17, 0xc5, 0xe1, 0x33, 0x58, 0x8a,
+ 0x8e, 0x5c, 0x37, 0xe5, 0x3f, 0xed, 0x86, 0x54, 0x50, 0x82,
+ 0xe9, 0x3b, 0x40, 0x92, 0xf9, 0x2b, 0x2f, 0xfd, 0x96, 0x44,
+ 0x9e, 0x4c, 0x27, 0xf5, 0xf1, 0x23, 0x48, 0x9a, 0x61, 0xb3,
+ 0xd8, 0x0a, 0x0e, 0xdc, 0xb7, 0x65, 0xbf, 0x6d, 0x06, 0xd4,
+ 0xd0, 0x02, 0x69, 0xbb, 0xc0, 0x12, 0x79, 0xab, 0xaf, 0x7d,
+ 0x16, 0xc4, 0x1e, 0xcc, 0xa7, 0x75, 0x71, 0xa3, 0xc8, 0x1a,
+ 0x3e, 0xec, 0x87, 0x55, 0x51, 0x83, 0xe8, 0x3a, 0xe0, 0x32,
+ 0x59, 0x8b, 0x8f, 0x5d, 0x36, 0xe4, 0x9f, 0x4d, 0x26, 0xf4,
+ 0xf0, 0x22, 0x49, 0x9b, 0x41, 0x93, 0xf8, 0x2a, 0x2e, 0xfc,
+ 0x97, 0x45, 0xdf, 0x0d, 0x66, 0xb4, 0xb0, 0x62, 0x09, 0xdb,
+ 0x01, 0xd3, 0xb8, 0x6a, 0x6e, 0xbc, 0xd7, 0x05, 0x7e, 0xac,
+ 0xc7, 0x15, 0x11, 0xc3, 0xa8, 0x7a, 0xa0, 0x72, 0x19, 0xcb,
+ 0xcf, 0x1d, 0x76, 0xa4, 0x80, 0x52, 0x39, 0xeb, 0xef, 0x3d,
+ 0x56, 0x84, 0x5e, 0x8c, 0xe7, 0x35, 0x31, 0xe3, 0x88, 0x5a,
+ 0x21, 0xf3, 0x98, 0x4a, 0x4e, 0x9c, 0xf7, 0x25, 0xff, 0x2d,
+ 0x46, 0x94, 0x90, 0x42, 0x29, 0xfb, 0x00, 0xd3, 0xbb, 0x68,
+ 0x6b, 0xb8, 0xd0, 0x03, 0xd6, 0x05, 0x6d, 0xbe, 0xbd, 0x6e,
+ 0x06, 0xd5, 0xb1, 0x62, 0x0a, 0xd9, 0xda, 0x09, 0x61, 0xb2,
+ 0x67, 0xb4, 0xdc, 0x0f, 0x0c, 0xdf, 0xb7, 0x64, 0x7f, 0xac,
+ 0xc4, 0x17, 0x14, 0xc7, 0xaf, 0x7c, 0xa9, 0x7a, 0x12, 0xc1,
+ 0xc2, 0x11, 0x79, 0xaa, 0xce, 0x1d, 0x75, 0xa6, 0xa5, 0x76,
+ 0x1e, 0xcd, 0x18, 0xcb, 0xa3, 0x70, 0x73, 0xa0, 0xc8, 0x1b,
+ 0xfe, 0x2d, 0x45, 0x96, 0x95, 0x46, 0x2e, 0xfd, 0x28, 0xfb,
+ 0x93, 0x40, 0x43, 0x90, 0xf8, 0x2b, 0x4f, 0x9c, 0xf4, 0x27,
+ 0x24, 0xf7, 0x9f, 0x4c, 0x99, 0x4a, 0x22, 0xf1, 0xf2, 0x21,
+ 0x49, 0x9a, 0x81, 0x52, 0x3a, 0xe9, 0xea, 0x39, 0x51, 0x82,
+ 0x57, 0x84, 0xec, 0x3f, 0x3c, 0xef, 0x87, 0x54, 0x30, 0xe3,
+ 0x8b, 0x58, 0x5b, 0x88, 0xe0, 0x33, 0xe6, 0x35, 0x5d, 0x8e,
+ 0x8d, 0x5e, 0x36, 0xe5, 0xe1, 0x32, 0x5a, 0x89, 0x8a, 0x59,
+ 0x31, 0xe2, 0x37, 0xe4, 0x8c, 0x5f, 0x5c, 0x8f, 0xe7, 0x34,
+ 0x50, 0x83, 0xeb, 0x38, 0x3b, 0xe8, 0x80, 0x53, 0x86, 0x55,
+ 0x3d, 0xee, 0xed, 0x3e, 0x56, 0x85, 0x9e, 0x4d, 0x25, 0xf6,
+ 0xf5, 0x26, 0x4e, 0x9d, 0x48, 0x9b, 0xf3, 0x20, 0x23, 0xf0,
+ 0x98, 0x4b, 0x2f, 0xfc, 0x94, 0x47, 0x44, 0x97, 0xff, 0x2c,
+ 0xf9, 0x2a, 0x42, 0x91, 0x92, 0x41, 0x29, 0xfa, 0x1f, 0xcc,
+ 0xa4, 0x77, 0x74, 0xa7, 0xcf, 0x1c, 0xc9, 0x1a, 0x72, 0xa1,
+ 0xa2, 0x71, 0x19, 0xca, 0xae, 0x7d, 0x15, 0xc6, 0xc5, 0x16,
+ 0x7e, 0xad, 0x78, 0xab, 0xc3, 0x10, 0x13, 0xc0, 0xa8, 0x7b,
+ 0x60, 0xb3, 0xdb, 0x08, 0x0b, 0xd8, 0xb0, 0x63, 0xb6, 0x65,
+ 0x0d, 0xde, 0xdd, 0x0e, 0x66, 0xb5, 0xd1, 0x02, 0x6a, 0xb9,
+ 0xba, 0x69, 0x01, 0xd2, 0x07, 0xd4, 0xbc, 0x6f, 0x6c, 0xbf,
+ 0xd7, 0x04, 0x00, 0xd4, 0xb5, 0x61, 0x77, 0xa3, 0xc2, 0x16,
+ 0xee, 0x3a, 0x5b, 0x8f, 0x99, 0x4d, 0x2c, 0xf8, 0xc1, 0x15,
+ 0x74, 0xa0, 0xb6, 0x62, 0x03, 0xd7, 0x2f, 0xfb, 0x9a, 0x4e,
+ 0x58, 0x8c, 0xed, 0x39, 0x9f, 0x4b, 0x2a, 0xfe, 0xe8, 0x3c,
+ 0x5d, 0x89, 0x71, 0xa5, 0xc4, 0x10, 0x06, 0xd2, 0xb3, 0x67,
+ 0x5e, 0x8a, 0xeb, 0x3f, 0x29, 0xfd, 0x9c, 0x48, 0xb0, 0x64,
+ 0x05, 0xd1, 0xc7, 0x13, 0x72, 0xa6, 0x23, 0xf7, 0x96, 0x42,
+ 0x54, 0x80, 0xe1, 0x35, 0xcd, 0x19, 0x78, 0xac, 0xba, 0x6e,
+ 0x0f, 0xdb, 0xe2, 0x36, 0x57, 0x83, 0x95, 0x41, 0x20, 0xf4,
+ 0x0c, 0xd8, 0xb9, 0x6d, 0x7b, 0xaf, 0xce, 0x1a, 0xbc, 0x68,
+ 0x09, 0xdd, 0xcb, 0x1f, 0x7e, 0xaa, 0x52, 0x86, 0xe7, 0x33,
+ 0x25, 0xf1, 0x90, 0x44, 0x7d, 0xa9, 0xc8, 0x1c, 0x0a, 0xde,
+ 0xbf, 0x6b, 0x93, 0x47, 0x26, 0xf2, 0xe4, 0x30, 0x51, 0x85,
+ 0x46, 0x92, 0xf3, 0x27, 0x31, 0xe5, 0x84, 0x50, 0xa8, 0x7c,
+ 0x1d, 0xc9, 0xdf, 0x0b, 0x6a, 0xbe, 0x87, 0x53, 0x32, 0xe6,
+ 0xf0, 0x24, 0x45, 0x91, 0x69, 0xbd, 0xdc, 0x08, 0x1e, 0xca,
+ 0xab, 0x7f, 0xd9, 0x0d, 0x6c, 0xb8, 0xae, 0x7a, 0x1b, 0xcf,
+ 0x37, 0xe3, 0x82, 0x56, 0x40, 0x94, 0xf5, 0x21, 0x18, 0xcc,
+ 0xad, 0x79, 0x6f, 0xbb, 0xda, 0x0e, 0xf6, 0x22, 0x43, 0x97,
+ 0x81, 0x55, 0x34, 0xe0, 0x65, 0xb1, 0xd0, 0x04, 0x12, 0xc6,
+ 0xa7, 0x73, 0x8b, 0x5f, 0x3e, 0xea, 0xfc, 0x28, 0x49, 0x9d,
+ 0xa4, 0x70, 0x11, 0xc5, 0xd3, 0x07, 0x66, 0xb2, 0x4a, 0x9e,
+ 0xff, 0x2b, 0x3d, 0xe9, 0x88, 0x5c, 0xfa, 0x2e, 0x4f, 0x9b,
+ 0x8d, 0x59, 0x38, 0xec, 0x14, 0xc0, 0xa1, 0x75, 0x63, 0xb7,
+ 0xd6, 0x02, 0x3b, 0xef, 0x8e, 0x5a, 0x4c, 0x98, 0xf9, 0x2d,
+ 0xd5, 0x01, 0x60, 0xb4, 0xa2, 0x76, 0x17, 0xc3, 0x00, 0xd5,
+ 0xb7, 0x62, 0x73, 0xa6, 0xc4, 0x11, 0xe6, 0x33, 0x51, 0x84,
+ 0x95, 0x40, 0x22, 0xf7, 0xd1, 0x04, 0x66, 0xb3, 0xa2, 0x77,
+ 0x15, 0xc0, 0x37, 0xe2, 0x80, 0x55, 0x44, 0x91, 0xf3, 0x26,
+ 0xbf, 0x6a, 0x08, 0xdd, 0xcc, 0x19, 0x7b, 0xae, 0x59, 0x8c,
+ 0xee, 0x3b, 0x2a, 0xff, 0x9d, 0x48, 0x6e, 0xbb, 0xd9, 0x0c,
+ 0x1d, 0xc8, 0xaa, 0x7f, 0x88, 0x5d, 0x3f, 0xea, 0xfb, 0x2e,
+ 0x4c, 0x99, 0x63, 0xb6, 0xd4, 0x01, 0x10, 0xc5, 0xa7, 0x72,
+ 0x85, 0x50, 0x32, 0xe7, 0xf6, 0x23, 0x41, 0x94, 0xb2, 0x67,
+ 0x05, 0xd0, 0xc1, 0x14, 0x76, 0xa3, 0x54, 0x81, 0xe3, 0x36,
+ 0x27, 0xf2, 0x90, 0x45, 0xdc, 0x09, 0x6b, 0xbe, 0xaf, 0x7a,
+ 0x18, 0xcd, 0x3a, 0xef, 0x8d, 0x58, 0x49, 0x9c, 0xfe, 0x2b,
+ 0x0d, 0xd8, 0xba, 0x6f, 0x7e, 0xab, 0xc9, 0x1c, 0xeb, 0x3e,
+ 0x5c, 0x89, 0x98, 0x4d, 0x2f, 0xfa, 0xc6, 0x13, 0x71, 0xa4,
+ 0xb5, 0x60, 0x02, 0xd7, 0x20, 0xf5, 0x97, 0x42, 0x53, 0x86,
+ 0xe4, 0x31, 0x17, 0xc2, 0xa0, 0x75, 0x64, 0xb1, 0xd3, 0x06,
+ 0xf1, 0x24, 0x46, 0x93, 0x82, 0x57, 0x35, 0xe0, 0x79, 0xac,
+ 0xce, 0x1b, 0x0a, 0xdf, 0xbd, 0x68, 0x9f, 0x4a, 0x28, 0xfd,
+ 0xec, 0x39, 0x5b, 0x8e, 0xa8, 0x7d, 0x1f, 0xca, 0xdb, 0x0e,
+ 0x6c, 0xb9, 0x4e, 0x9b, 0xf9, 0x2c, 0x3d, 0xe8, 0x8a, 0x5f,
+ 0xa5, 0x70, 0x12, 0xc7, 0xd6, 0x03, 0x61, 0xb4, 0x43, 0x96,
+ 0xf4, 0x21, 0x30, 0xe5, 0x87, 0x52, 0x74, 0xa1, 0xc3, 0x16,
+ 0x07, 0xd2, 0xb0, 0x65, 0x92, 0x47, 0x25, 0xf0, 0xe1, 0x34,
+ 0x56, 0x83, 0x1a, 0xcf, 0xad, 0x78, 0x69, 0xbc, 0xde, 0x0b,
+ 0xfc, 0x29, 0x4b, 0x9e, 0x8f, 0x5a, 0x38, 0xed, 0xcb, 0x1e,
+ 0x7c, 0xa9, 0xb8, 0x6d, 0x0f, 0xda, 0x2d, 0xf8, 0x9a, 0x4f,
+ 0x5e, 0x8b, 0xe9, 0x3c, 0x00, 0xd6, 0xb1, 0x67, 0x7f, 0xa9,
+ 0xce, 0x18, 0xfe, 0x28, 0x4f, 0x99, 0x81, 0x57, 0x30, 0xe6,
+ 0xe1, 0x37, 0x50, 0x86, 0x9e, 0x48, 0x2f, 0xf9, 0x1f, 0xc9,
+ 0xae, 0x78, 0x60, 0xb6, 0xd1, 0x07, 0xdf, 0x09, 0x6e, 0xb8,
+ 0xa0, 0x76, 0x11, 0xc7, 0x21, 0xf7, 0x90, 0x46, 0x5e, 0x88,
+ 0xef, 0x39, 0x3e, 0xe8, 0x8f, 0x59, 0x41, 0x97, 0xf0, 0x26,
+ 0xc0, 0x16, 0x71, 0xa7, 0xbf, 0x69, 0x0e, 0xd8, 0xa3, 0x75,
+ 0x12, 0xc4, 0xdc, 0x0a, 0x6d, 0xbb, 0x5d, 0x8b, 0xec, 0x3a,
+ 0x22, 0xf4, 0x93, 0x45, 0x42, 0x94, 0xf3, 0x25, 0x3d, 0xeb,
+ 0x8c, 0x5a, 0xbc, 0x6a, 0x0d, 0xdb, 0xc3, 0x15, 0x72, 0xa4,
+ 0x7c, 0xaa, 0xcd, 0x1b, 0x03, 0xd5, 0xb2, 0x64, 0x82, 0x54,
+ 0x33, 0xe5, 0xfd, 0x2b, 0x4c, 0x9a, 0x9d, 0x4b, 0x2c, 0xfa,
+ 0xe2, 0x34, 0x53, 0x85, 0x63, 0xb5, 0xd2, 0x04, 0x1c, 0xca,
+ 0xad, 0x7b, 0x5b, 0x8d, 0xea, 0x3c, 0x24, 0xf2, 0x95, 0x43,
+ 0xa5, 0x73, 0x14, 0xc2, 0xda, 0x0c, 0x6b, 0xbd, 0xba, 0x6c,
+ 0x0b, 0xdd, 0xc5, 0x13, 0x74, 0xa2, 0x44, 0x92, 0xf5, 0x23,
+ 0x3b, 0xed, 0x8a, 0x5c, 0x84, 0x52, 0x35, 0xe3, 0xfb, 0x2d,
+ 0x4a, 0x9c, 0x7a, 0xac, 0xcb, 0x1d, 0x05, 0xd3, 0xb4, 0x62,
+ 0x65, 0xb3, 0xd4, 0x02, 0x1a, 0xcc, 0xab, 0x7d, 0x9b, 0x4d,
+ 0x2a, 0xfc, 0xe4, 0x32, 0x55, 0x83, 0xf8, 0x2e, 0x49, 0x9f,
+ 0x87, 0x51, 0x36, 0xe0, 0x06, 0xd0, 0xb7, 0x61, 0x79, 0xaf,
+ 0xc8, 0x1e, 0x19, 0xcf, 0xa8, 0x7e, 0x66, 0xb0, 0xd7, 0x01,
+ 0xe7, 0x31, 0x56, 0x80, 0x98, 0x4e, 0x29, 0xff, 0x27, 0xf1,
+ 0x96, 0x40, 0x58, 0x8e, 0xe9, 0x3f, 0xd9, 0x0f, 0x68, 0xbe,
+ 0xa6, 0x70, 0x17, 0xc1, 0xc6, 0x10, 0x77, 0xa1, 0xb9, 0x6f,
+ 0x08, 0xde, 0x38, 0xee, 0x89, 0x5f, 0x47, 0x91, 0xf6, 0x20,
+ 0x00, 0xd7, 0xb3, 0x64, 0x7b, 0xac, 0xc8, 0x1f, 0xf6, 0x21,
+ 0x45, 0x92, 0x8d, 0x5a, 0x3e, 0xe9, 0xf1, 0x26, 0x42, 0x95,
+ 0x8a, 0x5d, 0x39, 0xee, 0x07, 0xd0, 0xb4, 0x63, 0x7c, 0xab,
+ 0xcf, 0x18, 0xff, 0x28, 0x4c, 0x9b, 0x84, 0x53, 0x37, 0xe0,
+ 0x09, 0xde, 0xba, 0x6d, 0x72, 0xa5, 0xc1, 0x16, 0x0e, 0xd9,
+ 0xbd, 0x6a, 0x75, 0xa2, 0xc6, 0x11, 0xf8, 0x2f, 0x4b, 0x9c,
+ 0x83, 0x54, 0x30, 0xe7, 0xe3, 0x34, 0x50, 0x87, 0x98, 0x4f,
+ 0x2b, 0xfc, 0x15, 0xc2, 0xa6, 0x71, 0x6e, 0xb9, 0xdd, 0x0a,
+ 0x12, 0xc5, 0xa1, 0x76, 0x69, 0xbe, 0xda, 0x0d, 0xe4, 0x33,
+ 0x57, 0x80, 0x9f, 0x48, 0x2c, 0xfb, 0x1c, 0xcb, 0xaf, 0x78,
+ 0x67, 0xb0, 0xd4, 0x03, 0xea, 0x3d, 0x59, 0x8e, 0x91, 0x46,
+ 0x22, 0xf5, 0xed, 0x3a, 0x5e, 0x89, 0x96, 0x41, 0x25, 0xf2,
+ 0x1b, 0xcc, 0xa8, 0x7f, 0x60, 0xb7, 0xd3, 0x04, 0xdb, 0x0c,
+ 0x68, 0xbf, 0xa0, 0x77, 0x13, 0xc4, 0x2d, 0xfa, 0x9e, 0x49,
+ 0x56, 0x81, 0xe5, 0x32, 0x2a, 0xfd, 0x99, 0x4e, 0x51, 0x86,
+ 0xe2, 0x35, 0xdc, 0x0b, 0x6f, 0xb8, 0xa7, 0x70, 0x14, 0xc3,
+ 0x24, 0xf3, 0x97, 0x40, 0x5f, 0x88, 0xec, 0x3b, 0xd2, 0x05,
+ 0x61, 0xb6, 0xa9, 0x7e, 0x1a, 0xcd, 0xd5, 0x02, 0x66, 0xb1,
+ 0xae, 0x79, 0x1d, 0xca, 0x23, 0xf4, 0x90, 0x47, 0x58, 0x8f,
+ 0xeb, 0x3c, 0x38, 0xef, 0x8b, 0x5c, 0x43, 0x94, 0xf0, 0x27,
+ 0xce, 0x19, 0x7d, 0xaa, 0xb5, 0x62, 0x06, 0xd1, 0xc9, 0x1e,
+ 0x7a, 0xad, 0xb2, 0x65, 0x01, 0xd6, 0x3f, 0xe8, 0x8c, 0x5b,
+ 0x44, 0x93, 0xf7, 0x20, 0xc7, 0x10, 0x74, 0xa3, 0xbc, 0x6b,
+ 0x0f, 0xd8, 0x31, 0xe6, 0x82, 0x55, 0x4a, 0x9d, 0xf9, 0x2e,
+ 0x36, 0xe1, 0x85, 0x52, 0x4d, 0x9a, 0xfe, 0x29, 0xc0, 0x17,
+ 0x73, 0xa4, 0xbb, 0x6c, 0x08, 0xdf, 0x00, 0xd8, 0xad, 0x75,
+ 0x47, 0x9f, 0xea, 0x32, 0x8e, 0x56, 0x23, 0xfb, 0xc9, 0x11,
+ 0x64, 0xbc, 0x01, 0xd9, 0xac, 0x74, 0x46, 0x9e, 0xeb, 0x33,
+ 0x8f, 0x57, 0x22, 0xfa, 0xc8, 0x10, 0x65, 0xbd, 0x02, 0xda,
+ 0xaf, 0x77, 0x45, 0x9d, 0xe8, 0x30, 0x8c, 0x54, 0x21, 0xf9,
+ 0xcb, 0x13, 0x66, 0xbe, 0x03, 0xdb, 0xae, 0x76, 0x44, 0x9c,
+ 0xe9, 0x31, 0x8d, 0x55, 0x20, 0xf8, 0xca, 0x12, 0x67, 0xbf,
+ 0x04, 0xdc, 0xa9, 0x71, 0x43, 0x9b, 0xee, 0x36, 0x8a, 0x52,
+ 0x27, 0xff, 0xcd, 0x15, 0x60, 0xb8, 0x05, 0xdd, 0xa8, 0x70,
+ 0x42, 0x9a, 0xef, 0x37, 0x8b, 0x53, 0x26, 0xfe, 0xcc, 0x14,
+ 0x61, 0xb9, 0x06, 0xde, 0xab, 0x73, 0x41, 0x99, 0xec, 0x34,
+ 0x88, 0x50, 0x25, 0xfd, 0xcf, 0x17, 0x62, 0xba, 0x07, 0xdf,
+ 0xaa, 0x72, 0x40, 0x98, 0xed, 0x35, 0x89, 0x51, 0x24, 0xfc,
+ 0xce, 0x16, 0x63, 0xbb, 0x08, 0xd0, 0xa5, 0x7d, 0x4f, 0x97,
+ 0xe2, 0x3a, 0x86, 0x5e, 0x2b, 0xf3, 0xc1, 0x19, 0x6c, 0xb4,
+ 0x09, 0xd1, 0xa4, 0x7c, 0x4e, 0x96, 0xe3, 0x3b, 0x87, 0x5f,
+ 0x2a, 0xf2, 0xc0, 0x18, 0x6d, 0xb5, 0x0a, 0xd2, 0xa7, 0x7f,
+ 0x4d, 0x95, 0xe0, 0x38, 0x84, 0x5c, 0x29, 0xf1, 0xc3, 0x1b,
+ 0x6e, 0xb6, 0x0b, 0xd3, 0xa6, 0x7e, 0x4c, 0x94, 0xe1, 0x39,
+ 0x85, 0x5d, 0x28, 0xf0, 0xc2, 0x1a, 0x6f, 0xb7, 0x0c, 0xd4,
+ 0xa1, 0x79, 0x4b, 0x93, 0xe6, 0x3e, 0x82, 0x5a, 0x2f, 0xf7,
+ 0xc5, 0x1d, 0x68, 0xb0, 0x0d, 0xd5, 0xa0, 0x78, 0x4a, 0x92,
+ 0xe7, 0x3f, 0x83, 0x5b, 0x2e, 0xf6, 0xc4, 0x1c, 0x69, 0xb1,
+ 0x0e, 0xd6, 0xa3, 0x7b, 0x49, 0x91, 0xe4, 0x3c, 0x80, 0x58,
+ 0x2d, 0xf5, 0xc7, 0x1f, 0x6a, 0xb2, 0x0f, 0xd7, 0xa2, 0x7a,
+ 0x48, 0x90, 0xe5, 0x3d, 0x81, 0x59, 0x2c, 0xf4, 0xc6, 0x1e,
+ 0x6b, 0xb3, 0x00, 0xd9, 0xaf, 0x76, 0x43, 0x9a, 0xec, 0x35,
+ 0x86, 0x5f, 0x29, 0xf0, 0xc5, 0x1c, 0x6a, 0xb3, 0x11, 0xc8,
+ 0xbe, 0x67, 0x52, 0x8b, 0xfd, 0x24, 0x97, 0x4e, 0x38, 0xe1,
+ 0xd4, 0x0d, 0x7b, 0xa2, 0x22, 0xfb, 0x8d, 0x54, 0x61, 0xb8,
+ 0xce, 0x17, 0xa4, 0x7d, 0x0b, 0xd2, 0xe7, 0x3e, 0x48, 0x91,
+ 0x33, 0xea, 0x9c, 0x45, 0x70, 0xa9, 0xdf, 0x06, 0xb5, 0x6c,
+ 0x1a, 0xc3, 0xf6, 0x2f, 0x59, 0x80, 0x44, 0x9d, 0xeb, 0x32,
+ 0x07, 0xde, 0xa8, 0x71, 0xc2, 0x1b, 0x6d, 0xb4, 0x81, 0x58,
+ 0x2e, 0xf7, 0x55, 0x8c, 0xfa, 0x23, 0x16, 0xcf, 0xb9, 0x60,
+ 0xd3, 0x0a, 0x7c, 0xa5, 0x90, 0x49, 0x3f, 0xe6, 0x66, 0xbf,
+ 0xc9, 0x10, 0x25, 0xfc, 0x8a, 0x53, 0xe0, 0x39, 0x4f, 0x96,
+ 0xa3, 0x7a, 0x0c, 0xd5, 0x77, 0xae, 0xd8, 0x01, 0x34, 0xed,
+ 0x9b, 0x42, 0xf1, 0x28, 0x5e, 0x87, 0xb2, 0x6b, 0x1d, 0xc4,
+ 0x88, 0x51, 0x27, 0xfe, 0xcb, 0x12, 0x64, 0xbd, 0x0e, 0xd7,
+ 0xa1, 0x78, 0x4d, 0x94, 0xe2, 0x3b, 0x99, 0x40, 0x36, 0xef,
+ 0xda, 0x03, 0x75, 0xac, 0x1f, 0xc6, 0xb0, 0x69, 0x5c, 0x85,
+ 0xf3, 0x2a, 0xaa, 0x73, 0x05, 0xdc, 0xe9, 0x30, 0x46, 0x9f,
+ 0x2c, 0xf5, 0x83, 0x5a, 0x6f, 0xb6, 0xc0, 0x19, 0xbb, 0x62,
+ 0x14, 0xcd, 0xf8, 0x21, 0x57, 0x8e, 0x3d, 0xe4, 0x92, 0x4b,
+ 0x7e, 0xa7, 0xd1, 0x08, 0xcc, 0x15, 0x63, 0xba, 0x8f, 0x56,
+ 0x20, 0xf9, 0x4a, 0x93, 0xe5, 0x3c, 0x09, 0xd0, 0xa6, 0x7f,
+ 0xdd, 0x04, 0x72, 0xab, 0x9e, 0x47, 0x31, 0xe8, 0x5b, 0x82,
+ 0xf4, 0x2d, 0x18, 0xc1, 0xb7, 0x6e, 0xee, 0x37, 0x41, 0x98,
+ 0xad, 0x74, 0x02, 0xdb, 0x68, 0xb1, 0xc7, 0x1e, 0x2b, 0xf2,
+ 0x84, 0x5d, 0xff, 0x26, 0x50, 0x89, 0xbc, 0x65, 0x13, 0xca,
+ 0x79, 0xa0, 0xd6, 0x0f, 0x3a, 0xe3, 0x95, 0x4c, 0x00, 0xda,
+ 0xa9, 0x73, 0x4f, 0x95, 0xe6, 0x3c, 0x9e, 0x44, 0x37, 0xed,
+ 0xd1, 0x0b, 0x78, 0xa2, 0x21, 0xfb, 0x88, 0x52, 0x6e, 0xb4,
+ 0xc7, 0x1d, 0xbf, 0x65, 0x16, 0xcc, 0xf0, 0x2a, 0x59, 0x83,
+ 0x42, 0x98, 0xeb, 0x31, 0x0d, 0xd7, 0xa4, 0x7e, 0xdc, 0x06,
+ 0x75, 0xaf, 0x93, 0x49, 0x3a, 0xe0, 0x63, 0xb9, 0xca, 0x10,
+ 0x2c, 0xf6, 0x85, 0x5f, 0xfd, 0x27, 0x54, 0x8e, 0xb2, 0x68,
+ 0x1b, 0xc1, 0x84, 0x5e, 0x2d, 0xf7, 0xcb, 0x11, 0x62, 0xb8,
+ 0x1a, 0xc0, 0xb3, 0x69, 0x55, 0x8f, 0xfc, 0x26, 0xa5, 0x7f,
+ 0x0c, 0xd6, 0xea, 0x30, 0x43, 0x99, 0x3b, 0xe1, 0x92, 0x48,
+ 0x74, 0xae, 0xdd, 0x07, 0xc6, 0x1c, 0x6f, 0xb5, 0x89, 0x53,
+ 0x20, 0xfa, 0x58, 0x82, 0xf1, 0x2b, 0x17, 0xcd, 0xbe, 0x64,
+ 0xe7, 0x3d, 0x4e, 0x94, 0xa8, 0x72, 0x01, 0xdb, 0x79, 0xa3,
+ 0xd0, 0x0a, 0x36, 0xec, 0x9f, 0x45, 0x15, 0xcf, 0xbc, 0x66,
+ 0x5a, 0x80, 0xf3, 0x29, 0x8b, 0x51, 0x22, 0xf8, 0xc4, 0x1e,
+ 0x6d, 0xb7, 0x34, 0xee, 0x9d, 0x47, 0x7b, 0xa1, 0xd2, 0x08,
+ 0xaa, 0x70, 0x03, 0xd9, 0xe5, 0x3f, 0x4c, 0x96, 0x57, 0x8d,
+ 0xfe, 0x24, 0x18, 0xc2, 0xb1, 0x6b, 0xc9, 0x13, 0x60, 0xba,
+ 0x86, 0x5c, 0x2f, 0xf5, 0x76, 0xac, 0xdf, 0x05, 0x39, 0xe3,
+ 0x90, 0x4a, 0xe8, 0x32, 0x41, 0x9b, 0xa7, 0x7d, 0x0e, 0xd4,
+ 0x91, 0x4b, 0x38, 0xe2, 0xde, 0x04, 0x77, 0xad, 0x0f, 0xd5,
+ 0xa6, 0x7c, 0x40, 0x9a, 0xe9, 0x33, 0xb0, 0x6a, 0x19, 0xc3,
+ 0xff, 0x25, 0x56, 0x8c, 0x2e, 0xf4, 0x87, 0x5d, 0x61, 0xbb,
+ 0xc8, 0x12, 0xd3, 0x09, 0x7a, 0xa0, 0x9c, 0x46, 0x35, 0xef,
+ 0x4d, 0x97, 0xe4, 0x3e, 0x02, 0xd8, 0xab, 0x71, 0xf2, 0x28,
+ 0x5b, 0x81, 0xbd, 0x67, 0x14, 0xce, 0x6c, 0xb6, 0xc5, 0x1f,
+ 0x23, 0xf9, 0x8a, 0x50, 0x00, 0xdb, 0xab, 0x70, 0x4b, 0x90,
+ 0xe0, 0x3b, 0x96, 0x4d, 0x3d, 0xe6, 0xdd, 0x06, 0x76, 0xad,
+ 0x31, 0xea, 0x9a, 0x41, 0x7a, 0xa1, 0xd1, 0x0a, 0xa7, 0x7c,
+ 0x0c, 0xd7, 0xec, 0x37, 0x47, 0x9c, 0x62, 0xb9, 0xc9, 0x12,
+ 0x29, 0xf2, 0x82, 0x59, 0xf4, 0x2f, 0x5f, 0x84, 0xbf, 0x64,
+ 0x14, 0xcf, 0x53, 0x88, 0xf8, 0x23, 0x18, 0xc3, 0xb3, 0x68,
+ 0xc5, 0x1e, 0x6e, 0xb5, 0x8e, 0x55, 0x25, 0xfe, 0xc4, 0x1f,
+ 0x6f, 0xb4, 0x8f, 0x54, 0x24, 0xff, 0x52, 0x89, 0xf9, 0x22,
+ 0x19, 0xc2, 0xb2, 0x69, 0xf5, 0x2e, 0x5e, 0x85, 0xbe, 0x65,
+ 0x15, 0xce, 0x63, 0xb8, 0xc8, 0x13, 0x28, 0xf3, 0x83, 0x58,
+ 0xa6, 0x7d, 0x0d, 0xd6, 0xed, 0x36, 0x46, 0x9d, 0x30, 0xeb,
+ 0x9b, 0x40, 0x7b, 0xa0, 0xd0, 0x0b, 0x97, 0x4c, 0x3c, 0xe7,
+ 0xdc, 0x07, 0x77, 0xac, 0x01, 0xda, 0xaa, 0x71, 0x4a, 0x91,
+ 0xe1, 0x3a, 0x95, 0x4e, 0x3e, 0xe5, 0xde, 0x05, 0x75, 0xae,
+ 0x03, 0xd8, 0xa8, 0x73, 0x48, 0x93, 0xe3, 0x38, 0xa4, 0x7f,
+ 0x0f, 0xd4, 0xef, 0x34, 0x44, 0x9f, 0x32, 0xe9, 0x99, 0x42,
+ 0x79, 0xa2, 0xd2, 0x09, 0xf7, 0x2c, 0x5c, 0x87, 0xbc, 0x67,
+ 0x17, 0xcc, 0x61, 0xba, 0xca, 0x11, 0x2a, 0xf1, 0x81, 0x5a,
+ 0xc6, 0x1d, 0x6d, 0xb6, 0x8d, 0x56, 0x26, 0xfd, 0x50, 0x8b,
+ 0xfb, 0x20, 0x1b, 0xc0, 0xb0, 0x6b, 0x51, 0x8a, 0xfa, 0x21,
+ 0x1a, 0xc1, 0xb1, 0x6a, 0xc7, 0x1c, 0x6c, 0xb7, 0x8c, 0x57,
+ 0x27, 0xfc, 0x60, 0xbb, 0xcb, 0x10, 0x2b, 0xf0, 0x80, 0x5b,
+ 0xf6, 0x2d, 0x5d, 0x86, 0xbd, 0x66, 0x16, 0xcd, 0x33, 0xe8,
+ 0x98, 0x43, 0x78, 0xa3, 0xd3, 0x08, 0xa5, 0x7e, 0x0e, 0xd5,
+ 0xee, 0x35, 0x45, 0x9e, 0x02, 0xd9, 0xa9, 0x72, 0x49, 0x92,
+ 0xe2, 0x39, 0x94, 0x4f, 0x3f, 0xe4, 0xdf, 0x04, 0x74, 0xaf,
+ 0x00, 0xdc, 0xa5, 0x79, 0x57, 0x8b, 0xf2, 0x2e, 0xae, 0x72,
+ 0x0b, 0xd7, 0xf9, 0x25, 0x5c, 0x80, 0x41, 0x9d, 0xe4, 0x38,
+ 0x16, 0xca, 0xb3, 0x6f, 0xef, 0x33, 0x4a, 0x96, 0xb8, 0x64,
+ 0x1d, 0xc1, 0x82, 0x5e, 0x27, 0xfb, 0xd5, 0x09, 0x70, 0xac,
+ 0x2c, 0xf0, 0x89, 0x55, 0x7b, 0xa7, 0xde, 0x02, 0xc3, 0x1f,
+ 0x66, 0xba, 0x94, 0x48, 0x31, 0xed, 0x6d, 0xb1, 0xc8, 0x14,
+ 0x3a, 0xe6, 0x9f, 0x43, 0x19, 0xc5, 0xbc, 0x60, 0x4e, 0x92,
+ 0xeb, 0x37, 0xb7, 0x6b, 0x12, 0xce, 0xe0, 0x3c, 0x45, 0x99,
+ 0x58, 0x84, 0xfd, 0x21, 0x0f, 0xd3, 0xaa, 0x76, 0xf6, 0x2a,
+ 0x53, 0x8f, 0xa1, 0x7d, 0x04, 0xd8, 0x9b, 0x47, 0x3e, 0xe2,
+ 0xcc, 0x10, 0x69, 0xb5, 0x35, 0xe9, 0x90, 0x4c, 0x62, 0xbe,
+ 0xc7, 0x1b, 0xda, 0x06, 0x7f, 0xa3, 0x8d, 0x51, 0x28, 0xf4,
+ 0x74, 0xa8, 0xd1, 0x0d, 0x23, 0xff, 0x86, 0x5a, 0x32, 0xee,
+ 0x97, 0x4b, 0x65, 0xb9, 0xc0, 0x1c, 0x9c, 0x40, 0x39, 0xe5,
+ 0xcb, 0x17, 0x6e, 0xb2, 0x73, 0xaf, 0xd6, 0x0a, 0x24, 0xf8,
+ 0x81, 0x5d, 0xdd, 0x01, 0x78, 0xa4, 0x8a, 0x56, 0x2f, 0xf3,
+ 0xb0, 0x6c, 0x15, 0xc9, 0xe7, 0x3b, 0x42, 0x9e, 0x1e, 0xc2,
+ 0xbb, 0x67, 0x49, 0x95, 0xec, 0x30, 0xf1, 0x2d, 0x54, 0x88,
+ 0xa6, 0x7a, 0x03, 0xdf, 0x5f, 0x83, 0xfa, 0x26, 0x08, 0xd4,
+ 0xad, 0x71, 0x2b, 0xf7, 0x8e, 0x52, 0x7c, 0xa0, 0xd9, 0x05,
+ 0x85, 0x59, 0x20, 0xfc, 0xd2, 0x0e, 0x77, 0xab, 0x6a, 0xb6,
+ 0xcf, 0x13, 0x3d, 0xe1, 0x98, 0x44, 0xc4, 0x18, 0x61, 0xbd,
+ 0x93, 0x4f, 0x36, 0xea, 0xa9, 0x75, 0x0c, 0xd0, 0xfe, 0x22,
+ 0x5b, 0x87, 0x07, 0xdb, 0xa2, 0x7e, 0x50, 0x8c, 0xf5, 0x29,
+ 0xe8, 0x34, 0x4d, 0x91, 0xbf, 0x63, 0x1a, 0xc6, 0x46, 0x9a,
+ 0xe3, 0x3f, 0x11, 0xcd, 0xb4, 0x68, 0x00, 0xdd, 0xa7, 0x7a,
+ 0x53, 0x8e, 0xf4, 0x29, 0xa6, 0x7b, 0x01, 0xdc, 0xf5, 0x28,
+ 0x52, 0x8f, 0x51, 0x8c, 0xf6, 0x2b, 0x02, 0xdf, 0xa5, 0x78,
+ 0xf7, 0x2a, 0x50, 0x8d, 0xa4, 0x79, 0x03, 0xde, 0xa2, 0x7f,
+ 0x05, 0xd8, 0xf1, 0x2c, 0x56, 0x8b, 0x04, 0xd9, 0xa3, 0x7e,
+ 0x57, 0x8a, 0xf0, 0x2d, 0xf3, 0x2e, 0x54, 0x89, 0xa0, 0x7d,
+ 0x07, 0xda, 0x55, 0x88, 0xf2, 0x2f, 0x06, 0xdb, 0xa1, 0x7c,
+ 0x59, 0x84, 0xfe, 0x23, 0x0a, 0xd7, 0xad, 0x70, 0xff, 0x22,
+ 0x58, 0x85, 0xac, 0x71, 0x0b, 0xd6, 0x08, 0xd5, 0xaf, 0x72,
+ 0x5b, 0x86, 0xfc, 0x21, 0xae, 0x73, 0x09, 0xd4, 0xfd, 0x20,
+ 0x5a, 0x87, 0xfb, 0x26, 0x5c, 0x81, 0xa8, 0x75, 0x0f, 0xd2,
+ 0x5d, 0x80, 0xfa, 0x27, 0x0e, 0xd3, 0xa9, 0x74, 0xaa, 0x77,
+ 0x0d, 0xd0, 0xf9, 0x24, 0x5e, 0x83, 0x0c, 0xd1, 0xab, 0x76,
+ 0x5f, 0x82, 0xf8, 0x25, 0xb2, 0x6f, 0x15, 0xc8, 0xe1, 0x3c,
+ 0x46, 0x9b, 0x14, 0xc9, 0xb3, 0x6e, 0x47, 0x9a, 0xe0, 0x3d,
+ 0xe3, 0x3e, 0x44, 0x99, 0xb0, 0x6d, 0x17, 0xca, 0x45, 0x98,
+ 0xe2, 0x3f, 0x16, 0xcb, 0xb1, 0x6c, 0x10, 0xcd, 0xb7, 0x6a,
+ 0x43, 0x9e, 0xe4, 0x39, 0xb6, 0x6b, 0x11, 0xcc, 0xe5, 0x38,
+ 0x42, 0x9f, 0x41, 0x9c, 0xe6, 0x3b, 0x12, 0xcf, 0xb5, 0x68,
+ 0xe7, 0x3a, 0x40, 0x9d, 0xb4, 0x69, 0x13, 0xce, 0xeb, 0x36,
+ 0x4c, 0x91, 0xb8, 0x65, 0x1f, 0xc2, 0x4d, 0x90, 0xea, 0x37,
+ 0x1e, 0xc3, 0xb9, 0x64, 0xba, 0x67, 0x1d, 0xc0, 0xe9, 0x34,
+ 0x4e, 0x93, 0x1c, 0xc1, 0xbb, 0x66, 0x4f, 0x92, 0xe8, 0x35,
+ 0x49, 0x94, 0xee, 0x33, 0x1a, 0xc7, 0xbd, 0x60, 0xef, 0x32,
+ 0x48, 0x95, 0xbc, 0x61, 0x1b, 0xc6, 0x18, 0xc5, 0xbf, 0x62,
+ 0x4b, 0x96, 0xec, 0x31, 0xbe, 0x63, 0x19, 0xc4, 0xed, 0x30,
+ 0x4a, 0x97, 0x00, 0xde, 0xa1, 0x7f, 0x5f, 0x81, 0xfe, 0x20,
+ 0xbe, 0x60, 0x1f, 0xc1, 0xe1, 0x3f, 0x40, 0x9e, 0x61, 0xbf,
+ 0xc0, 0x1e, 0x3e, 0xe0, 0x9f, 0x41, 0xdf, 0x01, 0x7e, 0xa0,
+ 0x80, 0x5e, 0x21, 0xff, 0xc2, 0x1c, 0x63, 0xbd, 0x9d, 0x43,
+ 0x3c, 0xe2, 0x7c, 0xa2, 0xdd, 0x03, 0x23, 0xfd, 0x82, 0x5c,
+ 0xa3, 0x7d, 0x02, 0xdc, 0xfc, 0x22, 0x5d, 0x83, 0x1d, 0xc3,
+ 0xbc, 0x62, 0x42, 0x9c, 0xe3, 0x3d, 0x99, 0x47, 0x38, 0xe6,
+ 0xc6, 0x18, 0x67, 0xb9, 0x27, 0xf9, 0x86, 0x58, 0x78, 0xa6,
+ 0xd9, 0x07, 0xf8, 0x26, 0x59, 0x87, 0xa7, 0x79, 0x06, 0xd8,
+ 0x46, 0x98, 0xe7, 0x39, 0x19, 0xc7, 0xb8, 0x66, 0x5b, 0x85,
+ 0xfa, 0x24, 0x04, 0xda, 0xa5, 0x7b, 0xe5, 0x3b, 0x44, 0x9a,
+ 0xba, 0x64, 0x1b, 0xc5, 0x3a, 0xe4, 0x9b, 0x45, 0x65, 0xbb,
+ 0xc4, 0x1a, 0x84, 0x5a, 0x25, 0xfb, 0xdb, 0x05, 0x7a, 0xa4,
+ 0x2f, 0xf1, 0x8e, 0x50, 0x70, 0xae, 0xd1, 0x0f, 0x91, 0x4f,
+ 0x30, 0xee, 0xce, 0x10, 0x6f, 0xb1, 0x4e, 0x90, 0xef, 0x31,
+ 0x11, 0xcf, 0xb0, 0x6e, 0xf0, 0x2e, 0x51, 0x8f, 0xaf, 0x71,
+ 0x0e, 0xd0, 0xed, 0x33, 0x4c, 0x92, 0xb2, 0x6c, 0x13, 0xcd,
+ 0x53, 0x8d, 0xf2, 0x2c, 0x0c, 0xd2, 0xad, 0x73, 0x8c, 0x52,
+ 0x2d, 0xf3, 0xd3, 0x0d, 0x72, 0xac, 0x32, 0xec, 0x93, 0x4d,
+ 0x6d, 0xb3, 0xcc, 0x12, 0xb6, 0x68, 0x17, 0xc9, 0xe9, 0x37,
+ 0x48, 0x96, 0x08, 0xd6, 0xa9, 0x77, 0x57, 0x89, 0xf6, 0x28,
+ 0xd7, 0x09, 0x76, 0xa8, 0x88, 0x56, 0x29, 0xf7, 0x69, 0xb7,
+ 0xc8, 0x16, 0x36, 0xe8, 0x97, 0x49, 0x74, 0xaa, 0xd5, 0x0b,
+ 0x2b, 0xf5, 0x8a, 0x54, 0xca, 0x14, 0x6b, 0xb5, 0x95, 0x4b,
+ 0x34, 0xea, 0x15, 0xcb, 0xb4, 0x6a, 0x4a, 0x94, 0xeb, 0x35,
+ 0xab, 0x75, 0x0a, 0xd4, 0xf4, 0x2a, 0x55, 0x8b, 0x00, 0xdf,
+ 0xa3, 0x7c, 0x5b, 0x84, 0xf8, 0x27, 0xb6, 0x69, 0x15, 0xca,
+ 0xed, 0x32, 0x4e, 0x91, 0x71, 0xae, 0xd2, 0x0d, 0x2a, 0xf5,
+ 0x89, 0x56, 0xc7, 0x18, 0x64, 0xbb, 0x9c, 0x43, 0x3f, 0xe0,
+ 0xe2, 0x3d, 0x41, 0x9e, 0xb9, 0x66, 0x1a, 0xc5, 0x54, 0x8b,
+ 0xf7, 0x28, 0x0f, 0xd0, 0xac, 0x73, 0x93, 0x4c, 0x30, 0xef,
+ 0xc8, 0x17, 0x6b, 0xb4, 0x25, 0xfa, 0x86, 0x59, 0x7e, 0xa1,
+ 0xdd, 0x02, 0xd9, 0x06, 0x7a, 0xa5, 0x82, 0x5d, 0x21, 0xfe,
+ 0x6f, 0xb0, 0xcc, 0x13, 0x34, 0xeb, 0x97, 0x48, 0xa8, 0x77,
+ 0x0b, 0xd4, 0xf3, 0x2c, 0x50, 0x8f, 0x1e, 0xc1, 0xbd, 0x62,
+ 0x45, 0x9a, 0xe6, 0x39, 0x3b, 0xe4, 0x98, 0x47, 0x60, 0xbf,
+ 0xc3, 0x1c, 0x8d, 0x52, 0x2e, 0xf1, 0xd6, 0x09, 0x75, 0xaa,
+ 0x4a, 0x95, 0xe9, 0x36, 0x11, 0xce, 0xb2, 0x6d, 0xfc, 0x23,
+ 0x5f, 0x80, 0xa7, 0x78, 0x04, 0xdb, 0xaf, 0x70, 0x0c, 0xd3,
+ 0xf4, 0x2b, 0x57, 0x88, 0x19, 0xc6, 0xba, 0x65, 0x42, 0x9d,
+ 0xe1, 0x3e, 0xde, 0x01, 0x7d, 0xa2, 0x85, 0x5a, 0x26, 0xf9,
+ 0x68, 0xb7, 0xcb, 0x14, 0x33, 0xec, 0x90, 0x4f, 0x4d, 0x92,
+ 0xee, 0x31, 0x16, 0xc9, 0xb5, 0x6a, 0xfb, 0x24, 0x58, 0x87,
+ 0xa0, 0x7f, 0x03, 0xdc, 0x3c, 0xe3, 0x9f, 0x40, 0x67, 0xb8,
+ 0xc4, 0x1b, 0x8a, 0x55, 0x29, 0xf6, 0xd1, 0x0e, 0x72, 0xad,
+ 0x76, 0xa9, 0xd5, 0x0a, 0x2d, 0xf2, 0x8e, 0x51, 0xc0, 0x1f,
+ 0x63, 0xbc, 0x9b, 0x44, 0x38, 0xe7, 0x07, 0xd8, 0xa4, 0x7b,
+ 0x5c, 0x83, 0xff, 0x20, 0xb1, 0x6e, 0x12, 0xcd, 0xea, 0x35,
+ 0x49, 0x96, 0x94, 0x4b, 0x37, 0xe8, 0xcf, 0x10, 0x6c, 0xb3,
+ 0x22, 0xfd, 0x81, 0x5e, 0x79, 0xa6, 0xda, 0x05, 0xe5, 0x3a,
+ 0x46, 0x99, 0xbe, 0x61, 0x1d, 0xc2, 0x53, 0x8c, 0xf0, 0x2f,
+ 0x08, 0xd7, 0xab, 0x74, 0x00, 0xe0, 0xdd, 0x3d, 0xa7, 0x47,
+ 0x7a, 0x9a, 0x53, 0xb3, 0x8e, 0x6e, 0xf4, 0x14, 0x29, 0xc9,
+ 0xa6, 0x46, 0x7b, 0x9b, 0x01, 0xe1, 0xdc, 0x3c, 0xf5, 0x15,
+ 0x28, 0xc8, 0x52, 0xb2, 0x8f, 0x6f, 0x51, 0xb1, 0x8c, 0x6c,
+ 0xf6, 0x16, 0x2b, 0xcb, 0x02, 0xe2, 0xdf, 0x3f, 0xa5, 0x45,
+ 0x78, 0x98, 0xf7, 0x17, 0x2a, 0xca, 0x50, 0xb0, 0x8d, 0x6d,
+ 0xa4, 0x44, 0x79, 0x99, 0x03, 0xe3, 0xde, 0x3e, 0xa2, 0x42,
+ 0x7f, 0x9f, 0x05, 0xe5, 0xd8, 0x38, 0xf1, 0x11, 0x2c, 0xcc,
+ 0x56, 0xb6, 0x8b, 0x6b, 0x04, 0xe4, 0xd9, 0x39, 0xa3, 0x43,
+ 0x7e, 0x9e, 0x57, 0xb7, 0x8a, 0x6a, 0xf0, 0x10, 0x2d, 0xcd,
+ 0xf3, 0x13, 0x2e, 0xce, 0x54, 0xb4, 0x89, 0x69, 0xa0, 0x40,
+ 0x7d, 0x9d, 0x07, 0xe7, 0xda, 0x3a, 0x55, 0xb5, 0x88, 0x68,
+ 0xf2, 0x12, 0x2f, 0xcf, 0x06, 0xe6, 0xdb, 0x3b, 0xa1, 0x41,
+ 0x7c, 0x9c, 0x59, 0xb9, 0x84, 0x64, 0xfe, 0x1e, 0x23, 0xc3,
+ 0x0a, 0xea, 0xd7, 0x37, 0xad, 0x4d, 0x70, 0x90, 0xff, 0x1f,
+ 0x22, 0xc2, 0x58, 0xb8, 0x85, 0x65, 0xac, 0x4c, 0x71, 0x91,
+ 0x0b, 0xeb, 0xd6, 0x36, 0x08, 0xe8, 0xd5, 0x35, 0xaf, 0x4f,
+ 0x72, 0x92, 0x5b, 0xbb, 0x86, 0x66, 0xfc, 0x1c, 0x21, 0xc1,
+ 0xae, 0x4e, 0x73, 0x93, 0x09, 0xe9, 0xd4, 0x34, 0xfd, 0x1d,
+ 0x20, 0xc0, 0x5a, 0xba, 0x87, 0x67, 0xfb, 0x1b, 0x26, 0xc6,
+ 0x5c, 0xbc, 0x81, 0x61, 0xa8, 0x48, 0x75, 0x95, 0x0f, 0xef,
+ 0xd2, 0x32, 0x5d, 0xbd, 0x80, 0x60, 0xfa, 0x1a, 0x27, 0xc7,
+ 0x0e, 0xee, 0xd3, 0x33, 0xa9, 0x49, 0x74, 0x94, 0xaa, 0x4a,
+ 0x77, 0x97, 0x0d, 0xed, 0xd0, 0x30, 0xf9, 0x19, 0x24, 0xc4,
+ 0x5e, 0xbe, 0x83, 0x63, 0x0c, 0xec, 0xd1, 0x31, 0xab, 0x4b,
+ 0x76, 0x96, 0x5f, 0xbf, 0x82, 0x62, 0xf8, 0x18, 0x25, 0xc5,
+ 0x00, 0xe1, 0xdf, 0x3e, 0xa3, 0x42, 0x7c, 0x9d, 0x5b, 0xba,
+ 0x84, 0x65, 0xf8, 0x19, 0x27, 0xc6, 0xb6, 0x57, 0x69, 0x88,
+ 0x15, 0xf4, 0xca, 0x2b, 0xed, 0x0c, 0x32, 0xd3, 0x4e, 0xaf,
+ 0x91, 0x70, 0x71, 0x90, 0xae, 0x4f, 0xd2, 0x33, 0x0d, 0xec,
+ 0x2a, 0xcb, 0xf5, 0x14, 0x89, 0x68, 0x56, 0xb7, 0xc7, 0x26,
+ 0x18, 0xf9, 0x64, 0x85, 0xbb, 0x5a, 0x9c, 0x7d, 0x43, 0xa2,
+ 0x3f, 0xde, 0xe0, 0x01, 0xe2, 0x03, 0x3d, 0xdc, 0x41, 0xa0,
+ 0x9e, 0x7f, 0xb9, 0x58, 0x66, 0x87, 0x1a, 0xfb, 0xc5, 0x24,
+ 0x54, 0xb5, 0x8b, 0x6a, 0xf7, 0x16, 0x28, 0xc9, 0x0f, 0xee,
+ 0xd0, 0x31, 0xac, 0x4d, 0x73, 0x92, 0x93, 0x72, 0x4c, 0xad,
+ 0x30, 0xd1, 0xef, 0x0e, 0xc8, 0x29, 0x17, 0xf6, 0x6b, 0x8a,
+ 0xb4, 0x55, 0x25, 0xc4, 0xfa, 0x1b, 0x86, 0x67, 0x59, 0xb8,
+ 0x7e, 0x9f, 0xa1, 0x40, 0xdd, 0x3c, 0x02, 0xe3, 0xd9, 0x38,
+ 0x06, 0xe7, 0x7a, 0x9b, 0xa5, 0x44, 0x82, 0x63, 0x5d, 0xbc,
+ 0x21, 0xc0, 0xfe, 0x1f, 0x6f, 0x8e, 0xb0, 0x51, 0xcc, 0x2d,
+ 0x13, 0xf2, 0x34, 0xd5, 0xeb, 0x0a, 0x97, 0x76, 0x48, 0xa9,
+ 0xa8, 0x49, 0x77, 0x96, 0x0b, 0xea, 0xd4, 0x35, 0xf3, 0x12,
+ 0x2c, 0xcd, 0x50, 0xb1, 0x8f, 0x6e, 0x1e, 0xff, 0xc1, 0x20,
+ 0xbd, 0x5c, 0x62, 0x83, 0x45, 0xa4, 0x9a, 0x7b, 0xe6, 0x07,
+ 0x39, 0xd8, 0x3b, 0xda, 0xe4, 0x05, 0x98, 0x79, 0x47, 0xa6,
+ 0x60, 0x81, 0xbf, 0x5e, 0xc3, 0x22, 0x1c, 0xfd, 0x8d, 0x6c,
+ 0x52, 0xb3, 0x2e, 0xcf, 0xf1, 0x10, 0xd6, 0x37, 0x09, 0xe8,
+ 0x75, 0x94, 0xaa, 0x4b, 0x4a, 0xab, 0x95, 0x74, 0xe9, 0x08,
+ 0x36, 0xd7, 0x11, 0xf0, 0xce, 0x2f, 0xb2, 0x53, 0x6d, 0x8c,
+ 0xfc, 0x1d, 0x23, 0xc2, 0x5f, 0xbe, 0x80, 0x61, 0xa7, 0x46,
+ 0x78, 0x99, 0x04, 0xe5, 0xdb, 0x3a, 0x00, 0xe2, 0xd9, 0x3b,
+ 0xaf, 0x4d, 0x76, 0x94, 0x43, 0xa1, 0x9a, 0x78, 0xec, 0x0e,
+ 0x35, 0xd7, 0x86, 0x64, 0x5f, 0xbd, 0x29, 0xcb, 0xf0, 0x12,
+ 0xc5, 0x27, 0x1c, 0xfe, 0x6a, 0x88, 0xb3, 0x51, 0x11, 0xf3,
+ 0xc8, 0x2a, 0xbe, 0x5c, 0x67, 0x85, 0x52, 0xb0, 0x8b, 0x69,
+ 0xfd, 0x1f, 0x24, 0xc6, 0x97, 0x75, 0x4e, 0xac, 0x38, 0xda,
+ 0xe1, 0x03, 0xd4, 0x36, 0x0d, 0xef, 0x7b, 0x99, 0xa2, 0x40,
+ 0x22, 0xc0, 0xfb, 0x19, 0x8d, 0x6f, 0x54, 0xb6, 0x61, 0x83,
+ 0xb8, 0x5a, 0xce, 0x2c, 0x17, 0xf5, 0xa4, 0x46, 0x7d, 0x9f,
+ 0x0b, 0xe9, 0xd2, 0x30, 0xe7, 0x05, 0x3e, 0xdc, 0x48, 0xaa,
+ 0x91, 0x73, 0x33, 0xd1, 0xea, 0x08, 0x9c, 0x7e, 0x45, 0xa7,
+ 0x70, 0x92, 0xa9, 0x4b, 0xdf, 0x3d, 0x06, 0xe4, 0xb5, 0x57,
+ 0x6c, 0x8e, 0x1a, 0xf8, 0xc3, 0x21, 0xf6, 0x14, 0x2f, 0xcd,
+ 0x59, 0xbb, 0x80, 0x62, 0x44, 0xa6, 0x9d, 0x7f, 0xeb, 0x09,
+ 0x32, 0xd0, 0x07, 0xe5, 0xde, 0x3c, 0xa8, 0x4a, 0x71, 0x93,
+ 0xc2, 0x20, 0x1b, 0xf9, 0x6d, 0x8f, 0xb4, 0x56, 0x81, 0x63,
+ 0x58, 0xba, 0x2e, 0xcc, 0xf7, 0x15, 0x55, 0xb7, 0x8c, 0x6e,
+ 0xfa, 0x18, 0x23, 0xc1, 0x16, 0xf4, 0xcf, 0x2d, 0xb9, 0x5b,
+ 0x60, 0x82, 0xd3, 0x31, 0x0a, 0xe8, 0x7c, 0x9e, 0xa5, 0x47,
+ 0x90, 0x72, 0x49, 0xab, 0x3f, 0xdd, 0xe6, 0x04, 0x66, 0x84,
+ 0xbf, 0x5d, 0xc9, 0x2b, 0x10, 0xf2, 0x25, 0xc7, 0xfc, 0x1e,
+ 0x8a, 0x68, 0x53, 0xb1, 0xe0, 0x02, 0x39, 0xdb, 0x4f, 0xad,
+ 0x96, 0x74, 0xa3, 0x41, 0x7a, 0x98, 0x0c, 0xee, 0xd5, 0x37,
+ 0x77, 0x95, 0xae, 0x4c, 0xd8, 0x3a, 0x01, 0xe3, 0x34, 0xd6,
+ 0xed, 0x0f, 0x9b, 0x79, 0x42, 0xa0, 0xf1, 0x13, 0x28, 0xca,
+ 0x5e, 0xbc, 0x87, 0x65, 0xb2, 0x50, 0x6b, 0x89, 0x1d, 0xff,
+ 0xc4, 0x26, 0x00, 0xe3, 0xdb, 0x38, 0xab, 0x48, 0x70, 0x93,
+ 0x4b, 0xa8, 0x90, 0x73, 0xe0, 0x03, 0x3b, 0xd8, 0x96, 0x75,
+ 0x4d, 0xae, 0x3d, 0xde, 0xe6, 0x05, 0xdd, 0x3e, 0x06, 0xe5,
+ 0x76, 0x95, 0xad, 0x4e, 0x31, 0xd2, 0xea, 0x09, 0x9a, 0x79,
+ 0x41, 0xa2, 0x7a, 0x99, 0xa1, 0x42, 0xd1, 0x32, 0x0a, 0xe9,
+ 0xa7, 0x44, 0x7c, 0x9f, 0x0c, 0xef, 0xd7, 0x34, 0xec, 0x0f,
+ 0x37, 0xd4, 0x47, 0xa4, 0x9c, 0x7f, 0x62, 0x81, 0xb9, 0x5a,
+ 0xc9, 0x2a, 0x12, 0xf1, 0x29, 0xca, 0xf2, 0x11, 0x82, 0x61,
+ 0x59, 0xba, 0xf4, 0x17, 0x2f, 0xcc, 0x5f, 0xbc, 0x84, 0x67,
+ 0xbf, 0x5c, 0x64, 0x87, 0x14, 0xf7, 0xcf, 0x2c, 0x53, 0xb0,
+ 0x88, 0x6b, 0xf8, 0x1b, 0x23, 0xc0, 0x18, 0xfb, 0xc3, 0x20,
+ 0xb3, 0x50, 0x68, 0x8b, 0xc5, 0x26, 0x1e, 0xfd, 0x6e, 0x8d,
+ 0xb5, 0x56, 0x8e, 0x6d, 0x55, 0xb6, 0x25, 0xc6, 0xfe, 0x1d,
+ 0xc4, 0x27, 0x1f, 0xfc, 0x6f, 0x8c, 0xb4, 0x57, 0x8f, 0x6c,
+ 0x54, 0xb7, 0x24, 0xc7, 0xff, 0x1c, 0x52, 0xb1, 0x89, 0x6a,
+ 0xf9, 0x1a, 0x22, 0xc1, 0x19, 0xfa, 0xc2, 0x21, 0xb2, 0x51,
+ 0x69, 0x8a, 0xf5, 0x16, 0x2e, 0xcd, 0x5e, 0xbd, 0x85, 0x66,
+ 0xbe, 0x5d, 0x65, 0x86, 0x15, 0xf6, 0xce, 0x2d, 0x63, 0x80,
+ 0xb8, 0x5b, 0xc8, 0x2b, 0x13, 0xf0, 0x28, 0xcb, 0xf3, 0x10,
+ 0x83, 0x60, 0x58, 0xbb, 0xa6, 0x45, 0x7d, 0x9e, 0x0d, 0xee,
+ 0xd6, 0x35, 0xed, 0x0e, 0x36, 0xd5, 0x46, 0xa5, 0x9d, 0x7e,
+ 0x30, 0xd3, 0xeb, 0x08, 0x9b, 0x78, 0x40, 0xa3, 0x7b, 0x98,
+ 0xa0, 0x43, 0xd0, 0x33, 0x0b, 0xe8, 0x97, 0x74, 0x4c, 0xaf,
+ 0x3c, 0xdf, 0xe7, 0x04, 0xdc, 0x3f, 0x07, 0xe4, 0x77, 0x94,
+ 0xac, 0x4f, 0x01, 0xe2, 0xda, 0x39, 0xaa, 0x49, 0x71, 0x92,
+ 0x4a, 0xa9, 0x91, 0x72, 0xe1, 0x02, 0x3a, 0xd9, 0x00, 0xe4,
+ 0xd5, 0x31, 0xb7, 0x53, 0x62, 0x86, 0x73, 0x97, 0xa6, 0x42,
+ 0xc4, 0x20, 0x11, 0xf5, 0xe6, 0x02, 0x33, 0xd7, 0x51, 0xb5,
+ 0x84, 0x60, 0x95, 0x71, 0x40, 0xa4, 0x22, 0xc6, 0xf7, 0x13,
+ 0xd1, 0x35, 0x04, 0xe0, 0x66, 0x82, 0xb3, 0x57, 0xa2, 0x46,
+ 0x77, 0x93, 0x15, 0xf1, 0xc0, 0x24, 0x37, 0xd3, 0xe2, 0x06,
+ 0x80, 0x64, 0x55, 0xb1, 0x44, 0xa0, 0x91, 0x75, 0xf3, 0x17,
+ 0x26, 0xc2, 0xbf, 0x5b, 0x6a, 0x8e, 0x08, 0xec, 0xdd, 0x39,
+ 0xcc, 0x28, 0x19, 0xfd, 0x7b, 0x9f, 0xae, 0x4a, 0x59, 0xbd,
+ 0x8c, 0x68, 0xee, 0x0a, 0x3b, 0xdf, 0x2a, 0xce, 0xff, 0x1b,
+ 0x9d, 0x79, 0x48, 0xac, 0x6e, 0x8a, 0xbb, 0x5f, 0xd9, 0x3d,
+ 0x0c, 0xe8, 0x1d, 0xf9, 0xc8, 0x2c, 0xaa, 0x4e, 0x7f, 0x9b,
+ 0x88, 0x6c, 0x5d, 0xb9, 0x3f, 0xdb, 0xea, 0x0e, 0xfb, 0x1f,
+ 0x2e, 0xca, 0x4c, 0xa8, 0x99, 0x7d, 0x63, 0x87, 0xb6, 0x52,
+ 0xd4, 0x30, 0x01, 0xe5, 0x10, 0xf4, 0xc5, 0x21, 0xa7, 0x43,
+ 0x72, 0x96, 0x85, 0x61, 0x50, 0xb4, 0x32, 0xd6, 0xe7, 0x03,
+ 0xf6, 0x12, 0x23, 0xc7, 0x41, 0xa5, 0x94, 0x70, 0xb2, 0x56,
+ 0x67, 0x83, 0x05, 0xe1, 0xd0, 0x34, 0xc1, 0x25, 0x14, 0xf0,
+ 0x76, 0x92, 0xa3, 0x47, 0x54, 0xb0, 0x81, 0x65, 0xe3, 0x07,
+ 0x36, 0xd2, 0x27, 0xc3, 0xf2, 0x16, 0x90, 0x74, 0x45, 0xa1,
+ 0xdc, 0x38, 0x09, 0xed, 0x6b, 0x8f, 0xbe, 0x5a, 0xaf, 0x4b,
+ 0x7a, 0x9e, 0x18, 0xfc, 0xcd, 0x29, 0x3a, 0xde, 0xef, 0x0b,
+ 0x8d, 0x69, 0x58, 0xbc, 0x49, 0xad, 0x9c, 0x78, 0xfe, 0x1a,
+ 0x2b, 0xcf, 0x0d, 0xe9, 0xd8, 0x3c, 0xba, 0x5e, 0x6f, 0x8b,
+ 0x7e, 0x9a, 0xab, 0x4f, 0xc9, 0x2d, 0x1c, 0xf8, 0xeb, 0x0f,
+ 0x3e, 0xda, 0x5c, 0xb8, 0x89, 0x6d, 0x98, 0x7c, 0x4d, 0xa9,
+ 0x2f, 0xcb, 0xfa, 0x1e, 0x00, 0xe5, 0xd7, 0x32, 0xb3, 0x56,
+ 0x64, 0x81, 0x7b, 0x9e, 0xac, 0x49, 0xc8, 0x2d, 0x1f, 0xfa,
+ 0xf6, 0x13, 0x21, 0xc4, 0x45, 0xa0, 0x92, 0x77, 0x8d, 0x68,
+ 0x5a, 0xbf, 0x3e, 0xdb, 0xe9, 0x0c, 0xf1, 0x14, 0x26, 0xc3,
+ 0x42, 0xa7, 0x95, 0x70, 0x8a, 0x6f, 0x5d, 0xb8, 0x39, 0xdc,
+ 0xee, 0x0b, 0x07, 0xe2, 0xd0, 0x35, 0xb4, 0x51, 0x63, 0x86,
+ 0x7c, 0x99, 0xab, 0x4e, 0xcf, 0x2a, 0x18, 0xfd, 0xff, 0x1a,
+ 0x28, 0xcd, 0x4c, 0xa9, 0x9b, 0x7e, 0x84, 0x61, 0x53, 0xb6,
+ 0x37, 0xd2, 0xe0, 0x05, 0x09, 0xec, 0xde, 0x3b, 0xba, 0x5f,
+ 0x6d, 0x88, 0x72, 0x97, 0xa5, 0x40, 0xc1, 0x24, 0x16, 0xf3,
+ 0x0e, 0xeb, 0xd9, 0x3c, 0xbd, 0x58, 0x6a, 0x8f, 0x75, 0x90,
+ 0xa2, 0x47, 0xc6, 0x23, 0x11, 0xf4, 0xf8, 0x1d, 0x2f, 0xca,
+ 0x4b, 0xae, 0x9c, 0x79, 0x83, 0x66, 0x54, 0xb1, 0x30, 0xd5,
+ 0xe7, 0x02, 0xe3, 0x06, 0x34, 0xd1, 0x50, 0xb5, 0x87, 0x62,
+ 0x98, 0x7d, 0x4f, 0xaa, 0x2b, 0xce, 0xfc, 0x19, 0x15, 0xf0,
+ 0xc2, 0x27, 0xa6, 0x43, 0x71, 0x94, 0x6e, 0x8b, 0xb9, 0x5c,
+ 0xdd, 0x38, 0x0a, 0xef, 0x12, 0xf7, 0xc5, 0x20, 0xa1, 0x44,
+ 0x76, 0x93, 0x69, 0x8c, 0xbe, 0x5b, 0xda, 0x3f, 0x0d, 0xe8,
+ 0xe4, 0x01, 0x33, 0xd6, 0x57, 0xb2, 0x80, 0x65, 0x9f, 0x7a,
+ 0x48, 0xad, 0x2c, 0xc9, 0xfb, 0x1e, 0x1c, 0xf9, 0xcb, 0x2e,
+ 0xaf, 0x4a, 0x78, 0x9d, 0x67, 0x82, 0xb0, 0x55, 0xd4, 0x31,
+ 0x03, 0xe6, 0xea, 0x0f, 0x3d, 0xd8, 0x59, 0xbc, 0x8e, 0x6b,
+ 0x91, 0x74, 0x46, 0xa3, 0x22, 0xc7, 0xf5, 0x10, 0xed, 0x08,
+ 0x3a, 0xdf, 0x5e, 0xbb, 0x89, 0x6c, 0x96, 0x73, 0x41, 0xa4,
+ 0x25, 0xc0, 0xf2, 0x17, 0x1b, 0xfe, 0xcc, 0x29, 0xa8, 0x4d,
+ 0x7f, 0x9a, 0x60, 0x85, 0xb7, 0x52, 0xd3, 0x36, 0x04, 0xe1,
+ 0x00, 0xe6, 0xd1, 0x37, 0xbf, 0x59, 0x6e, 0x88, 0x63, 0x85,
+ 0xb2, 0x54, 0xdc, 0x3a, 0x0d, 0xeb, 0xc6, 0x20, 0x17, 0xf1,
+ 0x79, 0x9f, 0xa8, 0x4e, 0xa5, 0x43, 0x74, 0x92, 0x1a, 0xfc,
+ 0xcb, 0x2d, 0x91, 0x77, 0x40, 0xa6, 0x2e, 0xc8, 0xff, 0x19,
+ 0xf2, 0x14, 0x23, 0xc5, 0x4d, 0xab, 0x9c, 0x7a, 0x57, 0xb1,
+ 0x86, 0x60, 0xe8, 0x0e, 0x39, 0xdf, 0x34, 0xd2, 0xe5, 0x03,
+ 0x8b, 0x6d, 0x5a, 0xbc, 0x3f, 0xd9, 0xee, 0x08, 0x80, 0x66,
+ 0x51, 0xb7, 0x5c, 0xba, 0x8d, 0x6b, 0xe3, 0x05, 0x32, 0xd4,
+ 0xf9, 0x1f, 0x28, 0xce, 0x46, 0xa0, 0x97, 0x71, 0x9a, 0x7c,
+ 0x4b, 0xad, 0x25, 0xc3, 0xf4, 0x12, 0xae, 0x48, 0x7f, 0x99,
+ 0x11, 0xf7, 0xc0, 0x26, 0xcd, 0x2b, 0x1c, 0xfa, 0x72, 0x94,
+ 0xa3, 0x45, 0x68, 0x8e, 0xb9, 0x5f, 0xd7, 0x31, 0x06, 0xe0,
+ 0x0b, 0xed, 0xda, 0x3c, 0xb4, 0x52, 0x65, 0x83, 0x7e, 0x98,
+ 0xaf, 0x49, 0xc1, 0x27, 0x10, 0xf6, 0x1d, 0xfb, 0xcc, 0x2a,
+ 0xa2, 0x44, 0x73, 0x95, 0xb8, 0x5e, 0x69, 0x8f, 0x07, 0xe1,
+ 0xd6, 0x30, 0xdb, 0x3d, 0x0a, 0xec, 0x64, 0x82, 0xb5, 0x53,
+ 0xef, 0x09, 0x3e, 0xd8, 0x50, 0xb6, 0x81, 0x67, 0x8c, 0x6a,
+ 0x5d, 0xbb, 0x33, 0xd5, 0xe2, 0x04, 0x29, 0xcf, 0xf8, 0x1e,
+ 0x96, 0x70, 0x47, 0xa1, 0x4a, 0xac, 0x9b, 0x7d, 0xf5, 0x13,
+ 0x24, 0xc2, 0x41, 0xa7, 0x90, 0x76, 0xfe, 0x18, 0x2f, 0xc9,
+ 0x22, 0xc4, 0xf3, 0x15, 0x9d, 0x7b, 0x4c, 0xaa, 0x87, 0x61,
+ 0x56, 0xb0, 0x38, 0xde, 0xe9, 0x0f, 0xe4, 0x02, 0x35, 0xd3,
+ 0x5b, 0xbd, 0x8a, 0x6c, 0xd0, 0x36, 0x01, 0xe7, 0x6f, 0x89,
+ 0xbe, 0x58, 0xb3, 0x55, 0x62, 0x84, 0x0c, 0xea, 0xdd, 0x3b,
+ 0x16, 0xf0, 0xc7, 0x21, 0xa9, 0x4f, 0x78, 0x9e, 0x75, 0x93,
+ 0xa4, 0x42, 0xca, 0x2c, 0x1b, 0xfd, 0x00, 0xe7, 0xd3, 0x34,
+ 0xbb, 0x5c, 0x68, 0x8f, 0x6b, 0x8c, 0xb8, 0x5f, 0xd0, 0x37,
+ 0x03, 0xe4, 0xd6, 0x31, 0x05, 0xe2, 0x6d, 0x8a, 0xbe, 0x59,
+ 0xbd, 0x5a, 0x6e, 0x89, 0x06, 0xe1, 0xd5, 0x32, 0xb1, 0x56,
+ 0x62, 0x85, 0x0a, 0xed, 0xd9, 0x3e, 0xda, 0x3d, 0x09, 0xee,
+ 0x61, 0x86, 0xb2, 0x55, 0x67, 0x80, 0xb4, 0x53, 0xdc, 0x3b,
+ 0x0f, 0xe8, 0x0c, 0xeb, 0xdf, 0x38, 0xb7, 0x50, 0x64, 0x83,
+ 0x7f, 0x98, 0xac, 0x4b, 0xc4, 0x23, 0x17, 0xf0, 0x14, 0xf3,
+ 0xc7, 0x20, 0xaf, 0x48, 0x7c, 0x9b, 0xa9, 0x4e, 0x7a, 0x9d,
+ 0x12, 0xf5, 0xc1, 0x26, 0xc2, 0x25, 0x11, 0xf6, 0x79, 0x9e,
+ 0xaa, 0x4d, 0xce, 0x29, 0x1d, 0xfa, 0x75, 0x92, 0xa6, 0x41,
+ 0xa5, 0x42, 0x76, 0x91, 0x1e, 0xf9, 0xcd, 0x2a, 0x18, 0xff,
+ 0xcb, 0x2c, 0xa3, 0x44, 0x70, 0x97, 0x73, 0x94, 0xa0, 0x47,
+ 0xc8, 0x2f, 0x1b, 0xfc, 0xfe, 0x19, 0x2d, 0xca, 0x45, 0xa2,
+ 0x96, 0x71, 0x95, 0x72, 0x46, 0xa1, 0x2e, 0xc9, 0xfd, 0x1a,
+ 0x28, 0xcf, 0xfb, 0x1c, 0x93, 0x74, 0x40, 0xa7, 0x43, 0xa4,
+ 0x90, 0x77, 0xf8, 0x1f, 0x2b, 0xcc, 0x4f, 0xa8, 0x9c, 0x7b,
+ 0xf4, 0x13, 0x27, 0xc0, 0x24, 0xc3, 0xf7, 0x10, 0x9f, 0x78,
+ 0x4c, 0xab, 0x99, 0x7e, 0x4a, 0xad, 0x22, 0xc5, 0xf1, 0x16,
+ 0xf2, 0x15, 0x21, 0xc6, 0x49, 0xae, 0x9a, 0x7d, 0x81, 0x66,
+ 0x52, 0xb5, 0x3a, 0xdd, 0xe9, 0x0e, 0xea, 0x0d, 0x39, 0xde,
+ 0x51, 0xb6, 0x82, 0x65, 0x57, 0xb0, 0x84, 0x63, 0xec, 0x0b,
+ 0x3f, 0xd8, 0x3c, 0xdb, 0xef, 0x08, 0x87, 0x60, 0x54, 0xb3,
+ 0x30, 0xd7, 0xe3, 0x04, 0x8b, 0x6c, 0x58, 0xbf, 0x5b, 0xbc,
+ 0x88, 0x6f, 0xe0, 0x07, 0x33, 0xd4, 0xe6, 0x01, 0x35, 0xd2,
+ 0x5d, 0xba, 0x8e, 0x69, 0x8d, 0x6a, 0x5e, 0xb9, 0x36, 0xd1,
+ 0xe5, 0x02, 0x00, 0xe8, 0xcd, 0x25, 0x87, 0x6f, 0x4a, 0xa2,
+ 0x13, 0xfb, 0xde, 0x36, 0x94, 0x7c, 0x59, 0xb1, 0x26, 0xce,
+ 0xeb, 0x03, 0xa1, 0x49, 0x6c, 0x84, 0x35, 0xdd, 0xf8, 0x10,
+ 0xb2, 0x5a, 0x7f, 0x97, 0x4c, 0xa4, 0x81, 0x69, 0xcb, 0x23,
+ 0x06, 0xee, 0x5f, 0xb7, 0x92, 0x7a, 0xd8, 0x30, 0x15, 0xfd,
+ 0x6a, 0x82, 0xa7, 0x4f, 0xed, 0x05, 0x20, 0xc8, 0x79, 0x91,
+ 0xb4, 0x5c, 0xfe, 0x16, 0x33, 0xdb, 0x98, 0x70, 0x55, 0xbd,
+ 0x1f, 0xf7, 0xd2, 0x3a, 0x8b, 0x63, 0x46, 0xae, 0x0c, 0xe4,
+ 0xc1, 0x29, 0xbe, 0x56, 0x73, 0x9b, 0x39, 0xd1, 0xf4, 0x1c,
+ 0xad, 0x45, 0x60, 0x88, 0x2a, 0xc2, 0xe7, 0x0f, 0xd4, 0x3c,
+ 0x19, 0xf1, 0x53, 0xbb, 0x9e, 0x76, 0xc7, 0x2f, 0x0a, 0xe2,
+ 0x40, 0xa8, 0x8d, 0x65, 0xf2, 0x1a, 0x3f, 0xd7, 0x75, 0x9d,
+ 0xb8, 0x50, 0xe1, 0x09, 0x2c, 0xc4, 0x66, 0x8e, 0xab, 0x43,
+ 0x2d, 0xc5, 0xe0, 0x08, 0xaa, 0x42, 0x67, 0x8f, 0x3e, 0xd6,
+ 0xf3, 0x1b, 0xb9, 0x51, 0x74, 0x9c, 0x0b, 0xe3, 0xc6, 0x2e,
+ 0x8c, 0x64, 0x41, 0xa9, 0x18, 0xf0, 0xd5, 0x3d, 0x9f, 0x77,
+ 0x52, 0xba, 0x61, 0x89, 0xac, 0x44, 0xe6, 0x0e, 0x2b, 0xc3,
+ 0x72, 0x9a, 0xbf, 0x57, 0xf5, 0x1d, 0x38, 0xd0, 0x47, 0xaf,
+ 0x8a, 0x62, 0xc0, 0x28, 0x0d, 0xe5, 0x54, 0xbc, 0x99, 0x71,
+ 0xd3, 0x3b, 0x1e, 0xf6, 0xb5, 0x5d, 0x78, 0x90, 0x32, 0xda,
+ 0xff, 0x17, 0xa6, 0x4e, 0x6b, 0x83, 0x21, 0xc9, 0xec, 0x04,
+ 0x93, 0x7b, 0x5e, 0xb6, 0x14, 0xfc, 0xd9, 0x31, 0x80, 0x68,
+ 0x4d, 0xa5, 0x07, 0xef, 0xca, 0x22, 0xf9, 0x11, 0x34, 0xdc,
+ 0x7e, 0x96, 0xb3, 0x5b, 0xea, 0x02, 0x27, 0xcf, 0x6d, 0x85,
+ 0xa0, 0x48, 0xdf, 0x37, 0x12, 0xfa, 0x58, 0xb0, 0x95, 0x7d,
+ 0xcc, 0x24, 0x01, 0xe9, 0x4b, 0xa3, 0x86, 0x6e, 0x00, 0xe9,
+ 0xcf, 0x26, 0x83, 0x6a, 0x4c, 0xa5, 0x1b, 0xf2, 0xd4, 0x3d,
+ 0x98, 0x71, 0x57, 0xbe, 0x36, 0xdf, 0xf9, 0x10, 0xb5, 0x5c,
+ 0x7a, 0x93, 0x2d, 0xc4, 0xe2, 0x0b, 0xae, 0x47, 0x61, 0x88,
+ 0x6c, 0x85, 0xa3, 0x4a, 0xef, 0x06, 0x20, 0xc9, 0x77, 0x9e,
+ 0xb8, 0x51, 0xf4, 0x1d, 0x3b, 0xd2, 0x5a, 0xb3, 0x95, 0x7c,
+ 0xd9, 0x30, 0x16, 0xff, 0x41, 0xa8, 0x8e, 0x67, 0xc2, 0x2b,
+ 0x0d, 0xe4, 0xd8, 0x31, 0x17, 0xfe, 0x5b, 0xb2, 0x94, 0x7d,
+ 0xc3, 0x2a, 0x0c, 0xe5, 0x40, 0xa9, 0x8f, 0x66, 0xee, 0x07,
+ 0x21, 0xc8, 0x6d, 0x84, 0xa2, 0x4b, 0xf5, 0x1c, 0x3a, 0xd3,
+ 0x76, 0x9f, 0xb9, 0x50, 0xb4, 0x5d, 0x7b, 0x92, 0x37, 0xde,
+ 0xf8, 0x11, 0xaf, 0x46, 0x60, 0x89, 0x2c, 0xc5, 0xe3, 0x0a,
+ 0x82, 0x6b, 0x4d, 0xa4, 0x01, 0xe8, 0xce, 0x27, 0x99, 0x70,
+ 0x56, 0xbf, 0x1a, 0xf3, 0xd5, 0x3c, 0xad, 0x44, 0x62, 0x8b,
+ 0x2e, 0xc7, 0xe1, 0x08, 0xb6, 0x5f, 0x79, 0x90, 0x35, 0xdc,
+ 0xfa, 0x13, 0x9b, 0x72, 0x54, 0xbd, 0x18, 0xf1, 0xd7, 0x3e,
+ 0x80, 0x69, 0x4f, 0xa6, 0x03, 0xea, 0xcc, 0x25, 0xc1, 0x28,
+ 0x0e, 0xe7, 0x42, 0xab, 0x8d, 0x64, 0xda, 0x33, 0x15, 0xfc,
+ 0x59, 0xb0, 0x96, 0x7f, 0xf7, 0x1e, 0x38, 0xd1, 0x74, 0x9d,
+ 0xbb, 0x52, 0xec, 0x05, 0x23, 0xca, 0x6f, 0x86, 0xa0, 0x49,
+ 0x75, 0x9c, 0xba, 0x53, 0xf6, 0x1f, 0x39, 0xd0, 0x6e, 0x87,
+ 0xa1, 0x48, 0xed, 0x04, 0x22, 0xcb, 0x43, 0xaa, 0x8c, 0x65,
+ 0xc0, 0x29, 0x0f, 0xe6, 0x58, 0xb1, 0x97, 0x7e, 0xdb, 0x32,
+ 0x14, 0xfd, 0x19, 0xf0, 0xd6, 0x3f, 0x9a, 0x73, 0x55, 0xbc,
+ 0x02, 0xeb, 0xcd, 0x24, 0x81, 0x68, 0x4e, 0xa7, 0x2f, 0xc6,
+ 0xe0, 0x09, 0xac, 0x45, 0x63, 0x8a, 0x34, 0xdd, 0xfb, 0x12,
+ 0xb7, 0x5e, 0x78, 0x91, 0x00, 0xea, 0xc9, 0x23, 0x8f, 0x65,
+ 0x46, 0xac, 0x03, 0xe9, 0xca, 0x20, 0x8c, 0x66, 0x45, 0xaf,
+ 0x06, 0xec, 0xcf, 0x25, 0x89, 0x63, 0x40, 0xaa, 0x05, 0xef,
+ 0xcc, 0x26, 0x8a, 0x60, 0x43, 0xa9, 0x0c, 0xe6, 0xc5, 0x2f,
+ 0x83, 0x69, 0x4a, 0xa0, 0x0f, 0xe5, 0xc6, 0x2c, 0x80, 0x6a,
+ 0x49, 0xa3, 0x0a, 0xe0, 0xc3, 0x29, 0x85, 0x6f, 0x4c, 0xa6,
+ 0x09, 0xe3, 0xc0, 0x2a, 0x86, 0x6c, 0x4f, 0xa5, 0x18, 0xf2,
+ 0xd1, 0x3b, 0x97, 0x7d, 0x5e, 0xb4, 0x1b, 0xf1, 0xd2, 0x38,
+ 0x94, 0x7e, 0x5d, 0xb7, 0x1e, 0xf4, 0xd7, 0x3d, 0x91, 0x7b,
+ 0x58, 0xb2, 0x1d, 0xf7, 0xd4, 0x3e, 0x92, 0x78, 0x5b, 0xb1,
+ 0x14, 0xfe, 0xdd, 0x37, 0x9b, 0x71, 0x52, 0xb8, 0x17, 0xfd,
+ 0xde, 0x34, 0x98, 0x72, 0x51, 0xbb, 0x12, 0xf8, 0xdb, 0x31,
+ 0x9d, 0x77, 0x54, 0xbe, 0x11, 0xfb, 0xd8, 0x32, 0x9e, 0x74,
+ 0x57, 0xbd, 0x30, 0xda, 0xf9, 0x13, 0xbf, 0x55, 0x76, 0x9c,
+ 0x33, 0xd9, 0xfa, 0x10, 0xbc, 0x56, 0x75, 0x9f, 0x36, 0xdc,
+ 0xff, 0x15, 0xb9, 0x53, 0x70, 0x9a, 0x35, 0xdf, 0xfc, 0x16,
+ 0xba, 0x50, 0x73, 0x99, 0x3c, 0xd6, 0xf5, 0x1f, 0xb3, 0x59,
+ 0x7a, 0x90, 0x3f, 0xd5, 0xf6, 0x1c, 0xb0, 0x5a, 0x79, 0x93,
+ 0x3a, 0xd0, 0xf3, 0x19, 0xb5, 0x5f, 0x7c, 0x96, 0x39, 0xd3,
+ 0xf0, 0x1a, 0xb6, 0x5c, 0x7f, 0x95, 0x28, 0xc2, 0xe1, 0x0b,
+ 0xa7, 0x4d, 0x6e, 0x84, 0x2b, 0xc1, 0xe2, 0x08, 0xa4, 0x4e,
+ 0x6d, 0x87, 0x2e, 0xc4, 0xe7, 0x0d, 0xa1, 0x4b, 0x68, 0x82,
+ 0x2d, 0xc7, 0xe4, 0x0e, 0xa2, 0x48, 0x6b, 0x81, 0x24, 0xce,
+ 0xed, 0x07, 0xab, 0x41, 0x62, 0x88, 0x27, 0xcd, 0xee, 0x04,
+ 0xa8, 0x42, 0x61, 0x8b, 0x22, 0xc8, 0xeb, 0x01, 0xad, 0x47,
+ 0x64, 0x8e, 0x21, 0xcb, 0xe8, 0x02, 0xae, 0x44, 0x67, 0x8d,
+ 0x00, 0xeb, 0xcb, 0x20, 0x8b, 0x60, 0x40, 0xab, 0x0b, 0xe0,
+ 0xc0, 0x2b, 0x80, 0x6b, 0x4b, 0xa0, 0x16, 0xfd, 0xdd, 0x36,
+ 0x9d, 0x76, 0x56, 0xbd, 0x1d, 0xf6, 0xd6, 0x3d, 0x96, 0x7d,
+ 0x5d, 0xb6, 0x2c, 0xc7, 0xe7, 0x0c, 0xa7, 0x4c, 0x6c, 0x87,
+ 0x27, 0xcc, 0xec, 0x07, 0xac, 0x47, 0x67, 0x8c, 0x3a, 0xd1,
+ 0xf1, 0x1a, 0xb1, 0x5a, 0x7a, 0x91, 0x31, 0xda, 0xfa, 0x11,
+ 0xba, 0x51, 0x71, 0x9a, 0x58, 0xb3, 0x93, 0x78, 0xd3, 0x38,
+ 0x18, 0xf3, 0x53, 0xb8, 0x98, 0x73, 0xd8, 0x33, 0x13, 0xf8,
+ 0x4e, 0xa5, 0x85, 0x6e, 0xc5, 0x2e, 0x0e, 0xe5, 0x45, 0xae,
+ 0x8e, 0x65, 0xce, 0x25, 0x05, 0xee, 0x74, 0x9f, 0xbf, 0x54,
+ 0xff, 0x14, 0x34, 0xdf, 0x7f, 0x94, 0xb4, 0x5f, 0xf4, 0x1f,
+ 0x3f, 0xd4, 0x62, 0x89, 0xa9, 0x42, 0xe9, 0x02, 0x22, 0xc9,
+ 0x69, 0x82, 0xa2, 0x49, 0xe2, 0x09, 0x29, 0xc2, 0xb0, 0x5b,
+ 0x7b, 0x90, 0x3b, 0xd0, 0xf0, 0x1b, 0xbb, 0x50, 0x70, 0x9b,
+ 0x30, 0xdb, 0xfb, 0x10, 0xa6, 0x4d, 0x6d, 0x86, 0x2d, 0xc6,
+ 0xe6, 0x0d, 0xad, 0x46, 0x66, 0x8d, 0x26, 0xcd, 0xed, 0x06,
+ 0x9c, 0x77, 0x57, 0xbc, 0x17, 0xfc, 0xdc, 0x37, 0x97, 0x7c,
+ 0x5c, 0xb7, 0x1c, 0xf7, 0xd7, 0x3c, 0x8a, 0x61, 0x41, 0xaa,
+ 0x01, 0xea, 0xca, 0x21, 0x81, 0x6a, 0x4a, 0xa1, 0x0a, 0xe1,
+ 0xc1, 0x2a, 0xe8, 0x03, 0x23, 0xc8, 0x63, 0x88, 0xa8, 0x43,
+ 0xe3, 0x08, 0x28, 0xc3, 0x68, 0x83, 0xa3, 0x48, 0xfe, 0x15,
+ 0x35, 0xde, 0x75, 0x9e, 0xbe, 0x55, 0xf5, 0x1e, 0x3e, 0xd5,
+ 0x7e, 0x95, 0xb5, 0x5e, 0xc4, 0x2f, 0x0f, 0xe4, 0x4f, 0xa4,
+ 0x84, 0x6f, 0xcf, 0x24, 0x04, 0xef, 0x44, 0xaf, 0x8f, 0x64,
+ 0xd2, 0x39, 0x19, 0xf2, 0x59, 0xb2, 0x92, 0x79, 0xd9, 0x32,
+ 0x12, 0xf9, 0x52, 0xb9, 0x99, 0x72, 0x00, 0xec, 0xc5, 0x29,
+ 0x97, 0x7b, 0x52, 0xbe, 0x33, 0xdf, 0xf6, 0x1a, 0xa4, 0x48,
+ 0x61, 0x8d, 0x66, 0x8a, 0xa3, 0x4f, 0xf1, 0x1d, 0x34, 0xd8,
+ 0x55, 0xb9, 0x90, 0x7c, 0xc2, 0x2e, 0x07, 0xeb, 0xcc, 0x20,
+ 0x09, 0xe5, 0x5b, 0xb7, 0x9e, 0x72, 0xff, 0x13, 0x3a, 0xd6,
+ 0x68, 0x84, 0xad, 0x41, 0xaa, 0x46, 0x6f, 0x83, 0x3d, 0xd1,
+ 0xf8, 0x14, 0x99, 0x75, 0x5c, 0xb0, 0x0e, 0xe2, 0xcb, 0x27,
+ 0x85, 0x69, 0x40, 0xac, 0x12, 0xfe, 0xd7, 0x3b, 0xb6, 0x5a,
+ 0x73, 0x9f, 0x21, 0xcd, 0xe4, 0x08, 0xe3, 0x0f, 0x26, 0xca,
+ 0x74, 0x98, 0xb1, 0x5d, 0xd0, 0x3c, 0x15, 0xf9, 0x47, 0xab,
+ 0x82, 0x6e, 0x49, 0xa5, 0x8c, 0x60, 0xde, 0x32, 0x1b, 0xf7,
+ 0x7a, 0x96, 0xbf, 0x53, 0xed, 0x01, 0x28, 0xc4, 0x2f, 0xc3,
+ 0xea, 0x06, 0xb8, 0x54, 0x7d, 0x91, 0x1c, 0xf0, 0xd9, 0x35,
+ 0x8b, 0x67, 0x4e, 0xa2, 0x17, 0xfb, 0xd2, 0x3e, 0x80, 0x6c,
+ 0x45, 0xa9, 0x24, 0xc8, 0xe1, 0x0d, 0xb3, 0x5f, 0x76, 0x9a,
+ 0x71, 0x9d, 0xb4, 0x58, 0xe6, 0x0a, 0x23, 0xcf, 0x42, 0xae,
+ 0x87, 0x6b, 0xd5, 0x39, 0x10, 0xfc, 0xdb, 0x37, 0x1e, 0xf2,
+ 0x4c, 0xa0, 0x89, 0x65, 0xe8, 0x04, 0x2d, 0xc1, 0x7f, 0x93,
+ 0xba, 0x56, 0xbd, 0x51, 0x78, 0x94, 0x2a, 0xc6, 0xef, 0x03,
+ 0x8e, 0x62, 0x4b, 0xa7, 0x19, 0xf5, 0xdc, 0x30, 0x92, 0x7e,
+ 0x57, 0xbb, 0x05, 0xe9, 0xc0, 0x2c, 0xa1, 0x4d, 0x64, 0x88,
+ 0x36, 0xda, 0xf3, 0x1f, 0xf4, 0x18, 0x31, 0xdd, 0x63, 0x8f,
+ 0xa6, 0x4a, 0xc7, 0x2b, 0x02, 0xee, 0x50, 0xbc, 0x95, 0x79,
+ 0x5e, 0xb2, 0x9b, 0x77, 0xc9, 0x25, 0x0c, 0xe0, 0x6d, 0x81,
+ 0xa8, 0x44, 0xfa, 0x16, 0x3f, 0xd3, 0x38, 0xd4, 0xfd, 0x11,
+ 0xaf, 0x43, 0x6a, 0x86, 0x0b, 0xe7, 0xce, 0x22, 0x9c, 0x70,
+ 0x59, 0xb5, 0x00, 0xed, 0xc7, 0x2a, 0x93, 0x7e, 0x54, 0xb9,
+ 0x3b, 0xd6, 0xfc, 0x11, 0xa8, 0x45, 0x6f, 0x82, 0x76, 0x9b,
+ 0xb1, 0x5c, 0xe5, 0x08, 0x22, 0xcf, 0x4d, 0xa0, 0x8a, 0x67,
+ 0xde, 0x33, 0x19, 0xf4, 0xec, 0x01, 0x2b, 0xc6, 0x7f, 0x92,
+ 0xb8, 0x55, 0xd7, 0x3a, 0x10, 0xfd, 0x44, 0xa9, 0x83, 0x6e,
+ 0x9a, 0x77, 0x5d, 0xb0, 0x09, 0xe4, 0xce, 0x23, 0xa1, 0x4c,
+ 0x66, 0x8b, 0x32, 0xdf, 0xf5, 0x18, 0xc5, 0x28, 0x02, 0xef,
+ 0x56, 0xbb, 0x91, 0x7c, 0xfe, 0x13, 0x39, 0xd4, 0x6d, 0x80,
+ 0xaa, 0x47, 0xb3, 0x5e, 0x74, 0x99, 0x20, 0xcd, 0xe7, 0x0a,
+ 0x88, 0x65, 0x4f, 0xa2, 0x1b, 0xf6, 0xdc, 0x31, 0x29, 0xc4,
+ 0xee, 0x03, 0xba, 0x57, 0x7d, 0x90, 0x12, 0xff, 0xd5, 0x38,
+ 0x81, 0x6c, 0x46, 0xab, 0x5f, 0xb2, 0x98, 0x75, 0xcc, 0x21,
+ 0x0b, 0xe6, 0x64, 0x89, 0xa3, 0x4e, 0xf7, 0x1a, 0x30, 0xdd,
+ 0x97, 0x7a, 0x50, 0xbd, 0x04, 0xe9, 0xc3, 0x2e, 0xac, 0x41,
+ 0x6b, 0x86, 0x3f, 0xd2, 0xf8, 0x15, 0xe1, 0x0c, 0x26, 0xcb,
+ 0x72, 0x9f, 0xb5, 0x58, 0xda, 0x37, 0x1d, 0xf0, 0x49, 0xa4,
+ 0x8e, 0x63, 0x7b, 0x96, 0xbc, 0x51, 0xe8, 0x05, 0x2f, 0xc2,
+ 0x40, 0xad, 0x87, 0x6a, 0xd3, 0x3e, 0x14, 0xf9, 0x0d, 0xe0,
+ 0xca, 0x27, 0x9e, 0x73, 0x59, 0xb4, 0x36, 0xdb, 0xf1, 0x1c,
+ 0xa5, 0x48, 0x62, 0x8f, 0x52, 0xbf, 0x95, 0x78, 0xc1, 0x2c,
+ 0x06, 0xeb, 0x69, 0x84, 0xae, 0x43, 0xfa, 0x17, 0x3d, 0xd0,
+ 0x24, 0xc9, 0xe3, 0x0e, 0xb7, 0x5a, 0x70, 0x9d, 0x1f, 0xf2,
+ 0xd8, 0x35, 0x8c, 0x61, 0x4b, 0xa6, 0xbe, 0x53, 0x79, 0x94,
+ 0x2d, 0xc0, 0xea, 0x07, 0x85, 0x68, 0x42, 0xaf, 0x16, 0xfb,
+ 0xd1, 0x3c, 0xc8, 0x25, 0x0f, 0xe2, 0x5b, 0xb6, 0x9c, 0x71,
+ 0xf3, 0x1e, 0x34, 0xd9, 0x60, 0x8d, 0xa7, 0x4a, 0x00, 0xee,
+ 0xc1, 0x2f, 0x9f, 0x71, 0x5e, 0xb0, 0x23, 0xcd, 0xe2, 0x0c,
+ 0xbc, 0x52, 0x7d, 0x93, 0x46, 0xa8, 0x87, 0x69, 0xd9, 0x37,
+ 0x18, 0xf6, 0x65, 0x8b, 0xa4, 0x4a, 0xfa, 0x14, 0x3b, 0xd5,
+ 0x8c, 0x62, 0x4d, 0xa3, 0x13, 0xfd, 0xd2, 0x3c, 0xaf, 0x41,
+ 0x6e, 0x80, 0x30, 0xde, 0xf1, 0x1f, 0xca, 0x24, 0x0b, 0xe5,
+ 0x55, 0xbb, 0x94, 0x7a, 0xe9, 0x07, 0x28, 0xc6, 0x76, 0x98,
+ 0xb7, 0x59, 0x05, 0xeb, 0xc4, 0x2a, 0x9a, 0x74, 0x5b, 0xb5,
+ 0x26, 0xc8, 0xe7, 0x09, 0xb9, 0x57, 0x78, 0x96, 0x43, 0xad,
+ 0x82, 0x6c, 0xdc, 0x32, 0x1d, 0xf3, 0x60, 0x8e, 0xa1, 0x4f,
+ 0xff, 0x11, 0x3e, 0xd0, 0x89, 0x67, 0x48, 0xa6, 0x16, 0xf8,
+ 0xd7, 0x39, 0xaa, 0x44, 0x6b, 0x85, 0x35, 0xdb, 0xf4, 0x1a,
+ 0xcf, 0x21, 0x0e, 0xe0, 0x50, 0xbe, 0x91, 0x7f, 0xec, 0x02,
+ 0x2d, 0xc3, 0x73, 0x9d, 0xb2, 0x5c, 0x0a, 0xe4, 0xcb, 0x25,
+ 0x95, 0x7b, 0x54, 0xba, 0x29, 0xc7, 0xe8, 0x06, 0xb6, 0x58,
+ 0x77, 0x99, 0x4c, 0xa2, 0x8d, 0x63, 0xd3, 0x3d, 0x12, 0xfc,
+ 0x6f, 0x81, 0xae, 0x40, 0xf0, 0x1e, 0x31, 0xdf, 0x86, 0x68,
+ 0x47, 0xa9, 0x19, 0xf7, 0xd8, 0x36, 0xa5, 0x4b, 0x64, 0x8a,
+ 0x3a, 0xd4, 0xfb, 0x15, 0xc0, 0x2e, 0x01, 0xef, 0x5f, 0xb1,
+ 0x9e, 0x70, 0xe3, 0x0d, 0x22, 0xcc, 0x7c, 0x92, 0xbd, 0x53,
+ 0x0f, 0xe1, 0xce, 0x20, 0x90, 0x7e, 0x51, 0xbf, 0x2c, 0xc2,
+ 0xed, 0x03, 0xb3, 0x5d, 0x72, 0x9c, 0x49, 0xa7, 0x88, 0x66,
+ 0xd6, 0x38, 0x17, 0xf9, 0x6a, 0x84, 0xab, 0x45, 0xf5, 0x1b,
+ 0x34, 0xda, 0x83, 0x6d, 0x42, 0xac, 0x1c, 0xf2, 0xdd, 0x33,
+ 0xa0, 0x4e, 0x61, 0x8f, 0x3f, 0xd1, 0xfe, 0x10, 0xc5, 0x2b,
+ 0x04, 0xea, 0x5a, 0xb4, 0x9b, 0x75, 0xe6, 0x08, 0x27, 0xc9,
+ 0x79, 0x97, 0xb8, 0x56, 0x00, 0xef, 0xc3, 0x2c, 0x9b, 0x74,
+ 0x58, 0xb7, 0x2b, 0xc4, 0xe8, 0x07, 0xb0, 0x5f, 0x73, 0x9c,
+ 0x56, 0xb9, 0x95, 0x7a, 0xcd, 0x22, 0x0e, 0xe1, 0x7d, 0x92,
+ 0xbe, 0x51, 0xe6, 0x09, 0x25, 0xca, 0xac, 0x43, 0x6f, 0x80,
+ 0x37, 0xd8, 0xf4, 0x1b, 0x87, 0x68, 0x44, 0xab, 0x1c, 0xf3,
+ 0xdf, 0x30, 0xfa, 0x15, 0x39, 0xd6, 0x61, 0x8e, 0xa2, 0x4d,
+ 0xd1, 0x3e, 0x12, 0xfd, 0x4a, 0xa5, 0x89, 0x66, 0x45, 0xaa,
+ 0x86, 0x69, 0xde, 0x31, 0x1d, 0xf2, 0x6e, 0x81, 0xad, 0x42,
+ 0xf5, 0x1a, 0x36, 0xd9, 0x13, 0xfc, 0xd0, 0x3f, 0x88, 0x67,
+ 0x4b, 0xa4, 0x38, 0xd7, 0xfb, 0x14, 0xa3, 0x4c, 0x60, 0x8f,
+ 0xe9, 0x06, 0x2a, 0xc5, 0x72, 0x9d, 0xb1, 0x5e, 0xc2, 0x2d,
+ 0x01, 0xee, 0x59, 0xb6, 0x9a, 0x75, 0xbf, 0x50, 0x7c, 0x93,
+ 0x24, 0xcb, 0xe7, 0x08, 0x94, 0x7b, 0x57, 0xb8, 0x0f, 0xe0,
+ 0xcc, 0x23, 0x8a, 0x65, 0x49, 0xa6, 0x11, 0xfe, 0xd2, 0x3d,
+ 0xa1, 0x4e, 0x62, 0x8d, 0x3a, 0xd5, 0xf9, 0x16, 0xdc, 0x33,
+ 0x1f, 0xf0, 0x47, 0xa8, 0x84, 0x6b, 0xf7, 0x18, 0x34, 0xdb,
+ 0x6c, 0x83, 0xaf, 0x40, 0x26, 0xc9, 0xe5, 0x0a, 0xbd, 0x52,
+ 0x7e, 0x91, 0x0d, 0xe2, 0xce, 0x21, 0x96, 0x79, 0x55, 0xba,
+ 0x70, 0x9f, 0xb3, 0x5c, 0xeb, 0x04, 0x28, 0xc7, 0x5b, 0xb4,
+ 0x98, 0x77, 0xc0, 0x2f, 0x03, 0xec, 0xcf, 0x20, 0x0c, 0xe3,
+ 0x54, 0xbb, 0x97, 0x78, 0xe4, 0x0b, 0x27, 0xc8, 0x7f, 0x90,
+ 0xbc, 0x53, 0x99, 0x76, 0x5a, 0xb5, 0x02, 0xed, 0xc1, 0x2e,
+ 0xb2, 0x5d, 0x71, 0x9e, 0x29, 0xc6, 0xea, 0x05, 0x63, 0x8c,
+ 0xa0, 0x4f, 0xf8, 0x17, 0x3b, 0xd4, 0x48, 0xa7, 0x8b, 0x64,
+ 0xd3, 0x3c, 0x10, 0xff, 0x35, 0xda, 0xf6, 0x19, 0xae, 0x41,
+ 0x6d, 0x82, 0x1e, 0xf1, 0xdd, 0x32, 0x85, 0x6a, 0x46, 0xa9,
+ 0x00, 0xf0, 0xfd, 0x0d, 0xe7, 0x17, 0x1a, 0xea, 0xd3, 0x23,
+ 0x2e, 0xde, 0x34, 0xc4, 0xc9, 0x39, 0xbb, 0x4b, 0x46, 0xb6,
+ 0x5c, 0xac, 0xa1, 0x51, 0x68, 0x98, 0x95, 0x65, 0x8f, 0x7f,
+ 0x72, 0x82, 0x6b, 0x9b, 0x96, 0x66, 0x8c, 0x7c, 0x71, 0x81,
+ 0xb8, 0x48, 0x45, 0xb5, 0x5f, 0xaf, 0xa2, 0x52, 0xd0, 0x20,
+ 0x2d, 0xdd, 0x37, 0xc7, 0xca, 0x3a, 0x03, 0xf3, 0xfe, 0x0e,
+ 0xe4, 0x14, 0x19, 0xe9, 0xd6, 0x26, 0x2b, 0xdb, 0x31, 0xc1,
+ 0xcc, 0x3c, 0x05, 0xf5, 0xf8, 0x08, 0xe2, 0x12, 0x1f, 0xef,
+ 0x6d, 0x9d, 0x90, 0x60, 0x8a, 0x7a, 0x77, 0x87, 0xbe, 0x4e,
+ 0x43, 0xb3, 0x59, 0xa9, 0xa4, 0x54, 0xbd, 0x4d, 0x40, 0xb0,
+ 0x5a, 0xaa, 0xa7, 0x57, 0x6e, 0x9e, 0x93, 0x63, 0x89, 0x79,
+ 0x74, 0x84, 0x06, 0xf6, 0xfb, 0x0b, 0xe1, 0x11, 0x1c, 0xec,
+ 0xd5, 0x25, 0x28, 0xd8, 0x32, 0xc2, 0xcf, 0x3f, 0xb1, 0x41,
+ 0x4c, 0xbc, 0x56, 0xa6, 0xab, 0x5b, 0x62, 0x92, 0x9f, 0x6f,
+ 0x85, 0x75, 0x78, 0x88, 0x0a, 0xfa, 0xf7, 0x07, 0xed, 0x1d,
+ 0x10, 0xe0, 0xd9, 0x29, 0x24, 0xd4, 0x3e, 0xce, 0xc3, 0x33,
+ 0xda, 0x2a, 0x27, 0xd7, 0x3d, 0xcd, 0xc0, 0x30, 0x09, 0xf9,
+ 0xf4, 0x04, 0xee, 0x1e, 0x13, 0xe3, 0x61, 0x91, 0x9c, 0x6c,
+ 0x86, 0x76, 0x7b, 0x8b, 0xb2, 0x42, 0x4f, 0xbf, 0x55, 0xa5,
+ 0xa8, 0x58, 0x67, 0x97, 0x9a, 0x6a, 0x80, 0x70, 0x7d, 0x8d,
+ 0xb4, 0x44, 0x49, 0xb9, 0x53, 0xa3, 0xae, 0x5e, 0xdc, 0x2c,
+ 0x21, 0xd1, 0x3b, 0xcb, 0xc6, 0x36, 0x0f, 0xff, 0xf2, 0x02,
+ 0xe8, 0x18, 0x15, 0xe5, 0x0c, 0xfc, 0xf1, 0x01, 0xeb, 0x1b,
+ 0x16, 0xe6, 0xdf, 0x2f, 0x22, 0xd2, 0x38, 0xc8, 0xc5, 0x35,
+ 0xb7, 0x47, 0x4a, 0xba, 0x50, 0xa0, 0xad, 0x5d, 0x64, 0x94,
+ 0x99, 0x69, 0x83, 0x73, 0x7e, 0x8e, 0x00, 0xf1, 0xff, 0x0e,
+ 0xe3, 0x12, 0x1c, 0xed, 0xdb, 0x2a, 0x24, 0xd5, 0x38, 0xc9,
+ 0xc7, 0x36, 0xab, 0x5a, 0x54, 0xa5, 0x48, 0xb9, 0xb7, 0x46,
+ 0x70, 0x81, 0x8f, 0x7e, 0x93, 0x62, 0x6c, 0x9d, 0x4b, 0xba,
+ 0xb4, 0x45, 0xa8, 0x59, 0x57, 0xa6, 0x90, 0x61, 0x6f, 0x9e,
+ 0x73, 0x82, 0x8c, 0x7d, 0xe0, 0x11, 0x1f, 0xee, 0x03, 0xf2,
+ 0xfc, 0x0d, 0x3b, 0xca, 0xc4, 0x35, 0xd8, 0x29, 0x27, 0xd6,
+ 0x96, 0x67, 0x69, 0x98, 0x75, 0x84, 0x8a, 0x7b, 0x4d, 0xbc,
+ 0xb2, 0x43, 0xae, 0x5f, 0x51, 0xa0, 0x3d, 0xcc, 0xc2, 0x33,
+ 0xde, 0x2f, 0x21, 0xd0, 0xe6, 0x17, 0x19, 0xe8, 0x05, 0xf4,
+ 0xfa, 0x0b, 0xdd, 0x2c, 0x22, 0xd3, 0x3e, 0xcf, 0xc1, 0x30,
+ 0x06, 0xf7, 0xf9, 0x08, 0xe5, 0x14, 0x1a, 0xeb, 0x76, 0x87,
+ 0x89, 0x78, 0x95, 0x64, 0x6a, 0x9b, 0xad, 0x5c, 0x52, 0xa3,
+ 0x4e, 0xbf, 0xb1, 0x40, 0x31, 0xc0, 0xce, 0x3f, 0xd2, 0x23,
+ 0x2d, 0xdc, 0xea, 0x1b, 0x15, 0xe4, 0x09, 0xf8, 0xf6, 0x07,
+ 0x9a, 0x6b, 0x65, 0x94, 0x79, 0x88, 0x86, 0x77, 0x41, 0xb0,
+ 0xbe, 0x4f, 0xa2, 0x53, 0x5d, 0xac, 0x7a, 0x8b, 0x85, 0x74,
+ 0x99, 0x68, 0x66, 0x97, 0xa1, 0x50, 0x5e, 0xaf, 0x42, 0xb3,
+ 0xbd, 0x4c, 0xd1, 0x20, 0x2e, 0xdf, 0x32, 0xc3, 0xcd, 0x3c,
+ 0x0a, 0xfb, 0xf5, 0x04, 0xe9, 0x18, 0x16, 0xe7, 0xa7, 0x56,
+ 0x58, 0xa9, 0x44, 0xb5, 0xbb, 0x4a, 0x7c, 0x8d, 0x83, 0x72,
+ 0x9f, 0x6e, 0x60, 0x91, 0x0c, 0xfd, 0xf3, 0x02, 0xef, 0x1e,
+ 0x10, 0xe1, 0xd7, 0x26, 0x28, 0xd9, 0x34, 0xc5, 0xcb, 0x3a,
+ 0xec, 0x1d, 0x13, 0xe2, 0x0f, 0xfe, 0xf0, 0x01, 0x37, 0xc6,
+ 0xc8, 0x39, 0xd4, 0x25, 0x2b, 0xda, 0x47, 0xb6, 0xb8, 0x49,
+ 0xa4, 0x55, 0x5b, 0xaa, 0x9c, 0x6d, 0x63, 0x92, 0x7f, 0x8e,
+ 0x80, 0x71, 0x00, 0xf2, 0xf9, 0x0b, 0xef, 0x1d, 0x16, 0xe4,
+ 0xc3, 0x31, 0x3a, 0xc8, 0x2c, 0xde, 0xd5, 0x27, 0x9b, 0x69,
+ 0x62, 0x90, 0x74, 0x86, 0x8d, 0x7f, 0x58, 0xaa, 0xa1, 0x53,
+ 0xb7, 0x45, 0x4e, 0xbc, 0x2b, 0xd9, 0xd2, 0x20, 0xc4, 0x36,
+ 0x3d, 0xcf, 0xe8, 0x1a, 0x11, 0xe3, 0x07, 0xf5, 0xfe, 0x0c,
+ 0xb0, 0x42, 0x49, 0xbb, 0x5f, 0xad, 0xa6, 0x54, 0x73, 0x81,
+ 0x8a, 0x78, 0x9c, 0x6e, 0x65, 0x97, 0x56, 0xa4, 0xaf, 0x5d,
+ 0xb9, 0x4b, 0x40, 0xb2, 0x95, 0x67, 0x6c, 0x9e, 0x7a, 0x88,
+ 0x83, 0x71, 0xcd, 0x3f, 0x34, 0xc6, 0x22, 0xd0, 0xdb, 0x29,
+ 0x0e, 0xfc, 0xf7, 0x05, 0xe1, 0x13, 0x18, 0xea, 0x7d, 0x8f,
+ 0x84, 0x76, 0x92, 0x60, 0x6b, 0x99, 0xbe, 0x4c, 0x47, 0xb5,
+ 0x51, 0xa3, 0xa8, 0x5a, 0xe6, 0x14, 0x1f, 0xed, 0x09, 0xfb,
+ 0xf0, 0x02, 0x25, 0xd7, 0xdc, 0x2e, 0xca, 0x38, 0x33, 0xc1,
+ 0xac, 0x5e, 0x55, 0xa7, 0x43, 0xb1, 0xba, 0x48, 0x6f, 0x9d,
+ 0x96, 0x64, 0x80, 0x72, 0x79, 0x8b, 0x37, 0xc5, 0xce, 0x3c,
+ 0xd8, 0x2a, 0x21, 0xd3, 0xf4, 0x06, 0x0d, 0xff, 0x1b, 0xe9,
+ 0xe2, 0x10, 0x87, 0x75, 0x7e, 0x8c, 0x68, 0x9a, 0x91, 0x63,
+ 0x44, 0xb6, 0xbd, 0x4f, 0xab, 0x59, 0x52, 0xa0, 0x1c, 0xee,
+ 0xe5, 0x17, 0xf3, 0x01, 0x0a, 0xf8, 0xdf, 0x2d, 0x26, 0xd4,
+ 0x30, 0xc2, 0xc9, 0x3b, 0xfa, 0x08, 0x03, 0xf1, 0x15, 0xe7,
+ 0xec, 0x1e, 0x39, 0xcb, 0xc0, 0x32, 0xd6, 0x24, 0x2f, 0xdd,
+ 0x61, 0x93, 0x98, 0x6a, 0x8e, 0x7c, 0x77, 0x85, 0xa2, 0x50,
+ 0x5b, 0xa9, 0x4d, 0xbf, 0xb4, 0x46, 0xd1, 0x23, 0x28, 0xda,
+ 0x3e, 0xcc, 0xc7, 0x35, 0x12, 0xe0, 0xeb, 0x19, 0xfd, 0x0f,
+ 0x04, 0xf6, 0x4a, 0xb8, 0xb3, 0x41, 0xa5, 0x57, 0x5c, 0xae,
+ 0x89, 0x7b, 0x70, 0x82, 0x66, 0x94, 0x9f, 0x6d, 0x00, 0xf3,
+ 0xfb, 0x08, 0xeb, 0x18, 0x10, 0xe3, 0xcb, 0x38, 0x30, 0xc3,
+ 0x20, 0xd3, 0xdb, 0x28, 0x8b, 0x78, 0x70, 0x83, 0x60, 0x93,
+ 0x9b, 0x68, 0x40, 0xb3, 0xbb, 0x48, 0xab, 0x58, 0x50, 0xa3,
+ 0x0b, 0xf8, 0xf0, 0x03, 0xe0, 0x13, 0x1b, 0xe8, 0xc0, 0x33,
+ 0x3b, 0xc8, 0x2b, 0xd8, 0xd0, 0x23, 0x80, 0x73, 0x7b, 0x88,
+ 0x6b, 0x98, 0x90, 0x63, 0x4b, 0xb8, 0xb0, 0x43, 0xa0, 0x53,
+ 0x5b, 0xa8, 0x16, 0xe5, 0xed, 0x1e, 0xfd, 0x0e, 0x06, 0xf5,
+ 0xdd, 0x2e, 0x26, 0xd5, 0x36, 0xc5, 0xcd, 0x3e, 0x9d, 0x6e,
+ 0x66, 0x95, 0x76, 0x85, 0x8d, 0x7e, 0x56, 0xa5, 0xad, 0x5e,
+ 0xbd, 0x4e, 0x46, 0xb5, 0x1d, 0xee, 0xe6, 0x15, 0xf6, 0x05,
+ 0x0d, 0xfe, 0xd6, 0x25, 0x2d, 0xde, 0x3d, 0xce, 0xc6, 0x35,
+ 0x96, 0x65, 0x6d, 0x9e, 0x7d, 0x8e, 0x86, 0x75, 0x5d, 0xae,
+ 0xa6, 0x55, 0xb6, 0x45, 0x4d, 0xbe, 0x2c, 0xdf, 0xd7, 0x24,
+ 0xc7, 0x34, 0x3c, 0xcf, 0xe7, 0x14, 0x1c, 0xef, 0x0c, 0xff,
+ 0xf7, 0x04, 0xa7, 0x54, 0x5c, 0xaf, 0x4c, 0xbf, 0xb7, 0x44,
+ 0x6c, 0x9f, 0x97, 0x64, 0x87, 0x74, 0x7c, 0x8f, 0x27, 0xd4,
+ 0xdc, 0x2f, 0xcc, 0x3f, 0x37, 0xc4, 0xec, 0x1f, 0x17, 0xe4,
+ 0x07, 0xf4, 0xfc, 0x0f, 0xac, 0x5f, 0x57, 0xa4, 0x47, 0xb4,
+ 0xbc, 0x4f, 0x67, 0x94, 0x9c, 0x6f, 0x8c, 0x7f, 0x77, 0x84,
+ 0x3a, 0xc9, 0xc1, 0x32, 0xd1, 0x22, 0x2a, 0xd9, 0xf1, 0x02,
+ 0x0a, 0xf9, 0x1a, 0xe9, 0xe1, 0x12, 0xb1, 0x42, 0x4a, 0xb9,
+ 0x5a, 0xa9, 0xa1, 0x52, 0x7a, 0x89, 0x81, 0x72, 0x91, 0x62,
+ 0x6a, 0x99, 0x31, 0xc2, 0xca, 0x39, 0xda, 0x29, 0x21, 0xd2,
+ 0xfa, 0x09, 0x01, 0xf2, 0x11, 0xe2, 0xea, 0x19, 0xba, 0x49,
+ 0x41, 0xb2, 0x51, 0xa2, 0xaa, 0x59, 0x71, 0x82, 0x8a, 0x79,
+ 0x9a, 0x69, 0x61, 0x92, 0x00, 0xf4, 0xf5, 0x01, 0xf7, 0x03,
+ 0x02, 0xf6, 0xf3, 0x07, 0x06, 0xf2, 0x04, 0xf0, 0xf1, 0x05,
+ 0xfb, 0x0f, 0x0e, 0xfa, 0x0c, 0xf8, 0xf9, 0x0d, 0x08, 0xfc,
+ 0xfd, 0x09, 0xff, 0x0b, 0x0a, 0xfe, 0xeb, 0x1f, 0x1e, 0xea,
+ 0x1c, 0xe8, 0xe9, 0x1d, 0x18, 0xec, 0xed, 0x19, 0xef, 0x1b,
+ 0x1a, 0xee, 0x10, 0xe4, 0xe5, 0x11, 0xe7, 0x13, 0x12, 0xe6,
+ 0xe3, 0x17, 0x16, 0xe2, 0x14, 0xe0, 0xe1, 0x15, 0xcb, 0x3f,
+ 0x3e, 0xca, 0x3c, 0xc8, 0xc9, 0x3d, 0x38, 0xcc, 0xcd, 0x39,
+ 0xcf, 0x3b, 0x3a, 0xce, 0x30, 0xc4, 0xc5, 0x31, 0xc7, 0x33,
+ 0x32, 0xc6, 0xc3, 0x37, 0x36, 0xc2, 0x34, 0xc0, 0xc1, 0x35,
+ 0x20, 0xd4, 0xd5, 0x21, 0xd7, 0x23, 0x22, 0xd6, 0xd3, 0x27,
+ 0x26, 0xd2, 0x24, 0xd0, 0xd1, 0x25, 0xdb, 0x2f, 0x2e, 0xda,
+ 0x2c, 0xd8, 0xd9, 0x2d, 0x28, 0xdc, 0xdd, 0x29, 0xdf, 0x2b,
+ 0x2a, 0xde, 0x8b, 0x7f, 0x7e, 0x8a, 0x7c, 0x88, 0x89, 0x7d,
+ 0x78, 0x8c, 0x8d, 0x79, 0x8f, 0x7b, 0x7a, 0x8e, 0x70, 0x84,
+ 0x85, 0x71, 0x87, 0x73, 0x72, 0x86, 0x83, 0x77, 0x76, 0x82,
+ 0x74, 0x80, 0x81, 0x75, 0x60, 0x94, 0x95, 0x61, 0x97, 0x63,
+ 0x62, 0x96, 0x93, 0x67, 0x66, 0x92, 0x64, 0x90, 0x91, 0x65,
+ 0x9b, 0x6f, 0x6e, 0x9a, 0x6c, 0x98, 0x99, 0x6d, 0x68, 0x9c,
+ 0x9d, 0x69, 0x9f, 0x6b, 0x6a, 0x9e, 0x40, 0xb4, 0xb5, 0x41,
+ 0xb7, 0x43, 0x42, 0xb6, 0xb3, 0x47, 0x46, 0xb2, 0x44, 0xb0,
+ 0xb1, 0x45, 0xbb, 0x4f, 0x4e, 0xba, 0x4c, 0xb8, 0xb9, 0x4d,
+ 0x48, 0xbc, 0xbd, 0x49, 0xbf, 0x4b, 0x4a, 0xbe, 0xab, 0x5f,
+ 0x5e, 0xaa, 0x5c, 0xa8, 0xa9, 0x5d, 0x58, 0xac, 0xad, 0x59,
+ 0xaf, 0x5b, 0x5a, 0xae, 0x50, 0xa4, 0xa5, 0x51, 0xa7, 0x53,
+ 0x52, 0xa6, 0xa3, 0x57, 0x56, 0xa2, 0x54, 0xa0, 0xa1, 0x55,
+ 0x00, 0xf5, 0xf7, 0x02, 0xf3, 0x06, 0x04, 0xf1, 0xfb, 0x0e,
+ 0x0c, 0xf9, 0x08, 0xfd, 0xff, 0x0a, 0xeb, 0x1e, 0x1c, 0xe9,
+ 0x18, 0xed, 0xef, 0x1a, 0x10, 0xe5, 0xe7, 0x12, 0xe3, 0x16,
+ 0x14, 0xe1, 0xcb, 0x3e, 0x3c, 0xc9, 0x38, 0xcd, 0xcf, 0x3a,
+ 0x30, 0xc5, 0xc7, 0x32, 0xc3, 0x36, 0x34, 0xc1, 0x20, 0xd5,
+ 0xd7, 0x22, 0xd3, 0x26, 0x24, 0xd1, 0xdb, 0x2e, 0x2c, 0xd9,
+ 0x28, 0xdd, 0xdf, 0x2a, 0x8b, 0x7e, 0x7c, 0x89, 0x78, 0x8d,
+ 0x8f, 0x7a, 0x70, 0x85, 0x87, 0x72, 0x83, 0x76, 0x74, 0x81,
+ 0x60, 0x95, 0x97, 0x62, 0x93, 0x66, 0x64, 0x91, 0x9b, 0x6e,
+ 0x6c, 0x99, 0x68, 0x9d, 0x9f, 0x6a, 0x40, 0xb5, 0xb7, 0x42,
+ 0xb3, 0x46, 0x44, 0xb1, 0xbb, 0x4e, 0x4c, 0xb9, 0x48, 0xbd,
+ 0xbf, 0x4a, 0xab, 0x5e, 0x5c, 0xa9, 0x58, 0xad, 0xaf, 0x5a,
+ 0x50, 0xa5, 0xa7, 0x52, 0xa3, 0x56, 0x54, 0xa1, 0x0b, 0xfe,
+ 0xfc, 0x09, 0xf8, 0x0d, 0x0f, 0xfa, 0xf0, 0x05, 0x07, 0xf2,
+ 0x03, 0xf6, 0xf4, 0x01, 0xe0, 0x15, 0x17, 0xe2, 0x13, 0xe6,
+ 0xe4, 0x11, 0x1b, 0xee, 0xec, 0x19, 0xe8, 0x1d, 0x1f, 0xea,
+ 0xc0, 0x35, 0x37, 0xc2, 0x33, 0xc6, 0xc4, 0x31, 0x3b, 0xce,
+ 0xcc, 0x39, 0xc8, 0x3d, 0x3f, 0xca, 0x2b, 0xde, 0xdc, 0x29,
+ 0xd8, 0x2d, 0x2f, 0xda, 0xd0, 0x25, 0x27, 0xd2, 0x23, 0xd6,
+ 0xd4, 0x21, 0x80, 0x75, 0x77, 0x82, 0x73, 0x86, 0x84, 0x71,
+ 0x7b, 0x8e, 0x8c, 0x79, 0x88, 0x7d, 0x7f, 0x8a, 0x6b, 0x9e,
+ 0x9c, 0x69, 0x98, 0x6d, 0x6f, 0x9a, 0x90, 0x65, 0x67, 0x92,
+ 0x63, 0x96, 0x94, 0x61, 0x4b, 0xbe, 0xbc, 0x49, 0xb8, 0x4d,
+ 0x4f, 0xba, 0xb0, 0x45, 0x47, 0xb2, 0x43, 0xb6, 0xb4, 0x41,
+ 0xa0, 0x55, 0x57, 0xa2, 0x53, 0xa6, 0xa4, 0x51, 0x5b, 0xae,
+ 0xac, 0x59, 0xa8, 0x5d, 0x5f, 0xaa, 0x00, 0xf6, 0xf1, 0x07,
+ 0xff, 0x09, 0x0e, 0xf8, 0xe3, 0x15, 0x12, 0xe4, 0x1c, 0xea,
+ 0xed, 0x1b, 0xdb, 0x2d, 0x2a, 0xdc, 0x24, 0xd2, 0xd5, 0x23,
+ 0x38, 0xce, 0xc9, 0x3f, 0xc7, 0x31, 0x36, 0xc0, 0xab, 0x5d,
+ 0x5a, 0xac, 0x54, 0xa2, 0xa5, 0x53, 0x48, 0xbe, 0xb9, 0x4f,
+ 0xb7, 0x41, 0x46, 0xb0, 0x70, 0x86, 0x81, 0x77, 0x8f, 0x79,
+ 0x7e, 0x88, 0x93, 0x65, 0x62, 0x94, 0x6c, 0x9a, 0x9d, 0x6b,
+ 0x4b, 0xbd, 0xba, 0x4c, 0xb4, 0x42, 0x45, 0xb3, 0xa8, 0x5e,
+ 0x59, 0xaf, 0x57, 0xa1, 0xa6, 0x50, 0x90, 0x66, 0x61, 0x97,
+ 0x6f, 0x99, 0x9e, 0x68, 0x73, 0x85, 0x82, 0x74, 0x8c, 0x7a,
+ 0x7d, 0x8b, 0xe0, 0x16, 0x11, 0xe7, 0x1f, 0xe9, 0xee, 0x18,
+ 0x03, 0xf5, 0xf2, 0x04, 0xfc, 0x0a, 0x0d, 0xfb, 0x3b, 0xcd,
+ 0xca, 0x3c, 0xc4, 0x32, 0x35, 0xc3, 0xd8, 0x2e, 0x29, 0xdf,
+ 0x27, 0xd1, 0xd6, 0x20, 0x96, 0x60, 0x67, 0x91, 0x69, 0x9f,
+ 0x98, 0x6e, 0x75, 0x83, 0x84, 0x72, 0x8a, 0x7c, 0x7b, 0x8d,
+ 0x4d, 0xbb, 0xbc, 0x4a, 0xb2, 0x44, 0x43, 0xb5, 0xae, 0x58,
+ 0x5f, 0xa9, 0x51, 0xa7, 0xa0, 0x56, 0x3d, 0xcb, 0xcc, 0x3a,
+ 0xc2, 0x34, 0x33, 0xc5, 0xde, 0x28, 0x2f, 0xd9, 0x21, 0xd7,
+ 0xd0, 0x26, 0xe6, 0x10, 0x17, 0xe1, 0x19, 0xef, 0xe8, 0x1e,
+ 0x05, 0xf3, 0xf4, 0x02, 0xfa, 0x0c, 0x0b, 0xfd, 0xdd, 0x2b,
+ 0x2c, 0xda, 0x22, 0xd4, 0xd3, 0x25, 0x3e, 0xc8, 0xcf, 0x39,
+ 0xc1, 0x37, 0x30, 0xc6, 0x06, 0xf0, 0xf7, 0x01, 0xf9, 0x0f,
+ 0x08, 0xfe, 0xe5, 0x13, 0x14, 0xe2, 0x1a, 0xec, 0xeb, 0x1d,
+ 0x76, 0x80, 0x87, 0x71, 0x89, 0x7f, 0x78, 0x8e, 0x95, 0x63,
+ 0x64, 0x92, 0x6a, 0x9c, 0x9b, 0x6d, 0xad, 0x5b, 0x5c, 0xaa,
+ 0x52, 0xa4, 0xa3, 0x55, 0x4e, 0xb8, 0xbf, 0x49, 0xb1, 0x47,
+ 0x40, 0xb6, 0x00, 0xf7, 0xf3, 0x04, 0xfb, 0x0c, 0x08, 0xff,
+ 0xeb, 0x1c, 0x18, 0xef, 0x10, 0xe7, 0xe3, 0x14, 0xcb, 0x3c,
+ 0x38, 0xcf, 0x30, 0xc7, 0xc3, 0x34, 0x20, 0xd7, 0xd3, 0x24,
+ 0xdb, 0x2c, 0x28, 0xdf, 0x8b, 0x7c, 0x78, 0x8f, 0x70, 0x87,
+ 0x83, 0x74, 0x60, 0x97, 0x93, 0x64, 0x9b, 0x6c, 0x68, 0x9f,
+ 0x40, 0xb7, 0xb3, 0x44, 0xbb, 0x4c, 0x48, 0xbf, 0xab, 0x5c,
+ 0x58, 0xaf, 0x50, 0xa7, 0xa3, 0x54, 0x0b, 0xfc, 0xf8, 0x0f,
+ 0xf0, 0x07, 0x03, 0xf4, 0xe0, 0x17, 0x13, 0xe4, 0x1b, 0xec,
+ 0xe8, 0x1f, 0xc0, 0x37, 0x33, 0xc4, 0x3b, 0xcc, 0xc8, 0x3f,
+ 0x2b, 0xdc, 0xd8, 0x2f, 0xd0, 0x27, 0x23, 0xd4, 0x80, 0x77,
+ 0x73, 0x84, 0x7b, 0x8c, 0x88, 0x7f, 0x6b, 0x9c, 0x98, 0x6f,
+ 0x90, 0x67, 0x63, 0x94, 0x4b, 0xbc, 0xb8, 0x4f, 0xb0, 0x47,
+ 0x43, 0xb4, 0xa0, 0x57, 0x53, 0xa4, 0x5b, 0xac, 0xa8, 0x5f,
+ 0x16, 0xe1, 0xe5, 0x12, 0xed, 0x1a, 0x1e, 0xe9, 0xfd, 0x0a,
+ 0x0e, 0xf9, 0x06, 0xf1, 0xf5, 0x02, 0xdd, 0x2a, 0x2e, 0xd9,
+ 0x26, 0xd1, 0xd5, 0x22, 0x36, 0xc1, 0xc5, 0x32, 0xcd, 0x3a,
+ 0x3e, 0xc9, 0x9d, 0x6a, 0x6e, 0x99, 0x66, 0x91, 0x95, 0x62,
+ 0x76, 0x81, 0x85, 0x72, 0x8d, 0x7a, 0x7e, 0x89, 0x56, 0xa1,
+ 0xa5, 0x52, 0xad, 0x5a, 0x5e, 0xa9, 0xbd, 0x4a, 0x4e, 0xb9,
+ 0x46, 0xb1, 0xb5, 0x42, 0x1d, 0xea, 0xee, 0x19, 0xe6, 0x11,
+ 0x15, 0xe2, 0xf6, 0x01, 0x05, 0xf2, 0x0d, 0xfa, 0xfe, 0x09,
+ 0xd6, 0x21, 0x25, 0xd2, 0x2d, 0xda, 0xde, 0x29, 0x3d, 0xca,
+ 0xce, 0x39, 0xc6, 0x31, 0x35, 0xc2, 0x96, 0x61, 0x65, 0x92,
+ 0x6d, 0x9a, 0x9e, 0x69, 0x7d, 0x8a, 0x8e, 0x79, 0x86, 0x71,
+ 0x75, 0x82, 0x5d, 0xaa, 0xae, 0x59, 0xa6, 0x51, 0x55, 0xa2,
+ 0xb6, 0x41, 0x45, 0xb2, 0x4d, 0xba, 0xbe, 0x49, 0x00, 0xf8,
+ 0xed, 0x15, 0xc7, 0x3f, 0x2a, 0xd2, 0x93, 0x6b, 0x7e, 0x86,
+ 0x54, 0xac, 0xb9, 0x41, 0x3b, 0xc3, 0xd6, 0x2e, 0xfc, 0x04,
+ 0x11, 0xe9, 0xa8, 0x50, 0x45, 0xbd, 0x6f, 0x97, 0x82, 0x7a,
+ 0x76, 0x8e, 0x9b, 0x63, 0xb1, 0x49, 0x5c, 0xa4, 0xe5, 0x1d,
+ 0x08, 0xf0, 0x22, 0xda, 0xcf, 0x37, 0x4d, 0xb5, 0xa0, 0x58,
+ 0x8a, 0x72, 0x67, 0x9f, 0xde, 0x26, 0x33, 0xcb, 0x19, 0xe1,
+ 0xf4, 0x0c, 0xec, 0x14, 0x01, 0xf9, 0x2b, 0xd3, 0xc6, 0x3e,
+ 0x7f, 0x87, 0x92, 0x6a, 0xb8, 0x40, 0x55, 0xad, 0xd7, 0x2f,
+ 0x3a, 0xc2, 0x10, 0xe8, 0xfd, 0x05, 0x44, 0xbc, 0xa9, 0x51,
+ 0x83, 0x7b, 0x6e, 0x96, 0x9a, 0x62, 0x77, 0x8f, 0x5d, 0xa5,
+ 0xb0, 0x48, 0x09, 0xf1, 0xe4, 0x1c, 0xce, 0x36, 0x23, 0xdb,
+ 0xa1, 0x59, 0x4c, 0xb4, 0x66, 0x9e, 0x8b, 0x73, 0x32, 0xca,
+ 0xdf, 0x27, 0xf5, 0x0d, 0x18, 0xe0, 0xc5, 0x3d, 0x28, 0xd0,
+ 0x02, 0xfa, 0xef, 0x17, 0x56, 0xae, 0xbb, 0x43, 0x91, 0x69,
+ 0x7c, 0x84, 0xfe, 0x06, 0x13, 0xeb, 0x39, 0xc1, 0xd4, 0x2c,
+ 0x6d, 0x95, 0x80, 0x78, 0xaa, 0x52, 0x47, 0xbf, 0xb3, 0x4b,
+ 0x5e, 0xa6, 0x74, 0x8c, 0x99, 0x61, 0x20, 0xd8, 0xcd, 0x35,
+ 0xe7, 0x1f, 0x0a, 0xf2, 0x88, 0x70, 0x65, 0x9d, 0x4f, 0xb7,
+ 0xa2, 0x5a, 0x1b, 0xe3, 0xf6, 0x0e, 0xdc, 0x24, 0x31, 0xc9,
+ 0x29, 0xd1, 0xc4, 0x3c, 0xee, 0x16, 0x03, 0xfb, 0xba, 0x42,
+ 0x57, 0xaf, 0x7d, 0x85, 0x90, 0x68, 0x12, 0xea, 0xff, 0x07,
+ 0xd5, 0x2d, 0x38, 0xc0, 0x81, 0x79, 0x6c, 0x94, 0x46, 0xbe,
+ 0xab, 0x53, 0x5f, 0xa7, 0xb2, 0x4a, 0x98, 0x60, 0x75, 0x8d,
+ 0xcc, 0x34, 0x21, 0xd9, 0x0b, 0xf3, 0xe6, 0x1e, 0x64, 0x9c,
+ 0x89, 0x71, 0xa3, 0x5b, 0x4e, 0xb6, 0xf7, 0x0f, 0x1a, 0xe2,
+ 0x30, 0xc8, 0xdd, 0x25, 0x00, 0xf9, 0xef, 0x16, 0xc3, 0x3a,
+ 0x2c, 0xd5, 0x9b, 0x62, 0x74, 0x8d, 0x58, 0xa1, 0xb7, 0x4e,
+ 0x2b, 0xd2, 0xc4, 0x3d, 0xe8, 0x11, 0x07, 0xfe, 0xb0, 0x49,
+ 0x5f, 0xa6, 0x73, 0x8a, 0x9c, 0x65, 0x56, 0xaf, 0xb9, 0x40,
+ 0x95, 0x6c, 0x7a, 0x83, 0xcd, 0x34, 0x22, 0xdb, 0x0e, 0xf7,
+ 0xe1, 0x18, 0x7d, 0x84, 0x92, 0x6b, 0xbe, 0x47, 0x51, 0xa8,
+ 0xe6, 0x1f, 0x09, 0xf0, 0x25, 0xdc, 0xca, 0x33, 0xac, 0x55,
+ 0x43, 0xba, 0x6f, 0x96, 0x80, 0x79, 0x37, 0xce, 0xd8, 0x21,
+ 0xf4, 0x0d, 0x1b, 0xe2, 0x87, 0x7e, 0x68, 0x91, 0x44, 0xbd,
+ 0xab, 0x52, 0x1c, 0xe5, 0xf3, 0x0a, 0xdf, 0x26, 0x30, 0xc9,
+ 0xfa, 0x03, 0x15, 0xec, 0x39, 0xc0, 0xd6, 0x2f, 0x61, 0x98,
+ 0x8e, 0x77, 0xa2, 0x5b, 0x4d, 0xb4, 0xd1, 0x28, 0x3e, 0xc7,
+ 0x12, 0xeb, 0xfd, 0x04, 0x4a, 0xb3, 0xa5, 0x5c, 0x89, 0x70,
+ 0x66, 0x9f, 0x45, 0xbc, 0xaa, 0x53, 0x86, 0x7f, 0x69, 0x90,
+ 0xde, 0x27, 0x31, 0xc8, 0x1d, 0xe4, 0xf2, 0x0b, 0x6e, 0x97,
+ 0x81, 0x78, 0xad, 0x54, 0x42, 0xbb, 0xf5, 0x0c, 0x1a, 0xe3,
+ 0x36, 0xcf, 0xd9, 0x20, 0x13, 0xea, 0xfc, 0x05, 0xd0, 0x29,
+ 0x3f, 0xc6, 0x88, 0x71, 0x67, 0x9e, 0x4b, 0xb2, 0xa4, 0x5d,
+ 0x38, 0xc1, 0xd7, 0x2e, 0xfb, 0x02, 0x14, 0xed, 0xa3, 0x5a,
+ 0x4c, 0xb5, 0x60, 0x99, 0x8f, 0x76, 0xe9, 0x10, 0x06, 0xff,
+ 0x2a, 0xd3, 0xc5, 0x3c, 0x72, 0x8b, 0x9d, 0x64, 0xb1, 0x48,
+ 0x5e, 0xa7, 0xc2, 0x3b, 0x2d, 0xd4, 0x01, 0xf8, 0xee, 0x17,
+ 0x59, 0xa0, 0xb6, 0x4f, 0x9a, 0x63, 0x75, 0x8c, 0xbf, 0x46,
+ 0x50, 0xa9, 0x7c, 0x85, 0x93, 0x6a, 0x24, 0xdd, 0xcb, 0x32,
+ 0xe7, 0x1e, 0x08, 0xf1, 0x94, 0x6d, 0x7b, 0x82, 0x57, 0xae,
+ 0xb8, 0x41, 0x0f, 0xf6, 0xe0, 0x19, 0xcc, 0x35, 0x23, 0xda,
+ 0x00, 0xfa, 0xe9, 0x13, 0xcf, 0x35, 0x26, 0xdc, 0x83, 0x79,
+ 0x6a, 0x90, 0x4c, 0xb6, 0xa5, 0x5f, 0x1b, 0xe1, 0xf2, 0x08,
+ 0xd4, 0x2e, 0x3d, 0xc7, 0x98, 0x62, 0x71, 0x8b, 0x57, 0xad,
+ 0xbe, 0x44, 0x36, 0xcc, 0xdf, 0x25, 0xf9, 0x03, 0x10, 0xea,
+ 0xb5, 0x4f, 0x5c, 0xa6, 0x7a, 0x80, 0x93, 0x69, 0x2d, 0xd7,
+ 0xc4, 0x3e, 0xe2, 0x18, 0x0b, 0xf1, 0xae, 0x54, 0x47, 0xbd,
+ 0x61, 0x9b, 0x88, 0x72, 0x6c, 0x96, 0x85, 0x7f, 0xa3, 0x59,
+ 0x4a, 0xb0, 0xef, 0x15, 0x06, 0xfc, 0x20, 0xda, 0xc9, 0x33,
+ 0x77, 0x8d, 0x9e, 0x64, 0xb8, 0x42, 0x51, 0xab, 0xf4, 0x0e,
+ 0x1d, 0xe7, 0x3b, 0xc1, 0xd2, 0x28, 0x5a, 0xa0, 0xb3, 0x49,
+ 0x95, 0x6f, 0x7c, 0x86, 0xd9, 0x23, 0x30, 0xca, 0x16, 0xec,
+ 0xff, 0x05, 0x41, 0xbb, 0xa8, 0x52, 0x8e, 0x74, 0x67, 0x9d,
+ 0xc2, 0x38, 0x2b, 0xd1, 0x0d, 0xf7, 0xe4, 0x1e, 0xd8, 0x22,
+ 0x31, 0xcb, 0x17, 0xed, 0xfe, 0x04, 0x5b, 0xa1, 0xb2, 0x48,
+ 0x94, 0x6e, 0x7d, 0x87, 0xc3, 0x39, 0x2a, 0xd0, 0x0c, 0xf6,
+ 0xe5, 0x1f, 0x40, 0xba, 0xa9, 0x53, 0x8f, 0x75, 0x66, 0x9c,
+ 0xee, 0x14, 0x07, 0xfd, 0x21, 0xdb, 0xc8, 0x32, 0x6d, 0x97,
+ 0x84, 0x7e, 0xa2, 0x58, 0x4b, 0xb1, 0xf5, 0x0f, 0x1c, 0xe6,
+ 0x3a, 0xc0, 0xd3, 0x29, 0x76, 0x8c, 0x9f, 0x65, 0xb9, 0x43,
+ 0x50, 0xaa, 0xb4, 0x4e, 0x5d, 0xa7, 0x7b, 0x81, 0x92, 0x68,
+ 0x37, 0xcd, 0xde, 0x24, 0xf8, 0x02, 0x11, 0xeb, 0xaf, 0x55,
+ 0x46, 0xbc, 0x60, 0x9a, 0x89, 0x73, 0x2c, 0xd6, 0xc5, 0x3f,
+ 0xe3, 0x19, 0x0a, 0xf0, 0x82, 0x78, 0x6b, 0x91, 0x4d, 0xb7,
+ 0xa4, 0x5e, 0x01, 0xfb, 0xe8, 0x12, 0xce, 0x34, 0x27, 0xdd,
+ 0x99, 0x63, 0x70, 0x8a, 0x56, 0xac, 0xbf, 0x45, 0x1a, 0xe0,
+ 0xf3, 0x09, 0xd5, 0x2f, 0x3c, 0xc6, 0x00, 0xfb, 0xeb, 0x10,
+ 0xcb, 0x30, 0x20, 0xdb, 0x8b, 0x70, 0x60, 0x9b, 0x40, 0xbb,
+ 0xab, 0x50, 0x0b, 0xf0, 0xe0, 0x1b, 0xc0, 0x3b, 0x2b, 0xd0,
+ 0x80, 0x7b, 0x6b, 0x90, 0x4b, 0xb0, 0xa0, 0x5b, 0x16, 0xed,
+ 0xfd, 0x06, 0xdd, 0x26, 0x36, 0xcd, 0x9d, 0x66, 0x76, 0x8d,
+ 0x56, 0xad, 0xbd, 0x46, 0x1d, 0xe6, 0xf6, 0x0d, 0xd6, 0x2d,
+ 0x3d, 0xc6, 0x96, 0x6d, 0x7d, 0x86, 0x5d, 0xa6, 0xb6, 0x4d,
+ 0x2c, 0xd7, 0xc7, 0x3c, 0xe7, 0x1c, 0x0c, 0xf7, 0xa7, 0x5c,
+ 0x4c, 0xb7, 0x6c, 0x97, 0x87, 0x7c, 0x27, 0xdc, 0xcc, 0x37,
+ 0xec, 0x17, 0x07, 0xfc, 0xac, 0x57, 0x47, 0xbc, 0x67, 0x9c,
+ 0x8c, 0x77, 0x3a, 0xc1, 0xd1, 0x2a, 0xf1, 0x0a, 0x1a, 0xe1,
+ 0xb1, 0x4a, 0x5a, 0xa1, 0x7a, 0x81, 0x91, 0x6a, 0x31, 0xca,
+ 0xda, 0x21, 0xfa, 0x01, 0x11, 0xea, 0xba, 0x41, 0x51, 0xaa,
+ 0x71, 0x8a, 0x9a, 0x61, 0x58, 0xa3, 0xb3, 0x48, 0x93, 0x68,
+ 0x78, 0x83, 0xd3, 0x28, 0x38, 0xc3, 0x18, 0xe3, 0xf3, 0x08,
+ 0x53, 0xa8, 0xb8, 0x43, 0x98, 0x63, 0x73, 0x88, 0xd8, 0x23,
+ 0x33, 0xc8, 0x13, 0xe8, 0xf8, 0x03, 0x4e, 0xb5, 0xa5, 0x5e,
+ 0x85, 0x7e, 0x6e, 0x95, 0xc5, 0x3e, 0x2e, 0xd5, 0x0e, 0xf5,
+ 0xe5, 0x1e, 0x45, 0xbe, 0xae, 0x55, 0x8e, 0x75, 0x65, 0x9e,
+ 0xce, 0x35, 0x25, 0xde, 0x05, 0xfe, 0xee, 0x15, 0x74, 0x8f,
+ 0x9f, 0x64, 0xbf, 0x44, 0x54, 0xaf, 0xff, 0x04, 0x14, 0xef,
+ 0x34, 0xcf, 0xdf, 0x24, 0x7f, 0x84, 0x94, 0x6f, 0xb4, 0x4f,
+ 0x5f, 0xa4, 0xf4, 0x0f, 0x1f, 0xe4, 0x3f, 0xc4, 0xd4, 0x2f,
+ 0x62, 0x99, 0x89, 0x72, 0xa9, 0x52, 0x42, 0xb9, 0xe9, 0x12,
+ 0x02, 0xf9, 0x22, 0xd9, 0xc9, 0x32, 0x69, 0x92, 0x82, 0x79,
+ 0xa2, 0x59, 0x49, 0xb2, 0xe2, 0x19, 0x09, 0xf2, 0x29, 0xd2,
+ 0xc2, 0x39, 0x00, 0xfc, 0xe5, 0x19, 0xd7, 0x2b, 0x32, 0xce,
+ 0xb3, 0x4f, 0x56, 0xaa, 0x64, 0x98, 0x81, 0x7d, 0x7b, 0x87,
+ 0x9e, 0x62, 0xac, 0x50, 0x49, 0xb5, 0xc8, 0x34, 0x2d, 0xd1,
+ 0x1f, 0xe3, 0xfa, 0x06, 0xf6, 0x0a, 0x13, 0xef, 0x21, 0xdd,
+ 0xc4, 0x38, 0x45, 0xb9, 0xa0, 0x5c, 0x92, 0x6e, 0x77, 0x8b,
+ 0x8d, 0x71, 0x68, 0x94, 0x5a, 0xa6, 0xbf, 0x43, 0x3e, 0xc2,
+ 0xdb, 0x27, 0xe9, 0x15, 0x0c, 0xf0, 0xf1, 0x0d, 0x14, 0xe8,
+ 0x26, 0xda, 0xc3, 0x3f, 0x42, 0xbe, 0xa7, 0x5b, 0x95, 0x69,
+ 0x70, 0x8c, 0x8a, 0x76, 0x6f, 0x93, 0x5d, 0xa1, 0xb8, 0x44,
+ 0x39, 0xc5, 0xdc, 0x20, 0xee, 0x12, 0x0b, 0xf7, 0x07, 0xfb,
+ 0xe2, 0x1e, 0xd0, 0x2c, 0x35, 0xc9, 0xb4, 0x48, 0x51, 0xad,
+ 0x63, 0x9f, 0x86, 0x7a, 0x7c, 0x80, 0x99, 0x65, 0xab, 0x57,
+ 0x4e, 0xb2, 0xcf, 0x33, 0x2a, 0xd6, 0x18, 0xe4, 0xfd, 0x01,
+ 0xff, 0x03, 0x1a, 0xe6, 0x28, 0xd4, 0xcd, 0x31, 0x4c, 0xb0,
+ 0xa9, 0x55, 0x9b, 0x67, 0x7e, 0x82, 0x84, 0x78, 0x61, 0x9d,
+ 0x53, 0xaf, 0xb6, 0x4a, 0x37, 0xcb, 0xd2, 0x2e, 0xe0, 0x1c,
+ 0x05, 0xf9, 0x09, 0xf5, 0xec, 0x10, 0xde, 0x22, 0x3b, 0xc7,
+ 0xba, 0x46, 0x5f, 0xa3, 0x6d, 0x91, 0x88, 0x74, 0x72, 0x8e,
+ 0x97, 0x6b, 0xa5, 0x59, 0x40, 0xbc, 0xc1, 0x3d, 0x24, 0xd8,
+ 0x16, 0xea, 0xf3, 0x0f, 0x0e, 0xf2, 0xeb, 0x17, 0xd9, 0x25,
+ 0x3c, 0xc0, 0xbd, 0x41, 0x58, 0xa4, 0x6a, 0x96, 0x8f, 0x73,
+ 0x75, 0x89, 0x90, 0x6c, 0xa2, 0x5e, 0x47, 0xbb, 0xc6, 0x3a,
+ 0x23, 0xdf, 0x11, 0xed, 0xf4, 0x08, 0xf8, 0x04, 0x1d, 0xe1,
+ 0x2f, 0xd3, 0xca, 0x36, 0x4b, 0xb7, 0xae, 0x52, 0x9c, 0x60,
+ 0x79, 0x85, 0x83, 0x7f, 0x66, 0x9a, 0x54, 0xa8, 0xb1, 0x4d,
+ 0x30, 0xcc, 0xd5, 0x29, 0xe7, 0x1b, 0x02, 0xfe, 0x00, 0xfd,
+ 0xe7, 0x1a, 0xd3, 0x2e, 0x34, 0xc9, 0xbb, 0x46, 0x5c, 0xa1,
+ 0x68, 0x95, 0x8f, 0x72, 0x6b, 0x96, 0x8c, 0x71, 0xb8, 0x45,
+ 0x5f, 0xa2, 0xd0, 0x2d, 0x37, 0xca, 0x03, 0xfe, 0xe4, 0x19,
+ 0xd6, 0x2b, 0x31, 0xcc, 0x05, 0xf8, 0xe2, 0x1f, 0x6d, 0x90,
+ 0x8a, 0x77, 0xbe, 0x43, 0x59, 0xa4, 0xbd, 0x40, 0x5a, 0xa7,
+ 0x6e, 0x93, 0x89, 0x74, 0x06, 0xfb, 0xe1, 0x1c, 0xd5, 0x28,
+ 0x32, 0xcf, 0xb1, 0x4c, 0x56, 0xab, 0x62, 0x9f, 0x85, 0x78,
+ 0x0a, 0xf7, 0xed, 0x10, 0xd9, 0x24, 0x3e, 0xc3, 0xda, 0x27,
+ 0x3d, 0xc0, 0x09, 0xf4, 0xee, 0x13, 0x61, 0x9c, 0x86, 0x7b,
+ 0xb2, 0x4f, 0x55, 0xa8, 0x67, 0x9a, 0x80, 0x7d, 0xb4, 0x49,
+ 0x53, 0xae, 0xdc, 0x21, 0x3b, 0xc6, 0x0f, 0xf2, 0xe8, 0x15,
+ 0x0c, 0xf1, 0xeb, 0x16, 0xdf, 0x22, 0x38, 0xc5, 0xb7, 0x4a,
+ 0x50, 0xad, 0x64, 0x99, 0x83, 0x7e, 0x7f, 0x82, 0x98, 0x65,
+ 0xac, 0x51, 0x4b, 0xb6, 0xc4, 0x39, 0x23, 0xde, 0x17, 0xea,
+ 0xf0, 0x0d, 0x14, 0xe9, 0xf3, 0x0e, 0xc7, 0x3a, 0x20, 0xdd,
+ 0xaf, 0x52, 0x48, 0xb5, 0x7c, 0x81, 0x9b, 0x66, 0xa9, 0x54,
+ 0x4e, 0xb3, 0x7a, 0x87, 0x9d, 0x60, 0x12, 0xef, 0xf5, 0x08,
+ 0xc1, 0x3c, 0x26, 0xdb, 0xc2, 0x3f, 0x25, 0xd8, 0x11, 0xec,
+ 0xf6, 0x0b, 0x79, 0x84, 0x9e, 0x63, 0xaa, 0x57, 0x4d, 0xb0,
+ 0xce, 0x33, 0x29, 0xd4, 0x1d, 0xe0, 0xfa, 0x07, 0x75, 0x88,
+ 0x92, 0x6f, 0xa6, 0x5b, 0x41, 0xbc, 0xa5, 0x58, 0x42, 0xbf,
+ 0x76, 0x8b, 0x91, 0x6c, 0x1e, 0xe3, 0xf9, 0x04, 0xcd, 0x30,
+ 0x2a, 0xd7, 0x18, 0xe5, 0xff, 0x02, 0xcb, 0x36, 0x2c, 0xd1,
+ 0xa3, 0x5e, 0x44, 0xb9, 0x70, 0x8d, 0x97, 0x6a, 0x73, 0x8e,
+ 0x94, 0x69, 0xa0, 0x5d, 0x47, 0xba, 0xc8, 0x35, 0x2f, 0xd2,
+ 0x1b, 0xe6, 0xfc, 0x01, 0x00, 0xfe, 0xe1, 0x1f, 0xdf, 0x21,
+ 0x3e, 0xc0, 0xa3, 0x5d, 0x42, 0xbc, 0x7c, 0x82, 0x9d, 0x63,
+ 0x5b, 0xa5, 0xba, 0x44, 0x84, 0x7a, 0x65, 0x9b, 0xf8, 0x06,
+ 0x19, 0xe7, 0x27, 0xd9, 0xc6, 0x38, 0xb6, 0x48, 0x57, 0xa9,
+ 0x69, 0x97, 0x88, 0x76, 0x15, 0xeb, 0xf4, 0x0a, 0xca, 0x34,
+ 0x2b, 0xd5, 0xed, 0x13, 0x0c, 0xf2, 0x32, 0xcc, 0xd3, 0x2d,
+ 0x4e, 0xb0, 0xaf, 0x51, 0x91, 0x6f, 0x70, 0x8e, 0x71, 0x8f,
+ 0x90, 0x6e, 0xae, 0x50, 0x4f, 0xb1, 0xd2, 0x2c, 0x33, 0xcd,
+ 0x0d, 0xf3, 0xec, 0x12, 0x2a, 0xd4, 0xcb, 0x35, 0xf5, 0x0b,
+ 0x14, 0xea, 0x89, 0x77, 0x68, 0x96, 0x56, 0xa8, 0xb7, 0x49,
+ 0xc7, 0x39, 0x26, 0xd8, 0x18, 0xe6, 0xf9, 0x07, 0x64, 0x9a,
+ 0x85, 0x7b, 0xbb, 0x45, 0x5a, 0xa4, 0x9c, 0x62, 0x7d, 0x83,
+ 0x43, 0xbd, 0xa2, 0x5c, 0x3f, 0xc1, 0xde, 0x20, 0xe0, 0x1e,
+ 0x01, 0xff, 0xe2, 0x1c, 0x03, 0xfd, 0x3d, 0xc3, 0xdc, 0x22,
+ 0x41, 0xbf, 0xa0, 0x5e, 0x9e, 0x60, 0x7f, 0x81, 0xb9, 0x47,
+ 0x58, 0xa6, 0x66, 0x98, 0x87, 0x79, 0x1a, 0xe4, 0xfb, 0x05,
+ 0xc5, 0x3b, 0x24, 0xda, 0x54, 0xaa, 0xb5, 0x4b, 0x8b, 0x75,
+ 0x6a, 0x94, 0xf7, 0x09, 0x16, 0xe8, 0x28, 0xd6, 0xc9, 0x37,
+ 0x0f, 0xf1, 0xee, 0x10, 0xd0, 0x2e, 0x31, 0xcf, 0xac, 0x52,
+ 0x4d, 0xb3, 0x73, 0x8d, 0x92, 0x6c, 0x93, 0x6d, 0x72, 0x8c,
+ 0x4c, 0xb2, 0xad, 0x53, 0x30, 0xce, 0xd1, 0x2f, 0xef, 0x11,
+ 0x0e, 0xf0, 0xc8, 0x36, 0x29, 0xd7, 0x17, 0xe9, 0xf6, 0x08,
+ 0x6b, 0x95, 0x8a, 0x74, 0xb4, 0x4a, 0x55, 0xab, 0x25, 0xdb,
+ 0xc4, 0x3a, 0xfa, 0x04, 0x1b, 0xe5, 0x86, 0x78, 0x67, 0x99,
+ 0x59, 0xa7, 0xb8, 0x46, 0x7e, 0x80, 0x9f, 0x61, 0xa1, 0x5f,
+ 0x40, 0xbe, 0xdd, 0x23, 0x3c, 0xc2, 0x02, 0xfc, 0xe3, 0x1d,
+ 0x00, 0xff, 0xe3, 0x1c, 0xdb, 0x24, 0x38, 0xc7, 0xab, 0x54,
+ 0x48, 0xb7, 0x70, 0x8f, 0x93, 0x6c, 0x4b, 0xb4, 0xa8, 0x57,
+ 0x90, 0x6f, 0x73, 0x8c, 0xe0, 0x1f, 0x03, 0xfc, 0x3b, 0xc4,
+ 0xd8, 0x27, 0x96, 0x69, 0x75, 0x8a, 0x4d, 0xb2, 0xae, 0x51,
+ 0x3d, 0xc2, 0xde, 0x21, 0xe6, 0x19, 0x05, 0xfa, 0xdd, 0x22,
+ 0x3e, 0xc1, 0x06, 0xf9, 0xe5, 0x1a, 0x76, 0x89, 0x95, 0x6a,
+ 0xad, 0x52, 0x4e, 0xb1, 0x31, 0xce, 0xd2, 0x2d, 0xea, 0x15,
+ 0x09, 0xf6, 0x9a, 0x65, 0x79, 0x86, 0x41, 0xbe, 0xa2, 0x5d,
+ 0x7a, 0x85, 0x99, 0x66, 0xa1, 0x5e, 0x42, 0xbd, 0xd1, 0x2e,
+ 0x32, 0xcd, 0x0a, 0xf5, 0xe9, 0x16, 0xa7, 0x58, 0x44, 0xbb,
+ 0x7c, 0x83, 0x9f, 0x60, 0x0c, 0xf3, 0xef, 0x10, 0xd7, 0x28,
+ 0x34, 0xcb, 0xec, 0x13, 0x0f, 0xf0, 0x37, 0xc8, 0xd4, 0x2b,
+ 0x47, 0xb8, 0xa4, 0x5b, 0x9c, 0x63, 0x7f, 0x80, 0x62, 0x9d,
+ 0x81, 0x7e, 0xb9, 0x46, 0x5a, 0xa5, 0xc9, 0x36, 0x2a, 0xd5,
+ 0x12, 0xed, 0xf1, 0x0e, 0x29, 0xd6, 0xca, 0x35, 0xf2, 0x0d,
+ 0x11, 0xee, 0x82, 0x7d, 0x61, 0x9e, 0x59, 0xa6, 0xba, 0x45,
+ 0xf4, 0x0b, 0x17, 0xe8, 0x2f, 0xd0, 0xcc, 0x33, 0x5f, 0xa0,
+ 0xbc, 0x43, 0x84, 0x7b, 0x67, 0x98, 0xbf, 0x40, 0x5c, 0xa3,
+ 0x64, 0x9b, 0x87, 0x78, 0x14, 0xeb, 0xf7, 0x08, 0xcf, 0x30,
+ 0x2c, 0xd3, 0x53, 0xac, 0xb0, 0x4f, 0x88, 0x77, 0x6b, 0x94,
+ 0xf8, 0x07, 0x1b, 0xe4, 0x23, 0xdc, 0xc0, 0x3f, 0x18, 0xe7,
+ 0xfb, 0x04, 0xc3, 0x3c, 0x20, 0xdf, 0xb3, 0x4c, 0x50, 0xaf,
+ 0x68, 0x97, 0x8b, 0x74, 0xc5, 0x3a, 0x26, 0xd9, 0x1e, 0xe1,
+ 0xfd, 0x02, 0x6e, 0x91, 0x8d, 0x72, 0xb5, 0x4a, 0x56, 0xa9,
+ 0x8e, 0x71, 0x6d, 0x92, 0x55, 0xaa, 0xb6, 0x49, 0x25, 0xda,
+ 0xc6, 0x39, 0xfe, 0x01, 0x1d, 0xe2
+};
+
+static const unsigned char gf_inv_table_base[] = {
+ 0x00, 0x01, 0x8e, 0xf4, 0x47, 0xa7, 0x7a, 0xba, 0xad, 0x9d,
+ 0xdd, 0x98, 0x3d, 0xaa, 0x5d, 0x96, 0xd8, 0x72, 0xc0, 0x58,
+ 0xe0, 0x3e, 0x4c, 0x66, 0x90, 0xde, 0x55, 0x80, 0xa0, 0x83,
+ 0x4b, 0x2a, 0x6c, 0xed, 0x39, 0x51, 0x60, 0x56, 0x2c, 0x8a,
+ 0x70, 0xd0, 0x1f, 0x4a, 0x26, 0x8b, 0x33, 0x6e, 0x48, 0x89,
+ 0x6f, 0x2e, 0xa4, 0xc3, 0x40, 0x5e, 0x50, 0x22, 0xcf, 0xa9,
+ 0xab, 0x0c, 0x15, 0xe1, 0x36, 0x5f, 0xf8, 0xd5, 0x92, 0x4e,
+ 0xa6, 0x04, 0x30, 0x88, 0x2b, 0x1e, 0x16, 0x67, 0x45, 0x93,
+ 0x38, 0x23, 0x68, 0x8c, 0x81, 0x1a, 0x25, 0x61, 0x13, 0xc1,
+ 0xcb, 0x63, 0x97, 0x0e, 0x37, 0x41, 0x24, 0x57, 0xca, 0x5b,
+ 0xb9, 0xc4, 0x17, 0x4d, 0x52, 0x8d, 0xef, 0xb3, 0x20, 0xec,
+ 0x2f, 0x32, 0x28, 0xd1, 0x11, 0xd9, 0xe9, 0xfb, 0xda, 0x79,
+ 0xdb, 0x77, 0x06, 0xbb, 0x84, 0xcd, 0xfe, 0xfc, 0x1b, 0x54,
+ 0xa1, 0x1d, 0x7c, 0xcc, 0xe4, 0xb0, 0x49, 0x31, 0x27, 0x2d,
+ 0x53, 0x69, 0x02, 0xf5, 0x18, 0xdf, 0x44, 0x4f, 0x9b, 0xbc,
+ 0x0f, 0x5c, 0x0b, 0xdc, 0xbd, 0x94, 0xac, 0x09, 0xc7, 0xa2,
+ 0x1c, 0x82, 0x9f, 0xc6, 0x34, 0xc2, 0x46, 0x05, 0xce, 0x3b,
+ 0x0d, 0x3c, 0x9c, 0x08, 0xbe, 0xb7, 0x87, 0xe5, 0xee, 0x6b,
+ 0xeb, 0xf2, 0xbf, 0xaf, 0xc5, 0x64, 0x07, 0x7b, 0x95, 0x9a,
+ 0xae, 0xb6, 0x12, 0x59, 0xa5, 0x35, 0x65, 0xb8, 0xa3, 0x9e,
+ 0xd2, 0xf7, 0x62, 0x5a, 0x85, 0x7d, 0xa8, 0x3a, 0x29, 0x71,
+ 0xc8, 0xf6, 0xf9, 0x43, 0xd7, 0xd6, 0x10, 0x73, 0x76, 0x78,
+ 0x99, 0x0a, 0x19, 0x91, 0x14, 0x3f, 0xe6, 0xf0, 0x86, 0xb1,
+ 0xe2, 0xf1, 0xfa, 0x74, 0xf3, 0xb4, 0x6d, 0x21, 0xb2, 0x6a,
+ 0xe3, 0xe7, 0xb5, 0xea, 0x03, 0x8f, 0xd3, 0xc9, 0x42, 0xd4,
+ 0xe8, 0x75, 0x7f, 0xff, 0x7e, 0xfd
+};
+#endif // GF_LARGE_TABLES
+
+#endif //_EC_BASE_H_
diff --git a/contrib/libs/isa-l/erasure_code/ec_highlevel_func.c b/contrib/libs/isa-l/erasure_code/ec_highlevel_func.c
new file mode 100644
index 0000000000..c57d460a61
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/ec_highlevel_func.c
@@ -0,0 +1,336 @@
+/**********************************************************************
+ Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**********************************************************************/
+#include <limits.h>
+#include "erasure_code.h"
+
+#if __x86_64__ || __i386__ || _M_X64 || _M_IX86
+void ec_encode_data_sse(int len, int k, int rows, unsigned char *g_tbls, unsigned char **data,
+ unsigned char **coding)
+{
+
+ if (len < 16) {
+ ec_encode_data_base(len, k, rows, g_tbls, data, coding);
+ return;
+ }
+
+ while (rows >= 4) {
+ gf_4vect_dot_prod_sse(len, k, g_tbls, data, coding);
+ g_tbls += 4 * k * 32;
+ coding += 4;
+ rows -= 4;
+ }
+ switch (rows) {
+ case 3:
+ gf_3vect_dot_prod_sse(len, k, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_dot_prod_sse(len, k, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_dot_prod_sse(len, k, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+
+}
+
+void ec_encode_data_avx(int len, int k, int rows, unsigned char *g_tbls, unsigned char **data,
+ unsigned char **coding)
+{
+ if (len < 16) {
+ ec_encode_data_base(len, k, rows, g_tbls, data, coding);
+ return;
+ }
+
+ while (rows >= 4) {
+ gf_4vect_dot_prod_avx(len, k, g_tbls, data, coding);
+ g_tbls += 4 * k * 32;
+ coding += 4;
+ rows -= 4;
+ }
+ switch (rows) {
+ case 3:
+ gf_3vect_dot_prod_avx(len, k, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_dot_prod_avx(len, k, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_dot_prod_avx(len, k, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+
+}
+
+void ec_encode_data_avx2(int len, int k, int rows, unsigned char *g_tbls, unsigned char **data,
+ unsigned char **coding)
+{
+
+ if (len < 32) {
+ ec_encode_data_base(len, k, rows, g_tbls, data, coding);
+ return;
+ }
+
+ while (rows >= 4) {
+ gf_4vect_dot_prod_avx2(len, k, g_tbls, data, coding);
+ g_tbls += 4 * k * 32;
+ coding += 4;
+ rows -= 4;
+ }
+ switch (rows) {
+ case 3:
+ gf_3vect_dot_prod_avx2(len, k, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_dot_prod_avx2(len, k, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_dot_prod_avx2(len, k, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+
+}
+
+#ifdef HAVE_AS_KNOWS_AVX512
+
+extern int gf_vect_dot_prod_avx512(int len, int k, unsigned char *g_tbls, unsigned char **data,
+ unsigned char *dest);
+extern int gf_2vect_dot_prod_avx512(int len, int k, unsigned char *g_tbls,
+ unsigned char **data, unsigned char **coding);
+extern int gf_3vect_dot_prod_avx512(int len, int k, unsigned char *g_tbls,
+ unsigned char **data, unsigned char **coding);
+extern int gf_4vect_dot_prod_avx512(int len, int k, unsigned char *g_tbls,
+ unsigned char **data, unsigned char **coding);
+extern void gf_vect_mad_avx512(int len, int vec, int vec_i, unsigned char *gftbls,
+ unsigned char *src, unsigned char *dest);
+extern void gf_2vect_mad_avx512(int len, int vec, int vec_i, unsigned char *gftbls,
+ unsigned char *src, unsigned char **dest);
+extern void gf_3vect_mad_avx512(int len, int vec, int vec_i, unsigned char *gftbls,
+ unsigned char *src, unsigned char **dest);
+extern void gf_4vect_mad_avx512(int len, int vec, int vec_i, unsigned char *gftbls,
+ unsigned char *src, unsigned char **dest);
+
+void ec_encode_data_avx512(int len, int k, int rows, unsigned char *g_tbls,
+ unsigned char **data, unsigned char **coding)
+{
+
+ if (len < 64) {
+ ec_encode_data_base(len, k, rows, g_tbls, data, coding);
+ return;
+ }
+
+ while (rows >= 4) {
+ gf_4vect_dot_prod_avx512(len, k, g_tbls, data, coding);
+ g_tbls += 4 * k * 32;
+ coding += 4;
+ rows -= 4;
+ }
+ switch (rows) {
+ case 3:
+ gf_3vect_dot_prod_avx512(len, k, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_dot_prod_avx512(len, k, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_dot_prod_avx512(len, k, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+}
+
+void ec_encode_data_update_avx512(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding)
+{
+ if (len < 64) {
+ ec_encode_data_update_base(len, k, rows, vec_i, g_tbls, data, coding);
+ return;
+ }
+
+ while (rows >= 4) {
+ gf_4vect_mad_avx512(len, k, vec_i, g_tbls, data, coding);
+ g_tbls += 4 * k * 32;
+ coding += 4;
+ rows -= 4;
+ }
+ switch (rows) {
+ case 3:
+ gf_3vect_mad_avx512(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_mad_avx512(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_mad_avx512(len, k, vec_i, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+}
+
+#endif // HAVE_AS_KNOWS_AVX512
+
+#if __WORDSIZE == 64 || _WIN64 || __x86_64__
+
+void ec_encode_data_update_sse(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding)
+{
+ if (len < 16) {
+ ec_encode_data_update_base(len, k, rows, vec_i, g_tbls, data, coding);
+ return;
+ }
+
+ while (rows > 6) {
+ gf_6vect_mad_sse(len, k, vec_i, g_tbls, data, coding);
+ g_tbls += 6 * k * 32;
+ coding += 6;
+ rows -= 6;
+ }
+ switch (rows) {
+ case 6:
+ gf_6vect_mad_sse(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 5:
+ gf_5vect_mad_sse(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 4:
+ gf_4vect_mad_sse(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 3:
+ gf_3vect_mad_sse(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_mad_sse(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_mad_sse(len, k, vec_i, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+
+}
+
+void ec_encode_data_update_avx(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding)
+{
+ if (len < 16) {
+ ec_encode_data_update_base(len, k, rows, vec_i, g_tbls, data, coding);
+ return;
+ }
+ while (rows > 6) {
+ gf_6vect_mad_avx(len, k, vec_i, g_tbls, data, coding);
+ g_tbls += 6 * k * 32;
+ coding += 6;
+ rows -= 6;
+ }
+ switch (rows) {
+ case 6:
+ gf_6vect_mad_avx(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 5:
+ gf_5vect_mad_avx(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 4:
+ gf_4vect_mad_avx(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 3:
+ gf_3vect_mad_avx(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_mad_avx(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_mad_avx(len, k, vec_i, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+
+}
+
+void ec_encode_data_update_avx2(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding)
+{
+ if (len < 32) {
+ ec_encode_data_update_base(len, k, rows, vec_i, g_tbls, data, coding);
+ return;
+ }
+ while (rows > 6) {
+ gf_6vect_mad_avx2(len, k, vec_i, g_tbls, data, coding);
+ g_tbls += 6 * k * 32;
+ coding += 6;
+ rows -= 6;
+ }
+ switch (rows) {
+ case 6:
+ gf_6vect_mad_avx2(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 5:
+ gf_5vect_mad_avx2(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 4:
+ gf_4vect_mad_avx2(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 3:
+ gf_3vect_mad_avx2(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 2:
+ gf_2vect_mad_avx2(len, k, vec_i, g_tbls, data, coding);
+ break;
+ case 1:
+ gf_vect_mad_avx2(len, k, vec_i, g_tbls, data, *coding);
+ break;
+ case 0:
+ break;
+ }
+
+}
+
+#endif //__WORDSIZE == 64 || _WIN64 || __x86_64__
+#endif //__x86_64__ || __i386__ || _M_X64 || _M_IX86
+
+struct slver {
+ unsigned short snum;
+ unsigned char ver;
+ unsigned char core;
+};
+
+// Version info
+struct slver ec_init_tables_slver_00010068;
+struct slver ec_init_tables_slver = { 0x0068, 0x01, 0x00 };
+
+struct slver ec_encode_data_sse_slver_00020069;
+struct slver ec_encode_data_sse_slver = { 0x0069, 0x02, 0x00 };
diff --git a/contrib/libs/isa-l/erasure_code/ec_multibinary.asm b/contrib/libs/isa-l/erasure_code/ec_multibinary.asm
new file mode 100644
index 0000000000..a07f45d6f8
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/ec_multibinary.asm
@@ -0,0 +1,95 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+%include "reg_sizes.asm"
+%include "multibinary.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf32
+ [bits 32]
+%else
+ default rel
+ [bits 64]
+
+ extern ec_encode_data_update_sse
+ extern ec_encode_data_update_avx
+ extern ec_encode_data_update_avx2
+%ifdef HAVE_AS_KNOWS_AVX512
+ extern ec_encode_data_avx512
+ extern gf_vect_dot_prod_avx512
+ extern ec_encode_data_update_avx512
+ extern gf_vect_mad_avx512
+%endif
+ extern gf_vect_mul_sse
+ extern gf_vect_mul_avx
+
+ extern gf_vect_mad_sse
+ extern gf_vect_mad_avx
+ extern gf_vect_mad_avx2
+%endif
+
+extern gf_vect_mul_base
+extern ec_encode_data_base
+extern ec_encode_data_update_base
+extern gf_vect_dot_prod_base
+extern gf_vect_mad_base
+
+extern gf_vect_dot_prod_sse
+extern gf_vect_dot_prod_avx
+extern gf_vect_dot_prod_avx2
+extern ec_encode_data_sse
+extern ec_encode_data_avx
+extern ec_encode_data_avx2
+
+mbin_interface ec_encode_data
+mbin_interface gf_vect_dot_prod
+mbin_interface gf_vect_mul
+mbin_interface ec_encode_data_update
+mbin_interface gf_vect_mad
+
+%ifidn __OUTPUT_FORMAT__, elf32
+ mbin_dispatch_init5 ec_encode_data, ec_encode_data_base, ec_encode_data_sse, ec_encode_data_avx, ec_encode_data_avx2
+ mbin_dispatch_init5 gf_vect_dot_prod, gf_vect_dot_prod_base, gf_vect_dot_prod_sse, gf_vect_dot_prod_avx, gf_vect_dot_prod_avx2
+ mbin_dispatch_init2 gf_vect_mul, gf_vect_mul_base
+ mbin_dispatch_init2 ec_encode_data_update, ec_encode_data_update_base
+ mbin_dispatch_init2 gf_vect_mad, gf_vect_mad_base
+%else
+
+ mbin_dispatch_init5 gf_vect_mul, gf_vect_mul_base, gf_vect_mul_sse, gf_vect_mul_avx, gf_vect_mul_avx
+ mbin_dispatch_init6 ec_encode_data, ec_encode_data_base, ec_encode_data_sse, ec_encode_data_avx, ec_encode_data_avx2, ec_encode_data_avx512
+ mbin_dispatch_init6 ec_encode_data_update, ec_encode_data_update_base, ec_encode_data_update_sse, ec_encode_data_update_avx, ec_encode_data_update_avx2, ec_encode_data_update_avx512
+ mbin_dispatch_init6 gf_vect_mad, gf_vect_mad_base, gf_vect_mad_sse, gf_vect_mad_avx, gf_vect_mad_avx2, gf_vect_mad_avx512
+ mbin_dispatch_init6 gf_vect_dot_prod, gf_vect_dot_prod_base, gf_vect_dot_prod_sse, gf_vect_dot_prod_avx, gf_vect_dot_prod_avx2, gf_vect_dot_prod_avx512
+%endif
+
+;;; func core, ver, snum
+slversion ec_encode_data, 00, 06, 0133
+slversion gf_vect_mul, 00, 05, 0134
+slversion ec_encode_data_update, 00, 05, 0212
+slversion gf_vect_dot_prod, 00, 05, 0138
+slversion gf_vect_mad, 00, 04, 0213
diff --git a/contrib/libs/isa-l/erasure_code/ec_multibinary_darwin.asm b/contrib/libs/isa-l/erasure_code/ec_multibinary_darwin.asm
new file mode 100644
index 0000000000..8c2537f562
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/ec_multibinary_darwin.asm
@@ -0,0 +1,96 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+%include "reg_sizes.asm"
+%include "multibinary.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf32
+ [bits 32]
+%else
+ default rel
+ [bits 64]
+
+ extern _ec_encode_data_update_sse
+ extern _ec_encode_data_update_avx
+ extern _ec_encode_data_update_avx2
+%ifdef HAVE_AS_KNOWS_AVX512
+ extern _ec_encode_data_avx512
+ extern _gf_vect_dot_prod_avx512
+ extern _ec_encode_data_update_avx512
+ extern _gf_vect_mad_avx512
+%endif
+ extern _gf_vect_mul_sse
+ extern _gf_vect_mul_avx
+
+ extern _gf_vect_mad_sse
+ extern _gf_vect_mad_avx
+ extern _gf_vect_mad_avx2
+%endif
+
+extern _gf_vect_mul_base
+extern _ec_encode_data_base
+extern _ec_encode_data_update_base
+extern _gf_vect_dot_prod_base
+extern _gf_vect_mad_base
+
+extern _gf_vect_dot_prod_sse
+extern _gf_vect_dot_prod_avx
+extern _gf_vect_dot_prod_avx2
+extern _ec_encode_data_sse
+extern _ec_encode_data_avx
+extern _ec_encode_data_avx2
+
+mbin_interface _ec_encode_data
+mbin_interface _gf_vect_dot_prod
+mbin_interface _gf_vect_mul
+mbin_interface _ec_encode_data_update
+mbin_interface _gf_vect_mad
+
+%ifidn __OUTPUT_FORMAT__, elf32
+ mbin_dispatch_init5 _ec_encode_data, _ec_encode_data_base, _ec_encode_data_sse, _ec_encode_data_avx, _ec_encode_data_avx2
+ mbin_dispatch_init5 _gf_vect_dot_prod, _gf_vect_dot_prod_base, _gf_vect_dot_prod_sse, _gf_vect_dot_prod_avx, _gf_vect_dot_prod_avx2
+ mbin_dispatch_init2 _gf_vect_mul, _gf_vect_mul_base
+ mbin_dispatch_init2 _ec_encode_data_update, _ec_encode_data_update_base
+ mbin_dispatch_init2 _gf_vect_mad, _gf_vect_mad_base
+%else
+
+ mbin_dispatch_init5 _gf_vect_mul, _gf_vect_mul_base, _gf_vect_mul_sse, _gf_vect_mul_avx, _gf_vect_mul_avx
+ mbin_dispatch_init6 _ec_encode_data, _ec_encode_data_base, _ec_encode_data_sse, _ec_encode_data_avx, _ec_encode_data_avx2, _ec_encode_data_avx512
+ mbin_dispatch_init6 _ec_encode_data_update, _ec_encode_data_update_base, _ec_encode_data_update_sse, _ec_encode_data_update_avx, _ec_encode_data_update_avx2, _ec_encode_data_update_avx512
+ mbin_dispatch_init6 _gf_vect_mad, _gf_vect_mad_base, _gf_vect_mad_sse, _gf_vect_mad_avx, _gf_vect_mad_avx2, _gf_vect_mad_avx512
+ mbin_dispatch_init6 _gf_vect_dot_prod, _gf_vect_dot_prod_base, _gf_vect_dot_prod_sse, _gf_vect_dot_prod_avx, _gf_vect_dot_prod_avx2, _gf_vect_dot_prod_avx512
+%endif
+
+
+;;; func core, ver, snum
+slversion ec_encode_data, 00, 06, 0133
+slversion gf_vect_mul, 00, 05, 0134
+slversion ec_encode_data_update, 00, 05, 0212
+slversion gf_vect_dot_prod, 00, 05, 0138
+slversion gf_vect_mad, 00, 04, 0213
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx.asm
new file mode 100644
index 0000000000..6233d42e5d
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx.asm
@@ -0,0 +1,341 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_dot_prod_avx(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r9
+ %define tmp4 r12 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 3*16 + 3*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_reg r12, 3*16 + 0*8
+ save_reg r13, 3*16 + 1*8
+ save_reg r14, 3*16 + 2*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ mov r12, [rsp + 3*16 + 0*8]
+ mov r13, [rsp + 3*16 + 1*8]
+ mov r14, [rsp + 3*16 + 2*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp4 trans2
+ %define tmp4_m var(0)
+ %define return eax
+ %macro SLDR 2 ;; stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*1 ;1 local variable
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*1 ;1 local variable
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+
+%define vec_i tmp2
+%define ptr tmp3
+%define dest2 tmp4
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp4_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ; 64-bit code
+ default rel
+ [bits 64]
+%endif
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f xmm8
+ %define xgft1_lo xmm7
+ %define xgft1_hi xmm6
+ %define xgft2_lo xmm5
+ %define xgft2_hi xmm4
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+%else ;32-bit code
+ %define xmask0f xmm4
+ %define xgft1_lo xmm7
+ %define xgft1_hi xmm6
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+%endif
+
+align 16
+global gf_2vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_dot_prod_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_dot_prod_avx)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop16:
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ %ifidn PS,8 ; 64-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+
+ SLDR len, len_m
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_2vect_dot_prod_avx, 02, 05, 0191
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx2.asm
new file mode 100644
index 0000000000..53052d56e0
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx2.asm
@@ -0,0 +1,360 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_dot_prod_avx2(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r9
+ %define tmp4 r12 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 3*16 + 3*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ save_reg r12, 3*16 + 0*8
+ save_reg r13, 3*16 + 1*8
+ save_reg r14, 3*16 + 2*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ mov r12, [rsp + 3*16 + 0*8]
+ mov r13, [rsp + 3*16 + 1*8]
+ mov r14, [rsp + 3*16 + 2*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define tmp edx
+ %define tmp.w edx
+ %define tmp.b dl
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp4 trans2
+ %define tmp4_m var(0)
+ %define return eax
+ %macro SLDR 2 ;stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*1 ;1 local variable
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*1 ;1 local variable
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+
+%define vec_i tmp2
+%define ptr tmp3
+%define dest2 tmp4
+%define pos return
+
+%ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp4_m
+%endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ;64-bit code
+ default rel
+ [bits 64]
+%endif
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f ymm8
+ %define xmask0fx xmm8
+ %define xgft1_lo ymm7
+ %define xgft1_hi ymm6
+ %define xgft2_lo ymm5
+ %define xgft2_hi ymm4
+
+ %define x0 ymm0
+ %define xtmpa ymm1
+ %define xp1 ymm2
+ %define xp2 ymm3
+%else ;32-bit code
+ %define xmask0f ymm7
+ %define xmask0fx xmm7
+ %define xgft1_lo ymm5
+ %define xgft1_hi ymm4
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+
+ %define x0 ymm0
+ %define xtmpa ymm1
+ %define xp1 ymm2
+ %define xp2 ymm3
+
+%endif
+
+align 16
+global gf_2vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_dot_prod_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_dot_prod_avx2)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 32
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop32:
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft1_lo, xgft1_lo, xgft1_lo, 0x00 ; swapped to lo | lo
+ %ifidn PS,8 ; 64-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft2_lo, xgft2_lo, xgft2_lo, 0x00 ; swapped to lo | lo
+
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add tmp, 32
+ add vec_i, PS
+ %else
+ XLDR x0, [ptr+pos] ;Get next source vector
+ %endif
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft2_lo, xgft2_lo, xgft2_lo, 0x00 ; swapped to lo | lo
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+
+ SLDR len, len_m
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop32 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_2vect_dot_prod_avx2, 04, 05, 0196
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx512.asm
new file mode 100644
index 0000000000..0fe2f434a1
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_avx512.asm
@@ -0,0 +1,250 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_dot_prod_avx512(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r12 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 9*16 + 5*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ vmovdqa [rsp + 3*16], xmm9
+ vmovdqa [rsp + 4*16], xmm10
+ vmovdqa [rsp + 5*16], xmm11
+ vmovdqa [rsp + 6*16], xmm12
+ vmovdqa [rsp + 7*16], xmm13
+ vmovdqa [rsp + 8*16], xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r13, 9*16 + 1*8
+ save_reg r14, 9*16 + 2*8
+ save_reg r15, 9*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r13, [rsp + 9*16 + 1*8]
+ mov r14, [rsp + 9*16 + 2*8]
+ mov r15, [rsp + 9*16 + 3*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest2 tmp3
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%define xmask0f zmm8
+%define xgft1_lo zmm7
+%define xgft1_loy ymm7
+%define xgft1_hi zmm6
+%define xgft2_lo zmm5
+%define xgft2_loy ymm5
+%define xgft2_hi zmm4
+
+%define x0 zmm0
+%define xtmpa zmm1
+%define xp1 zmm2
+%define xp2 zmm3
+
+default rel
+[bits 64]
+
+section .text
+
+align 16
+global gf_2vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_dot_prod_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_dot_prod_avx512)
+%endif
+
+ FUNC_SAVE
+ sub len, 64
+ jl .return_fail
+
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest2, [dest1+PS]
+ mov dest1, [dest1]
+
+.loop64:
+ vpxorq xp1, xp1, xp1
+ vpxorq xp2, xp2, xp2
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add vec_i, PS
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vmovdqu8 xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ vmovdqu8 xgft2_loy, [tmp+vec*(32/PS)] ;Load array Bx{00}..{0f}, Bx{00}..{f0}
+ add tmp, 32
+
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x55
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x00
+ vshufi64x2 xgft2_hi, xgft2_lo, xgft2_lo, 0x55
+ vshufi64x2 xgft2_lo, xgft2_lo, xgft2_lo, 0x00
+
+ vpshufb xgft1_hi, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft1_hi, xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxorq xp1, xp1, xgft1_hi ;xp1 += partial
+
+ vpshufb xgft2_hi, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft2_hi, xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxorq xp2, xp2, xgft2_hi ;xp2 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_2vect_dot_prod_avx512
+no_gf_2vect_dot_prod_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_sse.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_sse.asm
new file mode 100644
index 0000000000..ad61093471
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_dot_prod_sse.asm
@@ -0,0 +1,343 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_dot_prod_sse(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r9
+ %define tmp4 r12 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 3*16 + 3*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_reg r12, 3*16 + 0*8
+ save_reg r13, 3*16 + 1*8
+ save_reg r14, 3*16 + 2*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp + 0*16]
+ movdqa xmm7, [rsp + 1*16]
+ movdqa xmm8, [rsp + 2*16]
+ mov r12, [rsp + 3*16 + 0*8]
+ mov r13, [rsp + 3*16 + 1*8]
+ mov r14, [rsp + 3*16 + 2*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp4 trans2
+ %define tmp4_m var(0)
+ %define return eax
+ %macro SLDR 2 ;; stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*1 ;1 local variable
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*1 ;1 local variable
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+
+%define vec_i tmp2
+%define ptr tmp3
+%define dest2 tmp4
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp4_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+%ifidn PS,8 ;64-bit code
+ default rel
+ [bits 64]
+%endif
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f xmm8
+ %define xgft1_lo xmm7
+ %define xgft1_hi xmm6
+ %define xgft2_lo xmm5
+ %define xgft2_hi xmm4
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+%else ;32-bit code
+ %define xmask0f xmm4
+ %define xgft1_lo xmm7
+ %define xgft1_hi xmm6
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+%endif
+
+align 16
+global gf_2vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_dot_prod_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_dot_prod_sse)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop16:
+ pxor xp1, xp1
+ pxor xp2, xp2
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ movdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ %ifidn PS,8 ;64-bit code
+ movdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ;32-bit code
+ movdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ pshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ pxor xp2, xgft2_hi ;xp2 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+
+ SLDR len, len_m
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_2vect_dot_prod_sse, 00, 04, 0062
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx.asm
new file mode 100644
index 0000000000..2d51dad33f
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx.asm
@@ -0,0 +1,240 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_mad_avx(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*9 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r15, 9*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r15, [rsp + 9*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_2vect_mad_avx(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp2
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm14
+%define xgft1_lo xmm13
+%define xgft1_hi xmm12
+%define xgft2_lo xmm11
+%define xgft2_hi xmm10
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xd1 xmm6
+%define xd2 xmm7
+%define xtmpd1 xmm8
+%define xtmpd2 xmm9
+
+
+align 16
+global gf_2vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_mad_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_mad_avx)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xgft2_hi, [tmp+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+
+ mov dest2, [dest1+PS]
+ mov dest1, [dest1]
+
+ XLDR xtmpd1, [dest1+len] ;backup the last 16 bytes in dest
+ XLDR xtmpd2, [dest2+len] ;backup the last 16 bytes in dest
+
+.loop16:
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+.loop16_overlap:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xtmph1, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ vpshufb xtmph2, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ vmovdqa xd1, xtmpd1 ;Restore xd1
+ vmovdqa xd2, xtmpd2 ;Restore xd2
+ jmp .loop16_overlap ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_2vect_mad_avx, 02, 01, 0204
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx2.asm
new file mode 100644
index 0000000000..2b0fd8ea2d
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx2.asm
@@ -0,0 +1,251 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_mad_avx2(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*9 + 3*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ vmovdqa [rsp+16*3],xmm9
+ vmovdqa [rsp+16*4],xmm10
+ vmovdqa [rsp+16*5],xmm11
+ vmovdqa [rsp+16*6],xmm12
+ vmovdqa [rsp+16*7],xmm13
+ vmovdqa [rsp+16*8],xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r15, 9*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ vmovdqa xmm9, [rsp+16*3]
+ vmovdqa xmm10, [rsp+16*4]
+ vmovdqa xmm11, [rsp+16*5]
+ vmovdqa xmm12, [rsp+16*6]
+ vmovdqa xmm13, [rsp+16*7]
+ vmovdqa xmm14, [rsp+16*8]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r15, [rsp + 9*16 + 1*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_2vect_mad_avx2(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp2
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm14
+%define xmask0fx xmm14
+%define xgft1_lo ymm13
+%define xgft1_hi ymm12
+%define xgft2_lo ymm11
+%define xgft2_hi ymm10
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xtmph1 ymm2
+%define xtmpl1 ymm3
+%define xtmph2 ymm4
+%define xtmpl2 ymm5
+%define xd1 ymm6
+%define xd2 ymm7
+%define xtmpd1 ymm8
+%define xtmpd2 ymm9
+
+align 16
+global gf_2vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_mad_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_mad_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft1_lo, xgft1_lo, xgft1_lo, 0x00 ; swapped to lo | lo
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft2_lo, xgft2_lo, xgft2_lo, 0x00 ; swapped to lo | lo
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest1, [dest1]
+
+ XLDR xtmpd1, [dest1+len] ;backup the last 16 bytes in dest
+ XLDR xtmpd2, [dest2+len] ;backup the last 16 bytes in dest
+
+.loop32:
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+.loop32_overlap:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xtmph1, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ vpshufb xtmph2, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-32
+ vmovdqa xd1, xtmpd1 ;Restore xd1
+ vmovdqa xd2, xtmpd2 ;Restore xd2
+ jmp .loop32_overlap ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_2vect_mad_avx2, 04, 01, 0205
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx512.asm
new file mode 100644
index 0000000000..acb67e4334
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_avx512.asm
@@ -0,0 +1,235 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_mad_avx512(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define stack_size 16*9 + 3*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ vmovdqa [rsp+16*3],xmm9
+ vmovdqa [rsp+16*4],xmm10
+ vmovdqa [rsp+16*5],xmm11
+ vmovdqa [rsp+16*6],xmm12
+ vmovdqa [rsp+16*7],xmm13
+ vmovdqa [rsp+16*8],xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r15, 9*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ vmovdqa xmm9, [rsp+16*3]
+ vmovdqa xmm10, [rsp+16*4]
+ vmovdqa xmm11, [rsp+16*5]
+ vmovdqa xmm12, [rsp+16*6]
+ vmovdqa xmm13, [rsp+16*7]
+ vmovdqa xmm14, [rsp+16*8]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r15, [rsp + 9*16 + 1*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+
+%define PS 8
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+%define dest2 tmp2
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+default rel
+[bits 64]
+section .text
+
+%define x0 zmm0
+%define xtmpa zmm1
+%define xtmph1 zmm2
+%define xtmpl1 zmm3
+%define xtmph2 zmm4
+%define xtmpl2 zmm5
+%define xd1 zmm6
+%define xd2 zmm7
+%define xtmpd1 zmm8
+%define xtmpd2 zmm9
+%define xgft1_hi zmm10
+%define xgft1_lo zmm11
+%define xgft1_loy ymm11
+%define xgft2_hi zmm12
+%define xgft2_lo zmm13
+%define xgft2_loy ymm13
+%define xmask0f zmm14
+
+align 16
+global gf_2vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_mad_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_mad_avx512)
+%endif
+
+ FUNC_SAVE
+ sub len, 64
+ jl .return_fail
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+ vmovdqu xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ vmovdqu xgft2_loy, [tmp+vec] ;Load array Bx{00}..{0f}, Bx{00}..{f0}
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x55
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x00
+ vshufi64x2 xgft2_hi, xgft2_lo, xgft2_lo, 0x55
+ vshufi64x2 xgft2_lo, xgft2_lo, xgft2_lo, 0x00
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest1, [dest1]
+ mov tmp, -1
+ kmovq k1, tmp
+
+.loop64:
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xtmph1 {k1}{z}, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1 {k1}{z}, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxorq xd1, xd1, xtmph1 ;xd1 += partial
+
+ vpshufb xtmph2 {k1}{z}, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2 {k1}{z}, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxorq xd2, xd2, xtmph2 ;xd2 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, (1 << 63)
+ lea tmp, [len + 64 - 1]
+ and tmp, 63
+ sarx pos, pos, tmp
+ kmovq k1, pos
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_2vect_mad_avx512
+no_gf_2vect_mad_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_2vect_mad_sse.asm b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_sse.asm
new file mode 100644
index 0000000000..5bf380df14
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_2vect_mad_sse.asm
@@ -0,0 +1,244 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_2vect_mad_sse(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*9 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r15, 9*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r15, [rsp + 9*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_2vect_mad_sse(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp2
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm14
+%define xgft1_lo xmm13
+%define xgft1_hi xmm12
+%define xgft2_lo xmm11
+%define xgft2_hi xmm10
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xd1 xmm6
+%define xd2 xmm7
+%define xtmpd1 xmm8
+%define xtmpd2 xmm9
+
+
+align 16
+global gf_2vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_2vect_mad_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_2vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_2vect_mad_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+ movdqu xgft1_lo,[tmp] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xgft2_hi, [tmp+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ mov dest2, [dest1+PS]
+ mov dest1, [dest1]
+
+ XLDR xtmpd1, [dest1+len] ;backup the last 16 bytes in dest
+ XLDR xtmpd2, [dest2+len] ;backup the last 16 bytes in dest
+
+.loop16:
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+.loop16_overlap:
+ XLDR x0, [src+pos] ;Get next source vector
+ movdqa xtmph1, xgft1_hi ;Reload const array registers
+ movdqa xtmpl1, xgft1_lo
+ movdqa xtmph2, xgft2_hi ;Reload const array registers
+ movdqa xtmpl2, xgft2_lo
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pxor xd1, xtmph1
+
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pxor xd2, xtmph2
+
+ XSTR [dest1+pos], xd1 ;Store result
+ XSTR [dest2+pos], xd2 ;Store result
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ movdqa xd1, xtmpd1 ;Restore xd1
+ movdqa xd2, xtmpd2 ;Restore xd2
+ jmp .loop16_overlap ;Do one more overlap pass
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f:
+ dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_2vect_mad_sse, 00, 01, 0203
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx.asm
new file mode 100644
index 0000000000..a2619507b7
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx.asm
@@ -0,0 +1,382 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_dot_prod_avx(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 6*16 + 5*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_reg r12, 6*16 + 0*8
+ save_reg r13, 6*16 + 1*8
+ save_reg r14, 6*16 + 2*8
+ save_reg r15, 6*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ mov r12, [rsp + 6*16 + 0*8]
+ mov r13, [rsp + 6*16 + 1*8]
+ mov r14, [rsp + 6*16 + 2*8]
+ mov r15, [rsp + 6*16 + 3*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; var1
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define arg5 trans2
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp3_m var(0)
+ %define tmp4 trans2
+ %define tmp4_m var(1)
+ %define return eax
+ %macro SLDR 2 ;; stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*2 ;2 local variables
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*2 ;2 local variables
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp3_m
+ %define dest3_m tmp4_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ; 64-bit code
+ default rel
+ [bits 64]
+%endif
+
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f xmm11
+ %define xgft1_lo xmm10
+ %define xgft1_hi xmm9
+ %define xgft2_lo xmm8
+ %define xgft2_hi xmm7
+ %define xgft3_lo xmm6
+ %define xgft3_hi xmm5
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+ %define xp3 xmm4
+%else
+ %define xmask0f xmm7
+ %define xgft1_lo xmm6
+ %define xgft1_hi xmm5
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+ %define xgft3_lo xgft1_lo
+ %define xgft3_hi xgft1_hi
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+ %define xp3 xmm4
+%endif
+
+align 16
+global gf_3vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_dot_prod_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_dot_prod_avx)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest3, [dest1+2*PS]
+ SSTR dest3_m, dest3
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop16:
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ %ifidn PS,8 ; 64-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+vec*(64/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ vmovdqu xgft3_hi, [tmp+vec*(64/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ %endif
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ sal vec, 1
+ vmovdqu xgft3_lo, [tmp+vec*(32/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ vmovdqu xgft3_hi, [tmp+vec*(32/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ sar vec, 1
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ SLDR dest3, dest3_m
+ XSTR [dest3+pos], xp3
+
+ SLDR len, len_m
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_3vect_dot_prod_avx, 02, 05, 0192
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx2.asm
new file mode 100644
index 0000000000..26b6b82e21
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx2.asm
@@ -0,0 +1,402 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_dot_prod_avx2(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 6*16 + 5*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ vmovdqa [rsp + 3*16], xmm9
+ vmovdqa [rsp + 4*16], xmm10
+ vmovdqa [rsp + 5*16], xmm11
+ save_reg r12, 6*16 + 0*8
+ save_reg r13, 6*16 + 1*8
+ save_reg r14, 6*16 + 2*8
+ save_reg r15, 6*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ mov r12, [rsp + 6*16 + 0*8]
+ mov r13, [rsp + 6*16 + 1*8]
+ mov r14, [rsp + 6*16 + 2*8]
+ mov r15, [rsp + 6*16 + 3*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; var1
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define arg5 trans2
+ %define tmp edx
+ %define tmp.w edx
+ %define tmp.b dl
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp3_m var(0)
+ %define tmp4 trans2
+ %define tmp4_m var(1)
+ %define return eax
+ %macro SLDR 2 ;stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*2 ;2 local variables
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*2 ;2 local variables
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define pos return
+
+%ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp3_m
+ %define dest3_m tmp4_m
+%endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ;64-bit code
+ default rel
+ [bits 64]
+%endif
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f ymm11
+ %define xmask0fx xmm11
+ %define xgft1_lo ymm10
+ %define xgft1_hi ymm9
+ %define xgft2_lo ymm8
+ %define xgft2_hi ymm7
+ %define xgft3_lo ymm6
+ %define xgft3_hi ymm5
+
+ %define x0 ymm0
+ %define xtmpa ymm1
+ %define xp1 ymm2
+ %define xp2 ymm3
+ %define xp3 ymm4
+%else
+ %define xmask0f ymm7
+ %define xmask0fx xmm7
+ %define xgft1_lo ymm6
+ %define xgft1_hi ymm5
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+ %define xgft3_lo xgft1_lo
+ %define xgft3_hi xgft1_hi
+
+ %define x0 ymm0
+ %define xtmpa ymm1
+ %define xp1 ymm2
+ %define xp2 ymm3
+ %define xp3 ymm4
+
+%endif
+
+align 16
+global gf_3vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_dot_prod_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_dot_prod_avx2)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 32
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest3, [dest1+2*PS]
+ SSTR dest3_m, dest3
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop32:
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft1_lo, xgft1_lo, xgft1_lo, 0x00 ; swapped to lo | lo
+ %ifidn PS,8 ; 64-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft2_lo, xgft2_lo, xgft2_lo, 0x00 ; swapped to lo | lo
+
+ vmovdqu xgft3_lo, [tmp+vec*(64/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vperm2i128 xgft3_hi, xgft3_lo, xgft3_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft3_lo, xgft3_lo, xgft3_lo, 0x00 ; swapped to lo | lo
+
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft2_lo, xgft2_lo, xgft2_lo, 0x00 ; swapped to lo | lo
+ %endif
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ sal vec, 1
+ vmovdqu xgft3_lo, [tmp+vec*(32/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vperm2i128 xgft3_hi, xgft3_lo, xgft3_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft3_lo, xgft3_lo, xgft3_lo, 0x00 ; swapped to lo | lo
+ sar vec, 1
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ SLDR dest3, dest3_m
+ XSTR [dest3+pos], xp3
+
+ SLDR len, len_m
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop32 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_3vect_dot_prod_avx2, 04, 05, 0197
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx512.asm
new file mode 100644
index 0000000000..16a90eb2af
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_avx512.asm
@@ -0,0 +1,275 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_dot_prod_avx512(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 9*16 + 5*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ vmovdqa [rsp + 3*16], xmm9
+ vmovdqa [rsp + 4*16], xmm10
+ vmovdqa [rsp + 5*16], xmm11
+ vmovdqa [rsp + 6*16], xmm12
+ vmovdqa [rsp + 7*16], xmm13
+ vmovdqa [rsp + 8*16], xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r13, 9*16 + 1*8
+ save_reg r14, 9*16 + 2*8
+ save_reg r15, 9*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r13, [rsp + 9*16 + 1*8]
+ mov r14, [rsp + 9*16 + 2*8]
+ mov r15, [rsp + 9*16 + 3*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%define xmask0f zmm11
+%define xgft1_lo zmm10
+%define xgft1_loy ymm10
+%define xgft1_hi zmm9
+%define xgft2_lo zmm8
+%define xgft2_loy ymm8
+%define xgft2_hi zmm7
+%define xgft3_lo zmm6
+%define xgft3_loy ymm6
+%define xgft3_hi zmm5
+
+%define x0 zmm0
+%define xtmpa zmm1
+%define xp1 zmm2
+%define xp2 zmm3
+%define xp3 zmm4
+
+default rel
+[bits 64]
+
+section .text
+
+align 16
+global gf_3vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_dot_prod_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_dot_prod_avx512)
+%endif
+
+ FUNC_SAVE
+ sub len, 64
+ jl .return_fail
+
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest2, [dest1+PS]
+ mov dest3, [dest1+2*PS]
+ mov dest1, [dest1]
+
+.loop64:
+ vpxorq xp1, xp1, xp1
+ vpxorq xp2, xp2, xp2
+ vpxorq xp3, xp3, xp3
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add vec_i, PS
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vmovdqu8 xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ vmovdqu8 xgft2_loy, [tmp+vec*(32/PS)] ;Load array Bx{00}..{0f}, Bx{00}..{f0}
+ vmovdqu8 xgft3_loy, [tmp+vec*(64/PS)] ;Load array Cx{00}..{0f}, Cx{00}..{f0}
+ add tmp, 32
+
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x55
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x00
+ vshufi64x2 xgft2_hi, xgft2_lo, xgft2_lo, 0x55
+ vshufi64x2 xgft2_lo, xgft2_lo, xgft2_lo, 0x00
+
+ vpshufb xgft1_hi, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft1_hi, xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxorq xp1, xp1, xgft1_hi ;xp1 += partial
+
+ vpshufb xgft2_hi, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft2_hi, xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxorq xp2, xp2, xgft2_hi ;xp2 += partial
+
+ vshufi64x2 xgft3_hi, xgft3_lo, xgft3_lo, 0x55
+ vshufi64x2 xgft3_lo, xgft3_lo, xgft3_lo, 0x00
+
+ vpshufb xgft3_hi, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft3_hi, xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxorq xp3, xp3, xgft3_hi ;xp3 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [dest3+pos], xp3
+
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_3vect_dot_prod_avx512
+no_gf_3vect_dot_prod_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_sse.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_sse.asm
new file mode 100644
index 0000000000..582fac8481
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_dot_prod_sse.asm
@@ -0,0 +1,383 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_dot_prod_sse(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 6*16 + 5*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_reg r12, 6*16 + 0*8
+ save_reg r13, 6*16 + 1*8
+ save_reg r14, 6*16 + 2*8
+ save_reg r15, 6*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp + 0*16]
+ movdqa xmm7, [rsp + 1*16]
+ movdqa xmm8, [rsp + 2*16]
+ movdqa xmm9, [rsp + 3*16]
+ movdqa xmm10, [rsp + 4*16]
+ movdqa xmm11, [rsp + 5*16]
+ mov r12, [rsp + 6*16 + 0*8]
+ mov r13, [rsp + 6*16 + 1*8]
+ mov r14, [rsp + 6*16 + 2*8]
+ mov r15, [rsp + 6*16 + 3*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; var1
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define arg5 trans2
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp3_m var(0)
+ %define tmp4 trans2
+ %define tmp4_m var(1)
+ %define return eax
+ %macro SLDR 2 ;; stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*2 ;2 local variables
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*2 ;2 local variables
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp3_m
+ %define dest3_m tmp4_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+%ifidn PS,8 ; 64-bit code
+ default rel
+ [bits 64]
+%endif
+
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f xmm11
+ %define xgft1_lo xmm2
+ %define xgft1_hi xmm3
+ %define xgft2_lo xmm4
+ %define xgft2_hi xmm7
+ %define xgft3_lo xmm6
+ %define xgft3_hi xmm5
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm10
+ %define xp2 xmm9
+ %define xp3 xmm8
+%else
+ %define xmask0f xmm7
+ %define xgft1_lo xmm6
+ %define xgft1_hi xmm5
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+ %define xgft3_lo xgft1_lo
+ %define xgft3_hi xgft1_hi
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+ %define xp3 xmm4
+%endif
+
+align 16
+global gf_3vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_dot_prod_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_dot_prod_sse)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest3, [dest1+2*PS]
+ SSTR dest3_m, dest3
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop16:
+ pxor xp1, xp1
+ pxor xp2, xp2
+ pxor xp3, xp3
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ movdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ %ifidn PS,8 ;64-bit code
+ movdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ movdqu xgft3_lo, [tmp+vec*(64/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ movdqu xgft3_hi, [tmp+vec*(64/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ;32-bit code
+ movdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ %endif
+ pshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ pxor xp2, xgft2_hi ;xp2 += partial
+
+ %ifidn PS,4 ;32-bit code
+ sal vec, 1
+ movdqu xgft3_lo, [tmp+vec*(32/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ movdqu xgft3_hi, [tmp+vec*(32/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ sar vec, 1
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ pshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ pxor xp3, xgft3_hi ;xp3 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ SLDR dest3, dest3_m
+ XSTR [dest3+pos], xp3
+
+ SLDR len, len_m
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_3vect_dot_prod_sse, 00, 06, 0063
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx.asm
new file mode 100644
index 0000000000..7cf630558c
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx.asm
@@ -0,0 +1,292 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_mad_avx(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ vmovdqa [rsp+16*3],xmm9
+ vmovdqa [rsp+16*4],xmm10
+ vmovdqa [rsp+16*5],xmm11
+ vmovdqa [rsp+16*6],xmm12
+ vmovdqa [rsp+16*7],xmm13
+ vmovdqa [rsp+16*8],xmm14
+ vmovdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r15, 10*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ vmovdqa xmm9, [rsp+16*3]
+ vmovdqa xmm10, [rsp+16*4]
+ vmovdqa xmm11, [rsp+16*5]
+ vmovdqa xmm12, [rsp+16*6]
+ vmovdqa xmm13, [rsp+16*7]
+ vmovdqa xmm14, [rsp+16*8]
+ vmovdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r15, [rsp + 10*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_3vect_mad_avx(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 mul_array
+%define dest3 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft1_lo xmm14
+%define xgft1_hi xmm13
+%define xgft2_lo xmm12
+%define xgft2_hi xmm11
+%define xgft3_lo xmm10
+%define xgft3_hi xmm9
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xd1 xmm8
+%define xd2 xtmpl1
+%define xd3 xtmph1
+
+align 16
+global gf_3vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_mad_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_mad_avx)
+%endif
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xgft2_hi, [tmp+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xgft3_lo, [tmp+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ vmovdqu xgft3_hi, [tmp+2*vec+16]; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ XLDR xd2, [dest2+pos] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+pos] ;reuse xtmph1. Get next dest vector
+
+ ; dest2
+ vpshufb xtmph2, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ ; dest3
+ vpshufb xtmph3, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xtmpl3 ;GF add high and low partials
+ vpxor xd3, xd3, xtmph3 ;xd3 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+ XSTR [dest3+pos], xd3
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp, len ;Overlapped offset length-16
+ XLDR x0, [src+tmp] ;Get next source vector
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+tmp] ;reuse xtmph1. Get next dest vector
+
+ sub len, pos
+
+ movdqa xtmph3, [constip16] ;Load const of i + 16
+ vpinsrb xtmpl3, xtmpl3, len.w, 15
+ vpshufb xtmpl3, xtmpl3, xmask0f ;Broadcast len to all bytes
+ vpcmpgtb xtmpl3, xtmpl3, xtmph3
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xgft1_hi, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpand xgft1_hi, xgft1_hi, xtmpl3
+ vpxor xd1, xd1, xgft1_hi
+
+ ; dest2
+ vpshufb xgft2_hi, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpand xgft2_hi, xgft2_hi, xtmpl3
+ vpxor xd2, xd2, xgft2_hi
+
+ ; dest3
+ vpshufb xgft3_hi, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpand xgft3_hi, xgft3_hi, xtmpl3
+ vpxor xd3, xd3, xgft3_hi
+
+ XSTR [dest1+tmp], xd1
+ XSTR [dest2+tmp], xd2
+ XSTR [dest3+tmp], xd3
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_3vect_mad_avx, 02, 01, 0207
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx2.asm
new file mode 100644
index 0000000000..c218b4db28
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx2.asm
@@ -0,0 +1,322 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_mad_avx2(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+ %macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ vmovdqa [rsp+16*3],xmm9
+ vmovdqa [rsp+16*4],xmm10
+ vmovdqa [rsp+16*5],xmm11
+ vmovdqa [rsp+16*6],xmm12
+ vmovdqa [rsp+16*7],xmm13
+ vmovdqa [rsp+16*8],xmm14
+ vmovdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r15, 10*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ vmovdqa xmm9, [rsp+16*3]
+ vmovdqa xmm10, [rsp+16*4]
+ vmovdqa xmm11, [rsp+16*5]
+ vmovdqa xmm12, [rsp+16*6]
+ vmovdqa xmm13, [rsp+16*7]
+ vmovdqa xmm14, [rsp+16*8]
+ vmovdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r15, [rsp + 10*16 + 1*8]
+ add rsp, stack_size
+ %endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_3vect_mad_avx2(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 mul_array
+%define dest3 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm15
+%define xmask0fx xmm15
+%define xgft1_lo ymm14
+%define xgft1_hi ymm13
+%define xgft2_lo ymm12
+%define xgft3_lo ymm11
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xtmph1 ymm2
+%define xtmpl1 ymm3
+%define xtmph2 ymm4
+%define xtmpl2 ymm5
+%define xtmpl2x xmm5
+%define xtmph3 ymm6
+%define xtmpl3 ymm7
+%define xtmpl3x xmm7
+%define xd1 ymm8
+%define xd2 ymm9
+%define xd3 ymm10
+
+align 16
+global gf_3vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_mad_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_mad_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft1_lo, xgft1_lo, xgft1_lo, 0x00 ; swapped to lo | lo
+
+ vmovdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xgft3_lo, [tmp+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop32:
+ XLDR x0, [src+pos] ;Get next source vector
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+ XLDR xd3, [dest3+pos] ;Get next dest vector
+ vperm2i128 xtmph2, xgft2_lo, xgft2_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xtmpl2, xgft2_lo, xgft2_lo, 0x00 ; swapped to lo | lo
+
+ vperm2i128 xtmph3, xgft3_lo, xgft3_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xtmpl3, xgft3_lo, xgft3_lo, 0x00 ; swapped to lo | lo
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ ; dest2
+ vpshufb xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xtmph2 ;xd2 += partial
+
+ ; dest3
+ vpshufb xtmph3, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmpl3 ;GF add high and low partials
+ vpxor xd3, xtmph3 ;xd3 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+ XSTR [dest3+pos], xd3
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan32:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp.b, 0x1f
+ vpinsrb xtmpl2x, xtmpl2x, tmp.w, 0
+ vpbroadcastb xtmpl2, xtmpl2x ;Construct mask 0x1f1f1f...
+
+ mov tmp, len ;Overlapped offset length-32
+
+ XLDR x0, [src+tmp] ;Get next source vector
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+ XLDR xd3, [dest3+tmp] ;Get next dest vector
+
+ sub len, pos
+
+ vmovdqa xtmph3, [constip32] ;Load const of i + 32
+ vpinsrb xtmpl3x, xtmpl3x, len.w, 15
+ vinserti128 xtmpl3, xtmpl3, xtmpl3x, 1 ;swapped to xtmpl3x | xtmpl3x
+ vpshufb xtmpl3, xtmpl3, xtmpl2 ;Broadcast len to all bytes. xtmpl2=0x1f1f1f...
+ vpcmpgtb xtmpl3, xtmpl3, xtmph3
+
+ vperm2i128 xtmph2, xgft2_lo, xgft2_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft2_lo, xgft2_lo, xgft2_lo, 0x00 ; swapped to lo | lo
+
+ vperm2i128 xtmph3, xgft3_lo, xgft3_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft3_lo, xgft3_lo, xgft3_lo, 0x00 ; swapped to lo | lo
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpand xtmph1, xtmph1, xtmpl3
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xgft2_lo ;GF add high and low partials
+ vpand xtmph2, xtmph2, xtmpl3
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ ; dest3
+ vpshufb xtmph3, xtmph3, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xgft3_lo ;GF add high and low partials
+ vpand xtmph3, xtmph3, xtmpl3
+ vpxor xd3, xd3, xtmph3 ;xd3 += partial
+
+ XSTR [dest1+tmp], xd1
+ XSTR [dest2+tmp], xd2
+ XSTR [dest3+tmp], xd3
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 32
+constip32:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+ dq 0xe8e9eaebecedeeef, 0xe0e1e2e3e4e5e6e7
+
+;;; func core, ver, snum
+slversion gf_3vect_mad_avx2, 04, 01, 0208
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx512.asm
new file mode 100644
index 0000000000..53b3eb5afa
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_avx512.asm
@@ -0,0 +1,252 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_mad_avx512(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+ %macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ vmovdqa [rsp+16*3],xmm9
+ vmovdqa [rsp+16*4],xmm10
+ vmovdqa [rsp+16*5],xmm11
+ vmovdqa [rsp+16*6],xmm12
+ vmovdqa [rsp+16*7],xmm13
+ vmovdqa [rsp+16*8],xmm14
+ vmovdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r15, 10*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ vmovdqa xmm9, [rsp+16*3]
+ vmovdqa xmm10, [rsp+16*4]
+ vmovdqa xmm11, [rsp+16*5]
+ vmovdqa xmm12, [rsp+16*6]
+ vmovdqa xmm13, [rsp+16*7]
+ vmovdqa xmm14, [rsp+16*8]
+ vmovdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r15, [rsp + 10*16 + 1*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%define PS 8
+%define len arg0
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define dest2 mul_array
+%define dest3 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+default rel
+[bits 64]
+section .text
+
+%define x0 zmm0
+%define xtmpa zmm1
+%define xtmph1 zmm2
+%define xtmpl1 zmm3
+%define xtmph2 zmm4
+%define xtmpl2 zmm5
+%define xtmph3 zmm6
+%define xtmpl3 zmm7
+%define xgft1_hi zmm8
+%define xgft1_lo zmm9
+%define xgft1_loy ymm9
+%define xgft2_hi zmm10
+%define xgft2_lo zmm11
+%define xgft2_loy ymm11
+%define xgft3_hi zmm12
+%define xgft3_lo zmm13
+%define xgft3_loy ymm13
+%define xd1 zmm14
+%define xd2 zmm15
+%define xd3 zmm16
+%define xmask0f zmm17
+
+align 16
+global gf_3vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_mad_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_mad_avx512)
+%endif
+
+ FUNC_SAVE
+ sub len, 64
+ jl .return_fail
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+ vmovdqu xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ vmovdqu xgft2_loy, [tmp+vec] ;Load array Bx{00}..{0f}, Bx{00}..{f0}
+ vmovdqu xgft3_loy, [tmp+2*vec] ;Load array Cx{00}..{0f}, Cx{00}..{f0}
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x55
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x00
+ vshufi64x2 xgft2_hi, xgft2_lo, xgft2_lo, 0x55
+ vshufi64x2 xgft2_lo, xgft2_lo, xgft2_lo, 0x00
+ vshufi64x2 xgft3_hi, xgft3_lo, xgft3_lo, 0x55
+ vshufi64x2 xgft3_lo, xgft3_lo, xgft3_lo, 0x00
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS] ; reuse vec_i
+ mov dest1, [dest1]
+ mov tmp, -1
+ kmovq k1, tmp
+
+.loop64:
+ XLDR x0, [src+pos] ;Get next source vector
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+ XLDR xd3, [dest3+pos] ;Get next dest vector
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1 {k1}{z}, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1 {k1}{z}, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxorq xd1, xd1, xtmph1 ;xd1 += partial
+
+ ; dest2
+ vpshufb xtmph2 {k1}{z}, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2 {k1}{z}, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxorq xd2, xd2, xtmph2 ;xd2 += partial
+
+ ; dest3
+ vpshufb xtmph3 {k1}{z}, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3 {k1}{z}, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph3, xtmph3, xtmpl3 ;GF add high and low partials
+ vpxorq xd3, xd3, xtmph3 ;xd2 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+ XSTR [dest3+pos], xd3
+
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, (1 << 63)
+ lea tmp, [len + 64 - 1]
+ and tmp, 63
+ sarx pos, pos, tmp
+ kmovq k1, pos
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_3vect_mad_avx512
+no_gf_3vect_mad_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_3vect_mad_sse.asm b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_sse.asm
new file mode 100644
index 0000000000..d6dbe8f200
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_3vect_mad_sse.asm
@@ -0,0 +1,303 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_3vect_mad_sse(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r15, 10*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r15, [rsp + 10*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_3vect_mad_sse(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 mul_array
+%define dest3 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft1_lo xmm14
+%define xgft1_hi xmm13
+%define xgft2_lo xmm12
+%define xgft2_hi xmm11
+%define xgft3_lo xmm10
+%define xgft3_hi xmm9
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xd1 xmm8
+%define xd2 xtmpl1
+%define xd3 xtmph1
+
+align 16
+global gf_3vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_3vect_mad_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_3vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_3vect_mad_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5
+ lea tmp, [mul_array + vec_i]
+
+ movdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xgft2_hi, [tmp+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ movdqu xgft3_lo, [tmp+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ movdqu xgft3_hi, [tmp+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+ movdqa xtmph1, xgft1_hi ;Reload const array registers
+ movdqa xtmpl1, xgft1_lo
+ movdqa xtmph2, xgft2_hi ;Reload const array registers
+ movdqa xtmpl2, xgft2_lo
+ movdqa xtmph3, xgft3_hi ;Reload const array registers
+ movdqa xtmpl3, xgft3_lo
+
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ; dest1
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pxor xd1, xtmph1
+
+ XLDR xd2, [dest2+pos] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+pos] ;reuse xtmph1. Get next dest vector
+
+ ; dest2
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pxor xd2, xtmph2
+
+ ; dest3
+ pshufb xtmph3, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph3, xtmpl3 ;GF add high and low partials
+ pxor xd3, xtmph3
+
+ XSTR [dest1+pos], xd1 ;Store result
+ XSTR [dest2+pos], xd2 ;Store result
+ XSTR [dest3+pos], xd3 ;Store result
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp, len ;Overlapped offset length-16
+
+ XLDR x0, [src+tmp] ;Get next source vector
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+tmp] ;reuse xtmph1. Get next dest vector
+
+ sub len, pos
+
+ movdqa xtmph3, [constip16] ;Load const of i + 16
+ pinsrb xtmpl3, len.w, 15
+ pshufb xtmpl3, xmask0f ;Broadcast len to all bytes
+ pcmpgtb xtmpl3, xtmph3
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ; dest1
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pand xgft1_hi, xtmpl3
+ pxor xd1, xgft1_hi
+
+ ; dest2
+ pshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ pand xgft2_hi, xtmpl3
+ pxor xd2, xgft2_hi
+
+ ; dest3
+ pshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ pand xgft3_hi, xtmpl3
+ pxor xd3, xgft3_hi
+
+ XSTR [dest1+tmp], xd1 ;Store result
+ XSTR [dest2+tmp], xd2 ;Store result
+ XSTR [dest3+tmp], xd3 ;Store result
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f:
+ dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_3vect_mad_sse, 00, 01, 0206
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx.asm
new file mode 100644
index 0000000000..30f1e81f6b
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx.asm
@@ -0,0 +1,446 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_dot_prod_avx(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 9*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_xmm128 xmm12, 6*16
+ save_xmm128 xmm13, 7*16
+ save_xmm128 xmm14, 8*16
+ save_reg r12, 9*16 + 0*8
+ save_reg r13, 9*16 + 1*8
+ save_reg r14, 9*16 + 2*8
+ save_reg r15, 9*16 + 3*8
+ save_reg rdi, 9*16 + 4*8
+ save_reg rsi, 9*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r13, [rsp + 9*16 + 1*8]
+ mov r14, [rsp + 9*16 + 2*8]
+ mov r15, [rsp + 9*16 + 3*8]
+ mov rdi, [rsp + 9*16 + 4*8]
+ mov rsi, [rsp + 9*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; var1
+;;; var2
+;;; var3
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define arg5 trans2
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp3_m var(0)
+ %define tmp4 trans2
+ %define tmp4_m var(1)
+ %define tmp5 trans2
+ %define tmp5_m var(2)
+ %define tmp6 trans2
+ %define tmp6_m var(3)
+ %define return eax
+ %macro SLDR 2 ;stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*4 ;4 local variables
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*4 ;4 local variables
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define dest4 tmp5
+%define vskip3 tmp6
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp3_m
+ %define dest3_m tmp4_m
+ %define dest4_m tmp5_m
+ %define vskip3_m tmp6_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ; 64-bit code
+ default rel
+ [bits 64]
+%endif
+
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f xmm14
+ %define xgft1_lo xmm13
+ %define xgft1_hi xmm12
+ %define xgft2_lo xmm11
+ %define xgft2_hi xmm10
+ %define xgft3_lo xmm9
+ %define xgft3_hi xmm8
+ %define xgft4_lo xmm7
+ %define xgft4_hi xmm6
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+ %define xp3 xmm4
+ %define xp4 xmm5
+%else
+ %define xmm_trans xmm7 ;reuse xmask0f and xgft1_lo
+ %define xmask0f xmm_trans
+ %define xgft1_lo xmm_trans
+ %define xgft1_hi xmm6
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+ %define xgft3_lo xgft1_lo
+ %define xgft3_hi xgft1_hi
+ %define xgft4_lo xgft1_lo
+ %define xgft4_hi xgft1_hi
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+ %define xp3 xmm4
+ %define xp4 xmm5
+%endif
+align 16
+global gf_4vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_dot_prod_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_dot_prod_avx)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov vskip3, vec
+ imul vskip3, 96
+ SSTR vskip3_m, vskip3
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest3, [dest1+2*PS]
+ SSTR dest3_m, dest3
+ mov dest4, [dest1+3*PS]
+ SSTR dest4_m, dest4
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop16:
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ vpxor xp4, xp4
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ %ifidn PS,8 ;64-bit code
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+vec*(64/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ vmovdqu xgft3_hi, [tmp+vec*(64/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vmovdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ vmovdqu xgft4_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add tmp, 32
+ add vec_i, PS
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ %else ;32-bit code
+ XLDR x0, [ptr+pos] ;Get next source vector
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ %endif
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ;32-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ %endif
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ %ifidn PS,4 ;32-bit code
+ sal vec, 1
+ vmovdqu xgft3_lo, [tmp+vec*(32/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ vmovdqu xgft3_hi, [tmp+vec*(32/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ sar vec, 1
+ %endif
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+ %ifidn PS,4 ;32-bit code
+ SLDR vskip3, vskip3_m
+ vmovdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ vmovdqu xgft4_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ vpshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpxor xp4, xgft4_hi ;xp4 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ SLDR dest3, dest3_m
+ XSTR [dest3+pos], xp3
+ SLDR dest4, dest4_m
+ XSTR [dest4+pos], xp4
+
+ SLDR len, len_m
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_4vect_dot_prod_avx, 02, 05, 0193
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx2.asm
new file mode 100644
index 0000000000..efe2f76de9
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx2.asm
@@ -0,0 +1,466 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_dot_prod_avx2(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 9*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ vmovdqa [rsp + 3*16], xmm9
+ vmovdqa [rsp + 4*16], xmm10
+ vmovdqa [rsp + 5*16], xmm11
+ vmovdqa [rsp + 6*16], xmm12
+ vmovdqa [rsp + 7*16], xmm13
+ vmovdqa [rsp + 8*16], xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r13, 9*16 + 1*8
+ save_reg r14, 9*16 + 2*8
+ save_reg r15, 9*16 + 3*8
+ save_reg rdi, 9*16 + 4*8
+ save_reg rsi, 9*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r13, [rsp + 9*16 + 1*8]
+ mov r14, [rsp + 9*16 + 2*8]
+ mov r15, [rsp + 9*16 + 3*8]
+ mov rdi, [rsp + 9*16 + 4*8]
+ mov rsi, [rsp + 9*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; var1
+;;; var2
+;;; var3
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define arg5 trans2
+ %define tmp edx
+ %define tmp.w edx
+ %define tmp.b dl
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp3_m var(0)
+ %define tmp4 trans2
+ %define tmp4_m var(1)
+ %define tmp5 trans2
+ %define tmp5_m var(2)
+ %define tmp6 trans2
+ %define tmp6_m var(3)
+ %define return eax
+ %macro SLDR 2 ;stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*4 ;4 local variables
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*4 ;4 local variables
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define dest4 tmp5
+%define vskip3 tmp6
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp3_m
+ %define dest3_m tmp4_m
+ %define dest4_m tmp5_m
+ %define vskip3_m tmp6_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ;64-bit code
+ default rel
+ [bits 64]
+%endif
+
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f ymm14
+ %define xmask0fx xmm14
+ %define xgft1_lo ymm13
+ %define xgft1_hi ymm12
+ %define xgft2_lo ymm11
+ %define xgft2_hi ymm10
+ %define xgft3_lo ymm9
+ %define xgft3_hi ymm8
+ %define xgft4_lo ymm7
+ %define xgft4_hi ymm6
+
+ %define x0 ymm0
+ %define xtmpa ymm1
+ %define xp1 ymm2
+ %define xp2 ymm3
+ %define xp3 ymm4
+ %define xp4 ymm5
+%else
+ %define ymm_trans ymm7 ;reuse xmask0f and xgft1_hi
+ %define xmask0f ymm_trans
+ %define xmask0fx xmm7
+ %define xgft1_lo ymm6
+ %define xgft1_hi ymm_trans
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+ %define xgft3_lo xgft1_lo
+ %define xgft3_hi xgft1_hi
+ %define xgft4_lo xgft1_lo
+ %define xgft4_hi xgft1_hi
+
+ %define x0 ymm0
+ %define xtmpa ymm1
+ %define xp1 ymm2
+ %define xp2 ymm3
+ %define xp3 ymm4
+ %define xp4 ymm5
+%endif
+
+align 16
+global gf_4vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_dot_prod_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_dot_prod_avx2)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 32
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+ mov vskip3, vec
+ imul vskip3, 96
+ SSTR vskip3_m, vskip3
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest3, [dest1+2*PS]
+ SSTR dest3_m, dest3
+ mov dest4, [dest1+3*PS]
+ SSTR dest4_m, dest4
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop32:
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ vpxor xp4, xp4
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ add vec_i, PS
+ %ifidn PS,8 ;64-bit code
+ vpand xgft4_lo, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xgft4_lo, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xgft4_lo, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+vec*(64/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vmovdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft3_hi, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft4_hi, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+ add tmp, 32
+ %else ;32-bit code
+ mov cl, 0x0f ;use ecx as a temp variable
+ vpinsrb xmask0fx, xmask0fx, ecx, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ vpand xgft4_lo, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xgft4_lo, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xgft4_lo, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ %endif
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ vmovdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ %endif
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ sal vec, 1
+ vmovdqu xgft3_lo, [tmp+vec*(32/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vperm2i128 xgft3_hi, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ sar vec, 1
+ %endif
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+ %ifidn PS,4 ; 32-bit code
+ SLDR vskip3, vskip3_m
+ vmovdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ ; " DX{00}, Dx{10}, ..., Dx{f0}
+ vperm2i128 xgft4_hi, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+ add tmp, 32
+ %endif
+ vpshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpxor xp4, xgft4_hi ;xp4 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ SLDR dest3, dest3_m
+ XSTR [dest3+pos], xp3
+ SLDR dest4, dest4_m
+ XSTR [dest4+pos], xp4
+
+ SLDR len, len_m
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-32
+ jmp .loop32 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_4vect_dot_prod_avx2, 04, 05, 0198
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx512.asm
new file mode 100644
index 0000000000..c810008c85
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_avx512.asm
@@ -0,0 +1,306 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_dot_prod_avx512(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 9*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ vmovdqa [rsp + 3*16], xmm9
+ vmovdqa [rsp + 4*16], xmm10
+ vmovdqa [rsp + 5*16], xmm11
+ vmovdqa [rsp + 6*16], xmm12
+ vmovdqa [rsp + 7*16], xmm13
+ vmovdqa [rsp + 8*16], xmm14
+ save_reg r12, 9*16 + 0*8
+ save_reg r13, 9*16 + 1*8
+ save_reg r14, 9*16 + 2*8
+ save_reg r15, 9*16 + 3*8
+ save_reg rdi, 9*16 + 4*8
+ save_reg rsi, 9*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r13, [rsp + 9*16 + 1*8]
+ mov r14, [rsp + 9*16 + 2*8]
+ mov r15, [rsp + 9*16 + 3*8]
+ mov rdi, [rsp + 9*16 + 4*8]
+ mov rsi, [rsp + 9*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define dest4 tmp5
+%define vskip3 tmp6
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%define xmask0f zmm14
+%define xgft1_lo zmm13
+%define xgft1_loy ymm13
+%define xgft1_hi zmm12
+%define xgft2_lo zmm11
+%define xgft2_loy ymm11
+%define xgft2_hi zmm10
+%define xgft3_lo zmm9
+%define xgft3_loy ymm9
+%define xgft3_hi zmm8
+%define xgft4_lo zmm7
+%define xgft4_loy ymm7
+%define xgft4_hi zmm6
+
+%define x0 zmm0
+%define xtmpa zmm1
+%define xp1 zmm2
+%define xp2 zmm3
+%define xp3 zmm4
+%define xp4 zmm5
+
+default rel
+[bits 64]
+
+section .text
+
+align 16
+global gf_4vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_dot_prod_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_dot_prod_avx512)
+%endif
+
+ FUNC_SAVE
+ sub len, 64
+ jl .return_fail
+
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ mov vskip3, vec
+ imul vskip3, 96
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest2, [dest1+PS]
+ mov dest3, [dest1+2*PS]
+ mov dest4, [dest1+3*PS]
+ mov dest1, [dest1]
+
+.loop64:
+ vpxorq xp1, xp1, xp1
+ vpxorq xp2, xp2, xp2
+ vpxorq xp3, xp3, xp3
+ vpxorq xp4, xp4, xp4
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add vec_i, PS
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vmovdqu8 xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ vmovdqu8 xgft2_loy, [tmp+vec*(32/PS)] ;Load array Bx{00}..{0f}, Bx{00}..{f0}
+ vmovdqu8 xgft3_loy, [tmp+vec*(64/PS)] ;Load array Cx{00}..{0f}, Cx{00}..{f0}
+ vmovdqu8 xgft4_loy, [tmp+vskip3] ;Load array Dx{00}..{0f}, Dx{00}..{f0}
+ add tmp, 32
+
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x55
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x00
+ vshufi64x2 xgft2_hi, xgft2_lo, xgft2_lo, 0x55
+ vshufi64x2 xgft2_lo, xgft2_lo, xgft2_lo, 0x00
+
+ vpshufb xgft1_hi, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft1_hi, xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxorq xp1, xp1, xgft1_hi ;xp1 += partial
+
+ vpshufb xgft2_hi, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft2_hi, xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxorq xp2, xp2, xgft2_hi ;xp2 += partial
+
+ vshufi64x2 xgft3_hi, xgft3_lo, xgft3_lo, 0x55
+ vshufi64x2 xgft3_lo, xgft3_lo, xgft3_lo, 0x00
+ vshufi64x2 xgft4_hi, xgft4_lo, xgft4_lo, 0x55
+ vshufi64x2 xgft4_lo, xgft4_lo, xgft4_lo, 0x00
+
+ vpshufb xgft3_hi, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft3_hi, xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxorq xp3, xp3, xgft3_hi ;xp3 += partial
+
+ vpshufb xgft4_hi, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft4_hi, xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpxorq xp4, xp4, xgft4_hi ;xp4 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [dest3+pos], xp3
+ XSTR [dest4+pos], xp4
+
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_4vect_dot_prod_avx512
+no_gf_4vect_dot_prod_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_sse.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_sse.asm
new file mode 100644
index 0000000000..8a486bf7b5
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_dot_prod_sse.asm
@@ -0,0 +1,448 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_dot_prod_sse(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 9*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_xmm128 xmm12, 6*16
+ save_xmm128 xmm13, 7*16
+ save_xmm128 xmm14, 8*16
+ save_reg r12, 9*16 + 0*8
+ save_reg r13, 9*16 + 1*8
+ save_reg r14, 9*16 + 2*8
+ save_reg r15, 9*16 + 3*8
+ save_reg rdi, 9*16 + 4*8
+ save_reg rsi, 9*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp + 0*16]
+ movdqa xmm7, [rsp + 1*16]
+ movdqa xmm8, [rsp + 2*16]
+ movdqa xmm9, [rsp + 3*16]
+ movdqa xmm10, [rsp + 4*16]
+ movdqa xmm11, [rsp + 5*16]
+ movdqa xmm12, [rsp + 6*16]
+ movdqa xmm13, [rsp + 7*16]
+ movdqa xmm14, [rsp + 8*16]
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r13, [rsp + 9*16 + 1*8]
+ mov r14, [rsp + 9*16 + 2*8]
+ mov r15, [rsp + 9*16 + 3*8]
+ mov rdi, [rsp + 9*16 + 4*8]
+ mov rsi, [rsp + 9*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; var0
+;;; var1
+;;; var2
+;;; var3
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+ %define var(x) [ebp - PS - PS*x]
+
+ %define trans ecx
+ %define trans2 esi
+ %define arg0 trans ;trans and trans2 are for the variables in stack
+ %define arg0_m arg(0)
+ %define arg1 ebx
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 trans
+ %define arg3_m arg(3)
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define arg5 trans2
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 trans2
+ %define tmp3_m var(0)
+ %define tmp4 trans2
+ %define tmp4_m var(1)
+ %define tmp5 trans2
+ %define tmp5_m var(2)
+ %define tmp6 trans2
+ %define tmp6_m var(3)
+ %define return eax
+ %macro SLDR 2 ;stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ sub esp, PS*4 ;4 local variables
+ push esi
+ push edi
+ push ebx
+ mov arg1, arg(1)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ add esp, PS*4 ;4 local variables
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest2 tmp3
+%define dest3 tmp4
+%define dest4 tmp5
+%define vskip3 tmp6
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define len_m arg0_m
+ %define src_m arg3_m
+ %define dest1_m arg4_m
+ %define dest2_m tmp3_m
+ %define dest3_m tmp4_m
+ %define dest4_m tmp5_m
+ %define vskip3_m tmp6_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+%ifidn PS,8 ; 64-bit code
+ default rel
+ [bits 64]
+%endif
+
+
+section .text
+
+%ifidn PS,8 ;64-bit code
+ %define xmask0f xmm14
+ %define xgft1_lo xmm2
+ %define xgft1_hi xmm3
+ %define xgft2_lo xmm11
+ %define xgft2_hi xmm4
+ %define xgft3_lo xmm9
+ %define xgft3_hi xmm5
+ %define xgft4_lo xmm7
+ %define xgft4_hi xmm6
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm8
+ %define xp2 xmm10
+ %define xp3 xmm12
+ %define xp4 xmm13
+%else
+ %define xmm_trans xmm7 ;reuse xmask0f and xgft1_lo
+ %define xmask0f xmm_trans
+ %define xgft1_lo xmm_trans
+ %define xgft1_hi xmm6
+ %define xgft2_lo xgft1_lo
+ %define xgft2_hi xgft1_hi
+ %define xgft3_lo xgft1_lo
+ %define xgft3_hi xgft1_hi
+ %define xgft4_lo xgft1_lo
+ %define xgft4_hi xgft1_hi
+
+ %define x0 xmm0
+ %define xtmpa xmm1
+ %define xp1 xmm2
+ %define xp2 xmm3
+ %define xp3 xmm4
+ %define xp4 xmm5
+%endif
+align 16
+global gf_4vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_dot_prod_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_dot_prod_sse)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov vskip3, vec
+ imul vskip3, 96
+ SSTR vskip3_m, vskip3
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ SLDR dest1, dest1_m
+ mov dest2, [dest1+PS]
+ SSTR dest2_m, dest2
+ mov dest3, [dest1+2*PS]
+ SSTR dest3_m, dest3
+ mov dest4, [dest1+3*PS]
+ SSTR dest4_m, dest4
+ mov dest1, [dest1]
+ SSTR dest1_m, dest1
+
+.loop16:
+ pxor xp1, xp1
+ pxor xp2, xp2
+ pxor xp3, xp3
+ pxor xp4, xp4
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ SLDR src, src_m
+ mov ptr, [src+vec_i]
+
+ %ifidn PS,8 ;64-bit code
+ movdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ movdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ movdqu xgft3_lo, [tmp+vec*(64/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ movdqu xgft3_hi, [tmp+vec*(64/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ movdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ movdqu xgft4_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add tmp, 32
+ add vec_i, PS
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+ %else ;32-bit code
+ XLDR x0, [ptr+pos] ;Get next source vector
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ movdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ %endif
+
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pxor xp1, xgft1_hi ;xp1 += partial
+
+ %ifidn PS,4 ;32-bit code
+ movdqu xgft2_lo, [tmp+vec*(32/PS)] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vec*(32/PS)+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ %endif
+ pshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ pxor xp2, xgft2_hi ;xp2 += partial
+
+ %ifidn PS,4 ;32-bit code
+ sal vec, 1
+ movdqu xgft3_lo, [tmp+vec*(32/PS)] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ movdqu xgft3_hi, [tmp+vec*(32/PS)+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ sar vec, 1
+ %endif
+ pshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ pxor xp3, xgft3_hi ;xp3 += partial
+
+ %ifidn PS,4 ;32-bit code
+ SLDR vskip3, vskip3_m
+ movdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ movdqu xgft4_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+ add tmp, 32
+ add vec_i, PS
+ %endif
+ pshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ pxor xp4, xgft4_hi ;xp4 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest1, dest1_m
+ SLDR dest2, dest2_m
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ SLDR dest3, dest3_m
+ XSTR [dest3+pos], xp3
+ SLDR dest4, dest4_m
+ XSTR [dest4+pos], xp4
+
+ SLDR len, len_m
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_4vect_dot_prod_sse, 00, 06, 0064
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx.asm
new file mode 100644
index 0000000000..2d351663c3
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx.asm
@@ -0,0 +1,341 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_mad_avx(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r15, 10*16 + 2*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r15, [rsp + 10*16 + 2*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r12
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r12
+ %endmacro
+%endif
+
+;;; gf_4vect_mad_avx(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 mul_array
+%define dest3 tmp2
+%define dest4 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft3_hi xmm14
+%define xgft4_hi xmm13
+%define xgft4_lo xmm12
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xtmph4 xmm8
+%define xtmpl4 xmm9
+%define xd1 xmm10
+%define xd2 xmm11
+%define xd3 xtmph1
+%define xd4 xtmpl1
+
+align 16
+global gf_4vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_mad_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_mad_avx)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+ mov tmp, vec
+
+ sal vec_i, 5 ;Multiply by 32
+ lea tmp3, [mul_array + vec_i]
+
+ sal tmp, 6 ;Multiply by 64
+ vmovdqu xgft3_hi, [tmp3+tmp+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ sal vec, 5 ;Multiply by 32
+ add tmp, vec
+ vmovdqu xgft4_lo, [tmp3+tmp] ;Load array Dx{00}, Dx{01}, Dx{02}, ...
+ vmovdqu xgft4_hi, [tmp3+tmp+16] ; " Dx{00}, Dx{10}, Dx{20}, ... , Dx{f0}
+
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS]
+ mov dest4, [dest1+3*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+ vmovdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xtmpl1, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1
+
+ XLDR xd3, [dest3+pos] ;Reuse xtmph1, Get next dest vector
+ XLDR xd4, [dest4+pos] ;Reuse xtmpl1, Get next dest vector
+
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xtmpl2, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2
+
+ ; dest3
+ vpshufb xtmph3, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xtmpl3, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xtmpl3 ;GF add high and low partials
+ vpxor xd3, xd3, xtmph3
+
+ ; dest4
+ vpshufb xtmph4, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl4, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph4, xtmph4, xtmpl4 ;GF add high and low partials
+ vpxor xd4, xd4, xtmph4
+
+ XSTR [dest1+pos], xd1 ;Store result
+ XSTR [dest2+pos], xd2 ;Store result
+ XSTR [dest3+pos], xd3 ;Store result
+ XSTR [dest4+pos], xd4 ;Store result
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+
+ mov tmp, len ;Overlapped offset length-16
+
+ XLDR x0, [src+tmp] ;Get next source vector
+
+ vmovdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+ XLDR xtmph4, [dest3+tmp] ;Get next dest vector
+
+ sub len, pos
+
+ vmovdqa xtmpl4, [constip16] ;Load const of i + 16
+ vpinsrb xtmph3, xtmph3, len.w, 15
+ vpshufb xtmph3, xtmph3, xmask0f ;Broadcast len to all bytes
+ vpcmpgtb xtmph3, xtmph3, xtmpl4
+
+ XLDR xtmpl4, [dest4+tmp] ;Get next dest vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xtmpl1, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpand xtmph1, xtmph1, xtmph3
+ vpxor xd1, xd1, xtmph1
+
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xtmpl2, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpand xtmph2, xtmph2, xtmph3
+ vpxor xd2, xd2, xtmph2
+
+ ; dest3
+ vpshufb xgft3_hi, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xtmpl3, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_hi, xtmpl3 ;GF add high and low partials
+ vpand xgft3_hi, xgft3_hi, xtmph3
+ vpxor xtmph4, xtmph4, xgft3_hi
+
+ ; dest4
+ vpshufb xgft4_hi, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpand xgft4_hi, xgft4_hi, xtmph3
+ vpxor xtmpl4, xtmpl4, xgft4_hi
+
+ XSTR [dest1+tmp], xd1 ;Store result
+ XSTR [dest2+tmp], xd2 ;Store result
+ XSTR [dest3+tmp], xtmph4 ;Store result
+ XSTR [dest4+tmp], xtmpl4 ;Store result
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_4vect_mad_avx, 02, 01, 020a
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx2.asm
new file mode 100644
index 0000000000..9ec431ff27
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx2.asm
@@ -0,0 +1,347 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_mad_avx2(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r15, 10*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r15, [rsp + 10*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+
+;;; gf_4vect_mad_avx2(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 mul_array
+%define dest3 vec
+%define dest4 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm15
+%define xmask0fx xmm15
+%define xgft1_lo ymm14
+%define xgft2_lo ymm13
+%define xgft3_lo ymm12
+%define xgft4_lo ymm11
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xtmpl ymm2
+%define xtmplx xmm2
+%define xtmph1 ymm3
+%define xtmph1x xmm3
+%define xtmph2 ymm4
+%define xtmph3 ymm5
+%define xtmph4 ymm6
+%define xd1 ymm7
+%define xd2 ymm8
+%define xd3 ymm9
+%define xd4 ymm10
+
+align 16
+global gf_4vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_mad_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_mad_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5 ;Multiply by 32
+ lea tmp, [mul_array + vec_i]
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xgft3_lo, [tmp+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ add tmp, vec
+ vmovdqu xgft4_lo, [tmp+2*vec] ;Load array Dx{00}, Dx{01}, Dx{02}, ...
+ ; " Dx{00}, Dx{10}, Dx{20}, ... , Dx{f0}
+
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS] ; reuse vec
+ mov dest4, [dest1+3*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop32:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+ XLDR xd3, [dest3+pos] ;Get next dest vector
+ XLDR xd4, [dest4+pos] ;reuse xtmpl1. Get next dest vector
+
+ vpand xtmpl, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vperm2i128 xtmpa, xtmpl, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xtmpl, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vperm2i128 xtmph1, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph2, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph3, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph4, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ ; dest3
+ vpshufb xtmph3, xtmph3, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xtmpl ;GF add high and low partials
+ vpxor xd3, xd3, xtmph3 ;xd3 += partial
+
+ ; dest4
+ vpshufb xtmph4, xtmph4, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph4, xtmph4, xtmpl ;GF add high and low partials
+ vpxor xd4, xd4, xtmph4 ;xd4 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+ XSTR [dest3+pos], xd3
+ XSTR [dest4+pos], xd4
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan32:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp.b, 0x1f
+ vpinsrb xtmph1x, xtmph1x, tmp.w, 0
+ vpbroadcastb xtmph1, xtmph1x ;Construct mask 0x1f1f1f...
+
+ mov tmp, len ;Overlapped offset length-32
+
+ XLDR x0, [src+tmp] ;Get next source vector
+
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+ XLDR xd3, [dest3+tmp] ;Get next dest vector
+ XLDR xd4, [dest4+tmp] ;Get next dest vector
+
+ sub len, pos
+
+ vmovdqa xtmph2, [constip32] ;Load const of i + 32
+ vpinsrb xtmplx, xtmplx, len.w, 15
+ vinserti128 xtmpl, xtmpl, xtmplx, 1 ;swapped to xtmplx | xtmplx
+ vpshufb xtmpl, xtmpl, xtmph1 ;Broadcast len to all bytes. xtmph1=0x1f1f1f...
+ vpcmpgtb xtmpl, xtmpl, xtmph2
+
+ vpand xtmph1, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vperm2i128 xtmpa, xtmph1, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xtmph1, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vperm2i128 xtmph1, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph2, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph3, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph4, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xgft1_lo ;GF add high and low partials
+ vpand xtmph1, xtmph1, xtmpl
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xgft2_lo ;GF add high and low partials
+ vpand xtmph2, xtmph2, xtmpl
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ ; dest3
+ vpshufb xtmph3, xtmph3, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xgft3_lo ;GF add high and low partials
+ vpand xtmph3, xtmph3, xtmpl
+ vpxor xd3, xd3, xtmph3 ;xd3 += partial
+
+ ; dest4
+ vpshufb xtmph4, xtmph4, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph4, xtmph4, xgft4_lo ;GF add high and low partials
+ vpand xtmph4, xtmph4, xtmpl
+ vpxor xd4, xd4, xtmph4 ;xd4 += partial
+
+ XSTR [dest1+tmp], xd1
+ XSTR [dest2+tmp], xd2
+ XSTR [dest3+tmp], xd3
+ XSTR [dest4+tmp], xd4
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+align 32
+constip32:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+ dq 0xe8e9eaebecedeeef, 0xe0e1e2e3e4e5e6e7
+
+;;; func core, ver, snum
+slversion gf_4vect_mad_avx2, 04, 01, 020b
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx512.asm
new file mode 100644
index 0000000000..7a5866fdf0
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_avx512.asm
@@ -0,0 +1,272 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_mad_avx512(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define return rax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r15, 10*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r15, [rsp + 10*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+%endif
+
+%define PS 8
+%define len arg0
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define dest2 mul_array
+%define dest3 vec
+%define dest4 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+default rel
+[bits 64]
+section .text
+
+%define x0 zmm0
+%define xtmpa zmm1
+%define xtmpl1 zmm2
+%define xtmph1 zmm3
+%define xtmph2 zmm4
+%define xtmph3 zmm5
+%define xtmph4 zmm6
+%define xgft1_hi zmm7
+%define xgft1_lo zmm8
+%define xgft1_loy ymm8
+%define xgft2_hi zmm9
+%define xgft2_lo zmm10
+%define xgft2_loy ymm10
+%define xgft3_hi zmm11
+%define xgft3_lo zmm12
+%define xgft3_loy ymm12
+%define xgft4_hi zmm13
+%define xgft4_lo zmm14
+%define xgft4_loy ymm14
+%define xd1 zmm15
+%define xd2 zmm16
+%define xd3 zmm17
+%define xd4 zmm18
+%define xmask0f zmm19
+%define xtmpl2 zmm20
+%define xtmpl3 zmm21
+%define xtmpl4 zmm22
+%define xtmpl5 zmm23
+
+align 16
+global gf_4vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_mad_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_mad_avx512)
+%endif
+
+ FUNC_SAVE
+ sub len, 64
+ jl .return_fail
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5 ;Multiply by 32
+ lea tmp, [mul_array + vec_i]
+ vmovdqu xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ vmovdqu xgft2_loy, [tmp+vec] ;Load array Bx{00}..{0f}, Bx{00}..{f0}
+ vmovdqu xgft3_loy, [tmp+2*vec] ;Load array Cx{00}..{0f}, Cx{00}..{f0}
+ add tmp, vec
+ vmovdqu xgft4_loy, [tmp+2*vec] ;Load array Dx{00}..{0f}, Dx{00}..{f0}
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x55
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x00
+ vshufi64x2 xgft2_hi, xgft2_lo, xgft2_lo, 0x55
+ vshufi64x2 xgft2_lo, xgft2_lo, xgft2_lo, 0x00
+ vshufi64x2 xgft3_hi, xgft3_lo, xgft3_lo, 0x55
+ vshufi64x2 xgft3_lo, xgft3_lo, xgft3_lo, 0x00
+ vshufi64x2 xgft4_hi, xgft4_lo, xgft4_lo, 0x55
+ vshufi64x2 xgft4_lo, xgft4_lo, xgft4_lo, 0x00
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS] ; reuse vec
+ mov dest4, [dest1+3*PS] ; reuse vec_i
+ mov dest1, [dest1]
+ mov tmp, -1
+ kmovq k1, tmp
+
+.loop64:
+ XLDR x0, [src+pos] ;Get next source vector
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+ XLDR xd3, [dest3+pos] ;Get next dest vector
+ XLDR xd4, [dest4+pos] ;reuse xtmpl1. Get next dest vector
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1 {k1}{z}, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1 {k1}{z}, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxorq xd1, xd1, xtmph1 ;xd1 += partial
+
+ ; dest2
+ vpshufb xtmph2 {k1}{z}, xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2 {k1}{z}, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxorq xd2, xd2, xtmph2 ;xd2 += partial
+
+ ; dest3
+ vpshufb xtmph3 {k1}{z}, xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3 {k1}{z}, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph3, xtmph3, xtmpl3 ;GF add high and low partials
+ vpxorq xd3, xd3, xtmph3 ;xd2 += partial
+
+ ; dest4
+ vpshufb xtmph4 {k1}{z}, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl4 {k1}{z}, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph4, xtmph4, xtmpl4 ;GF add high and low partials
+ vpxorq xd4, xd4, xtmph4 ;xd2 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+ XSTR [dest3+pos], xd3
+ XSTR [dest4+pos], xd4
+
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, (1 << 63)
+ lea tmp, [len + 64 - 1]
+ and tmp, 63
+ sarx pos, pos, tmp
+ kmovq k1, pos
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_4vect_mad_avx512
+no_gf_4vect_mad_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_4vect_mad_sse.asm b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_sse.asm
new file mode 100644
index 0000000000..32b6cda183
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_4vect_mad_sse.asm
@@ -0,0 +1,347 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_4vect_mad_sse(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r15, 10*16 + 2*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r15, [rsp + 10*16 + 2*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r12
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r12
+ %endmacro
+%endif
+
+;;; gf_4vect_mad_sse(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 mul_array
+%define dest3 tmp2
+%define dest4 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft3_hi xmm14
+%define xgft4_hi xmm13
+%define xgft4_lo xmm12
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xtmph4 xmm8
+%define xtmpl4 xmm9
+%define xd1 xmm10
+%define xd2 xmm11
+%define xd3 xtmph1
+%define xd4 xtmpl1
+
+align 16
+global gf_4vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_4vect_mad_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_4vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_4vect_mad_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov tmp, vec
+
+ sal vec_i, 5 ;Multiply by 32
+ lea tmp3, [mul_array + vec_i]
+
+ sal tmp, 6 ;Multiply by 64
+
+ movdqu xgft3_hi, [tmp3+tmp+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ sal vec, 5 ;Multiply by 32
+ add tmp, vec
+ movdqu xgft4_lo, [tmp3+tmp] ;Load array Dx{00}, Dx{01}, Dx{02}, ...
+ movdqu xgft4_hi, [tmp3+tmp+16] ; " Dx{00}, Dx{10}, Dx{20}, ... , Dx{f0}
+
+ mov dest2, [dest1+PS] ; reuse mul_array
+ mov dest3, [dest1+2*PS]
+ mov dest4, [dest1+3*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+ movdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ movdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+
+ movdqa xtmph3, xgft3_hi
+ movdqa xtmpl4, xgft4_lo
+ movdqa xtmph4, xgft4_hi
+
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ; dest1
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pxor xd1, xtmph1
+
+ XLDR xd3, [dest3+pos] ;Reuse xtmph1, Get next dest vector
+ XLDR xd4, [dest4+pos] ;Reuse xtmpl1, Get next dest vector
+
+ ; dest2
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pxor xd2, xtmph2
+
+ ; dest3
+ pshufb xtmph3, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph3, xtmpl3 ;GF add high and low partials
+ pxor xd3, xtmph3
+
+ ; dest4
+ pshufb xtmph4, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl4, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph4, xtmpl4 ;GF add high and low partials
+ pxor xd4, xtmph4
+
+ XSTR [dest1+pos], xd1 ;Store result
+ XSTR [dest2+pos], xd2 ;Store result
+ XSTR [dest3+pos], xd3 ;Store result
+ XSTR [dest4+pos], xd4 ;Store result
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp, len ;Overlapped offset length-16
+
+ XLDR x0, [src+tmp] ;Get next source vector
+
+ movdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ movdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+ XLDR xtmph4, [dest3+tmp] ;Reuse xtmph1. Get next dest vector
+
+ sub len, pos
+
+ movdqa xtmpl4, [constip16] ;Load const of i + 16
+ pinsrb xtmph3, len.w, 15
+ pshufb xtmph3, xmask0f ;Broadcast len to all bytes
+ pcmpgtb xtmph3, xtmpl4
+
+ XLDR xtmpl4, [dest4+tmp] ;Get next dest vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ; dest1
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pand xtmph1, xtmph3
+ pxor xd1, xtmph1
+
+ ; dest2
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pand xtmph2, xtmph3
+ pxor xd2, xtmph2
+
+ ; dest3
+ pshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xgft3_hi, xtmpl3 ;GF add high and low partials
+ pand xgft3_hi, xtmph3
+ pxor xtmph4, xgft3_hi
+
+ ; dest4
+ pshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ pand xgft4_hi, xtmph3
+ pxor xtmpl4, xgft4_hi
+
+ XSTR [dest1+tmp], xd1 ;Store result
+ XSTR [dest2+tmp], xd2 ;Store result
+ XSTR [dest3+tmp], xtmph4 ;Store result
+ XSTR [dest4+tmp], xtmpl4 ;Store result
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f:
+ dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_4vect_mad_sse, 00, 01, 0209
diff --git a/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx.asm b/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx.asm
new file mode 100644
index 0000000000..1d8cccf70b
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx.asm
@@ -0,0 +1,308 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_5vect_dot_prod_avx(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 10*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_xmm128 xmm12, 6*16
+ save_xmm128 xmm13, 7*16
+ save_xmm128 xmm14, 8*16
+ save_xmm128 xmm15, 9*16
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ save_reg rsi, 10*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ vmovdqa xmm15, [rsp + 9*16]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ mov rsi, [rsp + 10*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest1 tmp3
+%define dest2 tmp4
+%define vskip1 tmp5
+%define vskip3 tmp6
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft1_lo xmm14
+%define xgft1_hi xmm13
+%define xgft2_lo xmm12
+%define xgft2_hi xmm11
+%define xgft3_lo xmm10
+%define xgft3_hi xmm9
+%define xgft4_lo xmm8
+%define xgft4_hi xmm7
+
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xp1 xmm2
+%define xp2 xmm3
+%define xp3 xmm4
+%define xp4 xmm5
+%define xp5 xmm6
+
+align 16
+global gf_5vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_5vect_dot_prod_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_5vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_5vect_dot_prod_avx)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov vskip1, vec
+ imul vskip1, 32
+ mov vskip3, vec
+ imul vskip3, 96
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest1, [dest]
+ mov dest2, [dest+PS]
+
+
+.loop16:
+ mov tmp, mul_array
+ xor vec_i, vec_i
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ vpxor xp4, xp4
+ vpxor xp5, xp5
+
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ add vec_i, PS
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vskip1*1] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vskip1*1+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+vskip1*2] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ vmovdqu xgft3_hi, [tmp+vskip1*2+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vmovdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ vmovdqu xgft4_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ vmovdqu xgft1_lo, [tmp+vskip1*4] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ vmovdqu xgft1_hi, [tmp+vskip1*4+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ add tmp, 32
+
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+ vpshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpxor xp4, xgft4_hi ;xp4 += partial
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp5, xgft1_hi ;xp5 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ mov tmp, [dest+2*PS]
+ mov ptr, [dest+3*PS]
+ mov vec_i, [dest+4*PS]
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [tmp+pos], xp3
+ XSTR [ptr+pos], xp4
+ XSTR [vec_i+pos], xp5
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_5vect_dot_prod_avx, 02, 04, 0194
diff --git a/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx2.asm
new file mode 100644
index 0000000000..0cdfee906e
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_avx2.asm
@@ -0,0 +1,320 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_5vect_dot_prod_avx2(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 10*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ vmovdqa [rsp + 3*16], xmm9
+ vmovdqa [rsp + 4*16], xmm10
+ vmovdqa [rsp + 5*16], xmm11
+ vmovdqa [rsp + 6*16], xmm12
+ vmovdqa [rsp + 7*16], xmm13
+ vmovdqa [rsp + 8*16], xmm14
+ vmovdqa [rsp + 9*16], xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ save_reg rsi, 10*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ vmovdqa xmm15, [rsp + 9*16]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ mov rsi, [rsp + 10*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest1 tmp3
+%define dest2 tmp4
+%define vskip1 tmp5
+%define vskip3 tmp6
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm15
+%define xmask0fx xmm15
+%define xgft1_lo ymm14
+%define xgft1_hi ymm13
+%define xgft2_lo ymm12
+%define xgft2_hi ymm11
+%define xgft3_lo ymm10
+%define xgft3_hi ymm9
+%define xgft4_lo ymm8
+%define xgft4_hi ymm7
+
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xp1 ymm2
+%define xp2 ymm3
+%define xp3 ymm4
+%define xp4 ymm5
+%define xp5 ymm6
+
+align 16
+global gf_5vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_5vect_dot_prod_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_5vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_5vect_dot_prod_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+ mov vskip1, vec
+ imul vskip1, 32
+ mov vskip3, vec
+ imul vskip3, 96
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest1, [dest]
+ mov dest2, [dest+PS]
+
+
+.loop32:
+ mov tmp, mul_array
+ xor vec_i, vec_i
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ vpxor xp4, xp4
+ vpxor xp5, xp5
+
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add vec_i, PS
+
+ vpand xgft4_lo, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xgft4_lo, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xgft4_lo, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vskip1*1] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+vskip1*2] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vmovdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft3_hi, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft4_hi, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ vmovdqu xgft1_lo, [tmp+vskip1*4] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ add tmp, 32
+
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+ vpshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpxor xp4, xgft4_hi ;xp4 += partial
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp5, xgft1_hi ;xp5 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ mov tmp, [dest+2*PS]
+ mov ptr, [dest+3*PS]
+ mov vec_i, [dest+4*PS]
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [tmp+pos], xp3
+ XSTR [ptr+pos], xp4
+ XSTR [vec_i+pos], xp5
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop32 ;Do one more overlap pass
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_5vect_dot_prod_avx2, 04, 04, 0199
diff --git a/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_sse.asm b/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_sse.asm
new file mode 100644
index 0000000000..577875dbb4
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_5vect_dot_prod_sse.asm
@@ -0,0 +1,309 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_5vect_dot_prod_sse(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 10*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_xmm128 xmm12, 6*16
+ save_xmm128 xmm13, 7*16
+ save_xmm128 xmm14, 8*16
+ save_xmm128 xmm15, 9*16
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ save_reg rsi, 10*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp + 0*16]
+ movdqa xmm7, [rsp + 1*16]
+ movdqa xmm8, [rsp + 2*16]
+ movdqa xmm9, [rsp + 3*16]
+ movdqa xmm10, [rsp + 4*16]
+ movdqa xmm11, [rsp + 5*16]
+ movdqa xmm12, [rsp + 6*16]
+ movdqa xmm13, [rsp + 7*16]
+ movdqa xmm14, [rsp + 8*16]
+ movdqa xmm15, [rsp + 9*16]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ mov rsi, [rsp + 10*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest1 tmp3
+%define dest2 tmp4
+%define vskip1 tmp5
+%define vskip3 tmp6
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft1_lo xmm2
+%define xgft1_hi xmm3
+%define xgft2_lo xmm4
+%define xgft2_hi xmm5
+%define xgft3_lo xmm10
+%define xgft3_hi xmm6
+%define xgft4_lo xmm8
+%define xgft4_hi xmm7
+
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xp1 xmm9
+%define xp2 xmm11
+%define xp3 xmm12
+%define xp4 xmm13
+%define xp5 xmm14
+
+align 16
+global gf_5vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_5vect_dot_prod_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_5vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_5vect_dot_prod_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov vskip1, vec
+ imul vskip1, 32
+ mov vskip3, vec
+ imul vskip3, 96
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest1, [dest]
+ mov dest2, [dest+PS]
+
+
+.loop16:
+ mov tmp, mul_array
+ xor vec_i, vec_i
+ pxor xp1, xp1
+ pxor xp2, xp2
+ pxor xp3, xp3
+ pxor xp4, xp4
+ pxor xp5, xp5
+
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ add vec_i, PS
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ movdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ movdqu xgft2_lo, [tmp+vskip1*1] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vskip1*1+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ movdqu xgft3_lo, [tmp+vskip1*2] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ movdqu xgft3_hi, [tmp+vskip1*2+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ movdqu xgft4_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ movdqu xgft4_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pxor xp1, xgft1_hi ;xp1 += partial
+
+ pshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ pxor xp2, xgft2_hi ;xp2 += partial
+
+ movdqu xgft1_lo, [tmp+vskip1*4] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ movdqu xgft1_hi, [tmp+vskip1*4+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ add tmp, 32
+
+ pshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ pxor xp3, xgft3_hi ;xp3 += partial
+
+ pshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ pxor xp4, xgft4_hi ;xp4 += partial
+
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pxor xp5, xgft1_hi ;xp5 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ mov tmp, [dest+2*PS]
+ mov ptr, [dest+3*PS]
+ mov vec_i, [dest+4*PS]
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [tmp+pos], xp3
+ XSTR [ptr+pos], xp4
+ XSTR [vec_i+pos], xp5
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_5vect_dot_prod_sse, 00, 05, 0065
diff --git a/contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx.asm b/contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx.asm
new file mode 100644
index 0000000000..8f38a415a1
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx.asm
@@ -0,0 +1,370 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_5vect_mad_avx(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13
+ %define tmp4 r14
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 5*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r12
+ %define tmp4 r13
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+;;; gf_5vect_mad_avx(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp4
+%define dest3 mul_array
+%define dest4 tmp2
+%define dest5 vec_i
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft5_hi xmm14
+%define xgft4_lo xmm13
+%define xgft4_hi xmm12
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xtmph5 xmm8
+%define xtmpl5 xmm9
+%define xd1 xmm10
+%define xd2 xmm11
+%define xd3 xtmpl1
+%define xd4 xtmph1
+%define xd5 xtmpl2
+
+
+align 16
+global gf_5vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_5vect_mad_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_5vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_5vect_mad_avx)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov tmp, vec
+ sal vec_i, 5 ;Multiply by 32
+ lea tmp3, [mul_array + vec_i]
+ sal tmp, 6 ;Multiply by 64
+ vmovdqu xgft5_hi, [tmp3+2*tmp+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ sal vec, 5 ;Multiply by 32
+ add tmp, vec
+ vmovdqu xgft4_hi, [tmp3+tmp+16] ; " Dx{00}, Dx{10}, Dx{20}, ... , Dx{f0}
+ vmovdqu xgft4_lo, [tmp3+tmp] ;Load array Dx{00}, Dx{01}, Dx{02}, ...
+
+ mov dest3, [dest1+2*PS] ; reuse mul_array
+ mov dest4, [dest1+3*PS]
+ mov dest5, [dest1+4*PS] ; reuse vec_i
+ mov dest2, [dest1+PS]
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vmovdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xtmph3, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ vmovdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ vmovdqu xtmpl5, [tmp3+4*vec] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xtmpl1, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1
+
+ XLDR xd3, [dest3+pos] ;Reuse xtmpl1, Get next dest vector
+ XLDR xd4, [dest4+pos] ;Reuse xtmph1, Get next dest vector
+
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xtmpl2, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2
+
+ XLDR xd5, [dest5+pos] ;Reuse xtmpl2. Get next dest vector
+
+ ; dest3
+ vpshufb xtmph3, xtmph3, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xtmpl3, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xtmpl3 ;GF add high and low partials
+ vpxor xd3, xd3, xtmph3
+
+ ; dest4
+ vpshufb xtmph2, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl3 ;GF add high and low partials
+ vpxor xd4, xd4, xtmph2
+
+ ; dest5
+ vpshufb xtmph5, xgft5_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl5, xtmpl5, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph5, xtmph5, xtmpl5 ;GF add high and low partials
+ vpxor xd5, xd5, xtmph5
+
+ XSTR [dest1+pos], xd1 ;Store result into dest1
+ XSTR [dest2+pos], xd2 ;Store result into dest2
+ XSTR [dest3+pos], xd3 ;Store result into dest3
+ XSTR [dest4+pos], xd4 ;Store result into dest4
+ XSTR [dest5+pos], xd5 ;Store result into dest5
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp, len ;Overlapped offset length-16
+ XLDR x0, [src+tmp] ;Get next source vector
+
+ sub len, pos
+
+ vmovdqa xtmph1, [constip16] ;Load const of i + 16
+ vpinsrb xtmph5, len.w, 15
+ vpshufb xtmph5, xmask0f ;Broadcast len to all bytes
+ vpcmpgtb xtmph5, xtmph5, xtmph1
+
+ vmovdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xtmph3, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ vmovdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ vmovdqu xtmpl5, [tmp3+4*vec] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xtmpl1, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpand xtmph1, xtmph1, xtmph5
+ vpxor xd1, xd1, xtmph1
+
+ XLDR xd3, [dest3+tmp] ;Reuse xtmpl1, Get next dest vector
+ XLDR xd4, [dest4+tmp] ;Reuse xtmph1, Get next dest vector
+
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xtmpl2, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpand xtmph2, xtmph2, xtmph5
+ vpxor xd2, xd2, xtmph2
+
+ XLDR xd5, [dest5+tmp] ;Reuse xtmpl2. Get next dest vector
+
+ ; dest3
+ vpshufb xtmph3, xtmph3, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xtmpl3, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xtmpl3 ;GF add high and low partials
+ vpand xtmph3, xtmph3, xtmph5
+ vpxor xd3, xd3, xtmph3
+
+ ; dest4
+ vpshufb xgft4_hi, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpand xgft4_hi, xgft4_hi, xtmph5
+ vpxor xd4, xd4, xgft4_hi
+
+ ; dest5
+ vpshufb xgft5_hi, xgft5_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl5, xtmpl5, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft5_hi, xgft5_hi, xtmpl5 ;GF add high and low partials
+ vpand xgft5_hi, xgft5_hi, xtmph5
+ vpxor xd5, xd5, xgft5_hi
+
+ XSTR [dest1+tmp], xd1 ;Store result into dest1
+ XSTR [dest2+tmp], xd2 ;Store result into dest2
+ XSTR [dest3+tmp], xd3 ;Store result into dest3
+ XSTR [dest4+tmp], xd4 ;Store result into dest4
+ XSTR [dest5+tmp], xd5 ;Store result into dest5
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_5vect_mad_avx, 02, 01, 020d
diff --git a/contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx2.asm
new file mode 100644
index 0000000000..9029f9287e
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_5vect_mad_avx2.asm
@@ -0,0 +1,368 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_5vect_mad_avx2(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r15, 10*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r15, [rsp + 10*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_5vect_mad_avx2(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp2
+%define dest3 mul_array
+%define dest4 vec
+%define dest5 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm15
+%define xmask0fx xmm15
+%define xgft1_lo ymm14
+%define xgft2_lo ymm13
+%define xgft3_lo ymm12
+%define xgft4_lo ymm11
+%define xgft5_lo ymm10
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xtmpl ymm2
+%define xtmplx xmm2
+%define xtmph1 ymm3
+%define xtmph1x xmm3
+%define xtmph2 ymm4
+%define xd1 ymm5
+%define xd2 ymm6
+%define xd3 ymm7
+%define xd4 ymm8
+%define xd5 ymm9
+
+align 16
+global gf_5vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_5vect_mad_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_5vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_5vect_mad_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5 ;Multiply by 32
+ lea tmp, [mul_array + vec_i]
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+2*vec] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vmovdqu xgft5_lo, [tmp+4*vec] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ add tmp, vec
+ vmovdqu xgft4_lo, [tmp+2*vec] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ mov dest3, [dest1+2*PS] ; reuse mul_array
+ mov dest4, [dest1+3*PS] ; reuse vec
+ mov dest5, [dest1+4*PS] ; reuse vec_i
+ mov dest2, [dest1+PS]
+ mov dest1, [dest1]
+
+.loop32:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+ XLDR xd3, [dest3+pos] ;Get next dest vector
+ XLDR xd4, [dest4+pos] ;Get next dest vector
+ XLDR xd5, [dest5+pos] ;Get next dest vector
+
+ vpand xtmpl, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xtmpl, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xtmpl, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vperm2i128 xtmph1, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph2, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ vperm2i128 xtmph1, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ vperm2i128 xtmph2, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+ ; dest3
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl ;GF add high and low partials
+ vpxor xd3, xd3, xtmph1 ;xd3 += partial
+
+ vperm2i128 xtmph1, xgft5_lo, xgft5_lo, 0x01 ; swapped to hi | lo
+ ; dest4
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl ;GF add high and low partials
+ vpxor xd4, xd4, xtmph2 ;xd4 += partial
+
+ ; dest5
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl ;GF add high and low partials
+ vpxor xd5, xd5, xtmph1 ;xd5 += partial
+
+ XSTR [dest1+pos], xd1
+ XSTR [dest2+pos], xd2
+ XSTR [dest3+pos], xd3
+ XSTR [dest4+pos], xd4
+ XSTR [dest5+pos], xd5
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan32:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp.b, 0x1f
+ vpinsrb xtmph1x, xtmph1x, tmp.w, 0
+ vpbroadcastb xtmph1, xtmph1x ;Construct mask 0x1f1f1f...
+
+ mov tmp, len ;Overlapped offset length-32
+
+ XLDR x0, [src+tmp] ;Get next source vector
+
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+ XLDR xd3, [dest3+tmp] ;Get next dest vector
+ XLDR xd4, [dest4+tmp] ;Get next dest vector
+ XLDR xd5, [dest5+tmp] ;Get next dest vector
+
+ sub len, pos
+
+ vmovdqa xtmph2, [constip32] ;Load const of i + 32
+ vpinsrb xtmplx, xtmplx, len.w, 15
+ vinserti128 xtmpl, xtmpl, xtmplx, 1 ;swapped to xtmplx | xtmplx
+ vpshufb xtmpl, xtmpl, xtmph1 ;Broadcast len to all bytes. xtmph1=0x1f1f1f...
+ vpcmpgtb xtmpl, xtmpl, xtmph2
+
+ vpand xtmph1, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xtmph1, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xtmph1, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vperm2i128 xtmph1, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xtmph2, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+
+ ; dest1
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xgft1_lo ;GF add high and low partials
+ vpand xtmph1, xtmph1, xtmpl
+ vpxor xd1, xd1, xtmph1 ;xd1 += partial
+
+ vperm2i128 xtmph1, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ ; dest2
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xgft2_lo ;GF add high and low partials
+ vpand xtmph2, xtmph2, xtmpl
+ vpxor xd2, xd2, xtmph2 ;xd2 += partial
+
+ vperm2i128 xtmph2, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+ ; dest3
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xgft3_lo ;GF add high and low partials
+ vpand xtmph1, xtmph1, xtmpl
+ vpxor xd3, xd3, xtmph1 ;xd3 += partial
+
+ vperm2i128 xtmph1, xgft5_lo, xgft5_lo, 0x01 ; swapped to hi | lo
+ ; dest4
+ vpshufb xtmph2, xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xgft4_lo ;GF add high and low partials
+ vpand xtmph2, xtmph2, xtmpl
+ vpxor xd4, xd4, xtmph2 ;xd4 += partial
+
+ ; dest5
+ vpshufb xtmph1, xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xgft5_lo, xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xgft5_lo ;GF add high and low partials
+ vpand xtmph1, xtmph1, xtmpl
+ vpxor xd5, xd5, xtmph1 ;xd5 += partial
+
+ XSTR [dest1+tmp], xd1
+ XSTR [dest2+tmp], xd2
+ XSTR [dest3+tmp], xd3
+ XSTR [dest4+tmp], xd4
+ XSTR [dest5+tmp], xd5
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+align 32
+constip32:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+ dq 0xe8e9eaebecedeeef, 0xe0e1e2e3e4e5e6e7
+
+;;; func core, ver, snum
+slversion gf_5vect_mad_avx2, 04, 01, 020e
diff --git a/contrib/libs/isa-l/erasure_code/gf_5vect_mad_sse.asm b/contrib/libs/isa-l/erasure_code/gf_5vect_mad_sse.asm
new file mode 100644
index 0000000000..15c96bf4dc
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_5vect_mad_sse.asm
@@ -0,0 +1,378 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_5vect_mad_sse(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13
+ %define tmp4 r14
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 5*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r12
+ %define tmp4 r13
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+;;; gf_5vect_mad_sse(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp4
+%define dest3 mul_array
+%define dest4 tmp2
+%define dest5 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft5_hi xmm14
+%define xgft4_lo xmm13
+%define xgft4_hi xmm12
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xtmph5 xmm8
+%define xtmpl5 xmm9
+%define xd1 xmm10
+%define xd2 xmm11
+%define xd3 xtmpl1
+%define xd4 xtmph1
+%define xd5 xtmpl2
+
+
+align 16
+global gf_5vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_5vect_mad_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_5vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_5vect_mad_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov tmp, vec
+ sal vec_i, 5 ;Multiply by 32
+ lea tmp3, [mul_array + vec_i]
+ sal tmp, 6 ;Multiply by 64
+ movdqu xgft5_hi, [tmp3+2*tmp+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ sal vec, 5 ;Multiply by 32
+ add tmp, vec
+ movdqu xgft4_hi, [tmp3+tmp+16] ; " Dx{00}, Dx{10}, Dx{20}, ... , Dx{f0}
+ movdqu xgft4_lo, [tmp3+tmp] ;Load array Dx{00}, Dx{01}, Dx{02}, ...
+
+ mov dest3, [dest1+2*PS] ; reuse mul_array
+ mov dest4, [dest1+3*PS]
+ mov dest5, [dest1+4*PS] ; reuse vec_i
+ mov dest2, [dest1+PS]
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ movdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ movdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xtmph3, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ movdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ movdqu xtmpl5, [tmp3+4*vec] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ movdqa xtmph5, xgft5_hi ;Reload const array registers
+
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ; dest1
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pxor xd1, xtmph1
+
+ XLDR xd3, [dest3+pos] ;Reuse xtmpl1, Get next dest vector
+ XLDR xd4, [dest4+pos] ;Reuse xtmph1. Get next dest vector
+
+ ; dest2
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pxor xd2, xtmph2
+
+ XLDR xd5, [dest5+pos] ;Reuse xtmpl2. Get next dest vector
+
+ ; dest3
+ pshufb xtmph3, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph3, xtmpl3 ;GF add high and low partials
+ pxor xd3, xtmph3
+
+ movdqa xtmph2, xgft4_hi ;Reload const array registers
+ movdqa xtmpl3, xgft4_lo ;Reload const array registers
+
+ ; dest5
+ pshufb xtmph5, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl5, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph5, xtmpl5 ;GF add high and low partials
+ pxor xd5, xtmph5
+
+ ; dest4
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl3 ;GF add high and low partials
+ pxor xd4, xtmph2
+
+ XSTR [dest1+pos], xd1 ;Store result into dest1
+ XSTR [dest2+pos], xd2 ;Store result into dest2
+ XSTR [dest3+pos], xd3 ;Store result into dest3
+ XSTR [dest4+pos], xd4 ;Store result into dest4
+ XSTR [dest5+pos], xd5 ;Store result into dest5
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp, len ;Overlapped offset length-16
+ XLDR x0, [src+tmp] ;Get next source vector
+
+ sub len, pos
+
+ movdqa xtmpl1, [constip16] ;Load const of i + 16
+ pinsrb xtmph5, len.w, 15
+ pshufb xtmph5, xmask0f ;Broadcast len to all bytes
+ pcmpgtb xtmph5, xtmpl1
+
+ movdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ movdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xtmph3, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ movdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ movdqu xtmpl5, [tmp3+4*vec] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ; dest1
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pand xtmph1, xtmph5
+ pxor xd1, xtmph1
+
+ XLDR xd3, [dest3+tmp] ;Reuse xtmpl1, Get next dest vector
+ XLDR xd4, [dest4+tmp] ;Reuse xtmph1. Get next dest vector
+
+ ; dest2
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pand xtmph2, xtmph5
+ pxor xd2, xtmph2
+
+ XLDR xd5, [dest5+tmp] ;Reuse xtmpl2. Get next dest vector
+
+ ; dest3
+ pshufb xtmph3, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph3, xtmpl3 ;GF add high and low partials
+ pand xtmph3, xtmph5
+ pxor xd3, xtmph3
+
+ ; dest4
+ pshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ pand xgft4_hi, xtmph5
+ pxor xd4, xgft4_hi
+
+ ; dest5
+ pshufb xgft5_hi, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl5, xtmpa ;Lookup mul table of low nibble
+ pxor xgft5_hi, xtmpl5 ;GF add high and low partials
+ pand xgft5_hi, xtmph5
+ pxor xd5, xgft5_hi
+
+ XSTR [dest1+tmp], xd1 ;Store result into dest1
+ XSTR [dest2+tmp], xd2 ;Store result into dest2
+ XSTR [dest3+tmp], xd3 ;Store result into dest3
+ XSTR [dest4+tmp], xd4 ;Store result into dest4
+ XSTR [dest5+tmp], xd5 ;Store result into dest5
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f:
+ dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_5vect_mad_sse, 00, 01, 020c
diff --git a/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx.asm b/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx.asm
new file mode 100644
index 0000000000..f12798edec
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx.asm
@@ -0,0 +1,320 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_6vect_dot_prod_avx(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 10*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_xmm128 xmm12, 6*16
+ save_xmm128 xmm13, 7*16
+ save_xmm128 xmm14, 8*16
+ save_xmm128 xmm15, 9*16
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ save_reg rsi, 10*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ vmovdqa xmm15, [rsp + 9*16]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ mov rsi, [rsp + 10*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest1 tmp3
+%define dest2 tmp4
+%define vskip1 tmp5
+%define vskip3 tmp6
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft1_lo xmm14
+%define xgft1_hi xmm13
+%define xgft2_lo xmm12
+%define xgft2_hi xmm11
+%define xgft3_lo xmm10
+%define xgft3_hi xmm9
+%define x0 xmm0
+%define xtmpa xmm1
+%define xp1 xmm2
+%define xp2 xmm3
+%define xp3 xmm4
+%define xp4 xmm5
+%define xp5 xmm6
+%define xp6 xmm7
+
+align 16
+global gf_6vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_6vect_dot_prod_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_6vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_6vect_dot_prod_avx)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov vskip1, vec
+ imul vskip1, 32
+ mov vskip3, vec
+ imul vskip3, 96
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest1, [dest]
+ mov dest2, [dest+PS]
+
+
+.loop16:
+ mov tmp, mul_array
+ xor vec_i, vec_i
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ vpxor xp4, xp4
+ vpxor xp5, xp5
+ vpxor xp6, xp6
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ add vec_i, PS
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ vmovdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vskip1*1] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ vmovdqu xgft2_hi, [tmp+vskip1*1+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+vskip1*2] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ vmovdqu xgft3_hi, [tmp+vskip1*2+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ lea ptr, [vskip1 + vskip1*4] ;ptr = vskip5
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+
+ vmovdqu xgft1_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ vmovdqu xgft1_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+ vmovdqu xgft2_lo, [tmp+vskip1*4] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ vmovdqu xgft2_hi, [tmp+vskip1*4+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ vmovdqu xgft3_lo, [tmp+ptr] ;Load array Fx{00}, Fx{01}, ..., Fx{0f}
+ vmovdqu xgft3_hi, [tmp+ptr+16] ; " Fx{00}, Fx{10}, ..., Fx{f0}
+ add tmp, 32
+
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp4, xgft1_hi ;xp4 += partial
+
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp5, xgft2_hi ;xp5 += partial
+
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp6, xgft3_hi ;xp6 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+
+ mov tmp, [dest+2*PS]
+ mov ptr, [dest+3*PS]
+ mov vec_i, [dest+4*PS]
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [tmp+pos], xp3
+ mov tmp, [dest+5*PS]
+ XSTR [ptr+pos], xp4
+ XSTR [vec_i+pos], xp5
+ XSTR [tmp+pos], xp6
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_6vect_dot_prod_avx, 02, 04, 0195
diff --git a/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx2.asm
new file mode 100644
index 0000000000..d5b2543225
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_avx2.asm
@@ -0,0 +1,331 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_6vect_dot_prod_avx2(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 10*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ vmovdqa [rsp + 0*16], xmm6
+ vmovdqa [rsp + 1*16], xmm7
+ vmovdqa [rsp + 2*16], xmm8
+ vmovdqa [rsp + 3*16], xmm9
+ vmovdqa [rsp + 4*16], xmm10
+ vmovdqa [rsp + 5*16], xmm11
+ vmovdqa [rsp + 6*16], xmm12
+ vmovdqa [rsp + 7*16], xmm13
+ vmovdqa [rsp + 8*16], xmm14
+ vmovdqa [rsp + 9*16], xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ save_reg rsi, 10*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm8, [rsp + 2*16]
+ vmovdqa xmm9, [rsp + 3*16]
+ vmovdqa xmm10, [rsp + 4*16]
+ vmovdqa xmm11, [rsp + 5*16]
+ vmovdqa xmm12, [rsp + 6*16]
+ vmovdqa xmm13, [rsp + 7*16]
+ vmovdqa xmm14, [rsp + 8*16]
+ vmovdqa xmm15, [rsp + 9*16]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ mov rsi, [rsp + 10*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest1 tmp3
+%define dest2 tmp4
+%define vskip1 tmp5
+%define vskip3 tmp6
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm15
+%define xmask0fx xmm15
+%define xgft1_lo ymm14
+%define xgft1_hi ymm13
+%define xgft2_lo ymm12
+%define xgft2_hi ymm11
+%define xgft3_lo ymm10
+%define xgft3_hi ymm9
+%define x0 ymm0
+%define xtmpa ymm1
+%define xp1 ymm2
+%define xp2 ymm3
+%define xp3 ymm4
+%define xp4 ymm5
+%define xp5 ymm6
+%define xp6 ymm7
+
+align 16
+global gf_6vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_6vect_dot_prod_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_6vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_6vect_dot_prod_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+ mov vskip1, vec
+ imul vskip1, 32
+ mov vskip3, vec
+ imul vskip3, 96
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest1, [dest]
+ mov dest2, [dest+PS]
+
+
+.loop32:
+ mov tmp, mul_array
+ xor vec_i, vec_i
+ vpxor xp1, xp1
+ vpxor xp2, xp2
+ vpxor xp3, xp3
+ vpxor xp4, xp4
+ vpxor xp5, xp5
+ vpxor xp6, xp6
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add vec_i, PS
+
+ vpand xgft3_lo, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xgft3_lo, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xgft3_lo, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vskip1*1] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+vskip1*2] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ lea ptr, [vskip1 + vskip1*4] ;ptr = vskip5
+
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft3_hi, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp1, xgft1_hi ;xp1 += partial
+
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp2, xgft2_hi ;xp2 += partial
+
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp3, xgft3_hi ;xp3 += partial
+
+
+ vmovdqu xgft1_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ ; " Dx{00}, Dx{10}, ..., Dx{f0}
+ vmovdqu xgft2_lo, [tmp+vskip1*4] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ vmovdqu xgft3_lo, [tmp+ptr] ;Load array Fx{00}, Fx{01}, ..., Fx{0f}
+ ; " Fx{00}, Fx{10}, ..., Fx{f0}
+ add tmp, 32
+ vperm2i128 xgft1_hi, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft2_hi, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vperm2i128 xgft3_hi, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+
+ vpshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxor xp4, xgft1_hi ;xp4 += partial
+
+ vpshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ vpxor xp5, xgft2_hi ;xp5 += partial
+
+ vpshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ vpxor xp6, xgft3_hi ;xp6 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+
+ mov tmp, [dest+2*PS]
+ mov ptr, [dest+3*PS]
+ mov vec_i, [dest+4*PS]
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [tmp+pos], xp3
+ mov tmp, [dest+5*PS]
+ XSTR [ptr+pos], xp4
+ XSTR [vec_i+pos], xp5
+ XSTR [tmp+pos], xp6
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop32 ;Do one more overlap pass
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_6vect_dot_prod_avx2, 04, 04, 019a
diff --git a/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_sse.asm b/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_sse.asm
new file mode 100644
index 0000000000..5dea0be18e
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_6vect_dot_prod_sse.asm
@@ -0,0 +1,320 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_6vect_dot_prod_sse(len, vec, *g_tbls, **buffs, **dests);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r12 ; must be saved and restored
+ %define tmp5 r14 ; must be saved and restored
+ %define tmp6 r15 ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ push r15
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13 ; must be saved and restored
+ %define tmp4 r14 ; must be saved and restored
+ %define tmp5 rdi ; must be saved and restored
+ %define tmp6 rsi ; must be saved and restored
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 10*16 + 7*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm8, 2*16
+ save_xmm128 xmm9, 3*16
+ save_xmm128 xmm10, 4*16
+ save_xmm128 xmm11, 5*16
+ save_xmm128 xmm12, 6*16
+ save_xmm128 xmm13, 7*16
+ save_xmm128 xmm14, 8*16
+ save_xmm128 xmm15, 9*16
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ save_reg rsi, 10*16 + 5*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp + 0*16]
+ movdqa xmm7, [rsp + 1*16]
+ movdqa xmm8, [rsp + 2*16]
+ movdqa xmm9, [rsp + 3*16]
+ movdqa xmm10, [rsp + 4*16]
+ movdqa xmm11, [rsp + 5*16]
+ movdqa xmm12, [rsp + 6*16]
+ movdqa xmm13, [rsp + 7*16]
+ movdqa xmm14, [rsp + 8*16]
+ movdqa xmm15, [rsp + 9*16]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ mov rsi, [rsp + 10*16 + 5*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+%define ptr arg5
+%define vec_i tmp2
+%define dest1 tmp3
+%define dest2 tmp4
+%define vskip1 tmp5
+%define vskip3 tmp6
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft1_lo xmm2
+%define xgft1_hi xmm3
+%define xgft2_lo xmm4
+%define xgft2_hi xmm5
+%define xgft3_lo xmm6
+%define xgft3_hi xmm7
+%define x0 xmm0
+%define xtmpa xmm1
+%define xp1 xmm8
+%define xp2 xmm9
+%define xp3 xmm10
+%define xp4 xmm11
+%define xp5 xmm12
+%define xp6 xmm13
+
+align 16
+global gf_6vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_6vect_dot_prod_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_6vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_6vect_dot_prod_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov vskip1, vec
+ imul vskip1, 32
+ mov vskip3, vec
+ imul vskip3, 96
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ mov dest1, [dest]
+ mov dest2, [dest+PS]
+
+
+.loop16:
+ mov tmp, mul_array
+ xor vec_i, vec_i
+ pxor xp1, xp1
+ pxor xp2, xp2
+ pxor xp3, xp3
+ pxor xp4, xp4
+ pxor xp5, xp5
+ pxor xp6, xp6
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ add vec_i, PS
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ movdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ movdqu xgft1_hi, [tmp+16] ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ movdqu xgft2_lo, [tmp+vskip1*1] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ movdqu xgft2_hi, [tmp+vskip1*1+16] ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ movdqu xgft3_lo, [tmp+vskip1*2] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ movdqu xgft3_hi, [tmp+vskip1*2+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ lea ptr, [vskip1 + vskip1*4] ;ptr = vskip5
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pxor xp1, xgft1_hi ;xp1 += partial
+
+ pshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ pxor xp2, xgft2_hi ;xp2 += partial
+
+ pshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ pxor xp3, xgft3_hi ;xp3 += partial
+
+
+ movdqu xgft1_lo, [tmp+vskip3] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ movdqu xgft1_hi, [tmp+vskip3+16] ; " Dx{00}, Dx{10}, ..., Dx{f0}
+ movdqu xgft2_lo, [tmp+vskip1*4] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ movdqu xgft2_hi, [tmp+vskip1*4+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ movdqu xgft3_lo, [tmp+ptr] ;Load array Fx{00}, Fx{01}, ..., Fx{0f}
+ movdqu xgft3_hi, [tmp+ptr+16] ; " Fx{00}, Fx{10}, ..., Fx{f0}
+ add tmp, 32
+
+
+ pshufb xgft1_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft1_hi, xgft1_lo ;GF add high and low partials
+ pxor xp4, xgft1_hi ;xp4 += partial
+
+ pshufb xgft2_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft2_hi, xgft2_lo ;GF add high and low partials
+ pxor xp5, xgft2_hi ;xp5 += partial
+
+ pshufb xgft3_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft3_hi, xgft3_lo ;GF add high and low partials
+ pxor xp6, xgft3_hi ;xp6 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+
+ mov tmp, [dest+2*PS]
+ mov ptr, [dest+3*PS]
+ mov vec_i, [dest+4*PS]
+
+ XSTR [dest1+pos], xp1
+ XSTR [dest2+pos], xp2
+ XSTR [tmp+pos], xp3
+ mov tmp, [dest+5*PS]
+ XSTR [ptr+pos], xp4
+ XSTR [vec_i+pos], xp5
+ XSTR [tmp+pos], xp6
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_6vect_dot_prod_sse, 00, 05, 0066
diff --git a/contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx.asm b/contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx.asm
new file mode 100644
index 0000000000..c9d7e57472
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx.asm
@@ -0,0 +1,399 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_6vect_mad_avx(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r13
+ %define tmp4 r14
+ %define tmp5 rdi
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 5*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r12
+ %define tmp4 r13
+ %define tmp5 r14
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+;;; gf_6vect_mad_avx(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp4
+%define dest3 tmp2
+%define dest4 mul_array
+%define dest5 tmp5
+%define dest6 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft4_lo xmm14
+%define xgft4_hi xmm13
+%define xgft5_lo xmm12
+%define xgft5_hi xmm11
+%define xgft6_lo xmm10
+%define xgft6_hi xmm9
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xd1 xmm8
+%define xd2 xtmpl1
+%define xd3 xtmph1
+
+
+align 16
+global gf_6vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_6vect_mad_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_6vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_6vect_mad_avx)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ mov tmp, vec
+ sal vec_i, 5 ;Multiply by 32
+ lea tmp3, [mul_array + vec_i]
+ sal tmp, 6 ;Multiply by 64
+
+ sal vec, 5 ;Multiply by 32
+ lea vec_i, [tmp + vec] ;vec_i = vec*96
+ lea mul_array, [tmp + vec_i] ;mul_array = vec*160
+
+ vmovdqu xgft5_lo, [tmp3+2*tmp] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ vmovdqu xgft5_hi, [tmp3+2*tmp+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ vmovdqu xgft4_lo, [tmp3+vec_i] ;Load array Dx{00}, Dx{01}, Dx{02}, ...
+ vmovdqu xgft4_hi, [tmp3+vec_i+16] ; " Dx{00}, Dx{10}, Dx{20}, ... , Dx{f0}
+ vmovdqu xgft6_lo, [tmp3+mul_array] ;Load array Fx{00}, Fx{01}, ..., Fx{0f}
+ vmovdqu xgft6_hi, [tmp3+mul_array+16] ; " Fx{00}, Fx{10}, ..., Fx{f0}
+
+ mov dest2, [dest1+PS]
+ mov dest3, [dest1+2*PS]
+ mov dest4, [dest1+3*PS] ; reuse mul_array
+ mov dest5, [dest1+4*PS]
+ mov dest6, [dest1+5*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vmovdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ vmovdqu xtmph3, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+
+ ;dest1
+ vpshufb xtmph1, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xtmph1
+
+ XLDR xd2, [dest2+pos] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+pos] ;reuse xtmph1. Get next dest vector
+
+ ;dest2
+ vpshufb xtmph2, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xtmph2
+
+ ;dest3
+ vpshufb xtmph3, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmpl3 ;GF add high and low partials
+ vpxor xd3, xtmph3
+
+ XSTR [dest1+pos], xd1 ;Store result into dest1
+ XSTR [dest2+pos], xd2 ;Store result into dest2
+ XSTR [dest3+pos], xd3 ;Store result into dest3
+
+ ;dest4
+ XLDR xd1, [dest4+pos] ;Get next dest vector
+ vpshufb xtmph1, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl1, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph1, xtmph1, xtmpl1 ;GF add high and low partials
+ vpxor xd1, xd1, xtmph1
+
+ XLDR xd2, [dest5+pos] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest6+pos] ;reuse xtmph1. Get next dest vector
+
+ ;dest5
+ vpshufb xtmph2, xgft5_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl2, xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph2, xtmph2, xtmpl2 ;GF add high and low partials
+ vpxor xd2, xd2, xtmph2
+
+ ;dest6
+ vpshufb xtmph3, xgft6_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl3, xgft6_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph3, xtmph3, xtmpl3 ;GF add high and low partials
+ vpxor xd3, xd3, xtmph3
+
+ XSTR [dest4+pos], xd1 ;Store result into dest4
+ XSTR [dest5+pos], xd2 ;Store result into dest5
+ XSTR [dest6+pos], xd3 ;Store result into dest6
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+ ;; Overlapped offset length-16
+ mov tmp, len ;Backup len as len=rdi
+
+ XLDR x0, [src+tmp] ;Get next source vector
+ XLDR xd1, [dest4+tmp] ;Get next dest vector
+ XLDR xd2, [dest5+tmp] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest6+tmp] ;reuse xtmph1. Get next dest vector
+
+ sub len, pos
+
+ vmovdqa xtmph3, [constip16] ;Load const of i + 16
+ vpinsrb xtmpl3, len.w, 15
+ vpshufb xtmpl3, xmask0f ;Broadcast len to all bytes
+ vpcmpgtb xtmpl3, xtmpl3, xtmph3
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ ;dest4
+ vpshufb xgft4_hi, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpand xgft4_hi, xgft4_hi, xtmpl3
+ vpxor xd1, xd1, xgft4_hi
+
+ ;dest5
+ vpshufb xgft5_hi, xgft5_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft5_lo, xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft5_hi, xgft5_hi, xgft5_lo ;GF add high and low partials
+ vpand xgft5_hi, xgft5_hi, xtmpl3
+ vpxor xd2, xd2, xgft5_hi
+
+ ;dest6
+ vpshufb xgft6_hi, xgft6_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft6_lo, xgft6_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft6_hi, xgft6_hi, xgft6_lo ;GF add high and low partials
+ vpand xgft6_hi, xgft6_hi, xtmpl3
+ vpxor xd3, xd3, xgft6_hi
+
+ XSTR [dest4+tmp], xd1 ;Store result into dest4
+ XSTR [dest5+tmp], xd2 ;Store result into dest5
+ XSTR [dest6+tmp], xd3 ;Store result into dest6
+
+ vmovdqu xgft4_lo, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ vmovdqu xgft4_hi, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ vmovdqu xgft5_lo, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ vmovdqu xgft5_hi, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ vmovdqu xgft6_lo, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ vmovdqu xgft6_hi, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+tmp] ;reuse xtmph1. Get next dest3 vector
+
+ ;dest1
+ vpshufb xgft4_hi, xgft4_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft4_hi, xgft4_hi, xgft4_lo ;GF add high and low partials
+ vpand xgft4_hi, xgft4_hi, xtmpl3
+ vpxor xd1, xd1, xgft4_hi
+
+ ;dest2
+ vpshufb xgft5_hi, xgft5_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft5_lo, xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft5_hi, xgft5_hi, xgft5_lo ;GF add high and low partials
+ vpand xgft5_hi, xgft5_hi, xtmpl3
+ vpxor xd2, xd2, xgft5_hi
+
+ ;dest3
+ vpshufb xgft6_hi, xgft6_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft6_lo, xgft6_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft6_hi, xgft6_hi, xgft6_lo ;GF add high and low partials
+ vpand xgft6_hi, xgft6_hi, xtmpl3
+ vpxor xd3, xd3, xgft6_hi
+
+ XSTR [dest1+tmp], xd1 ;Store result into dest1
+ XSTR [dest2+tmp], xd2 ;Store result into dest2
+ XSTR [dest3+tmp], xd3 ;Store result into dest3
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_6vect_mad_avx, 02, 01, 0210
diff --git a/contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx2.asm
new file mode 100644
index 0000000000..2b6babcba5
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_6vect_mad_avx2.asm
@@ -0,0 +1,405 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_6vect_mad_avx2(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r13
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r15, 10*16 + 2*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r15, [rsp + 10*16 + 2*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r12
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r12
+ %endmacro
+%endif
+
+;;; gf_6vect_mad_avx2(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 tmp3
+%define dest3 tmp2
+%define dest4 mul_array
+%define dest5 vec
+%define dest6 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm15
+%define xmask0fx xmm15
+%define xgft1_lo ymm14
+%define xgft2_lo ymm13
+%define xgft3_lo ymm12
+%define xgft4_lo ymm11
+%define xgft5_lo ymm10
+%define xgft6_lo ymm9
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xtmpl ymm2
+%define xtmplx xmm2
+%define xtmph ymm3
+%define xtmphx xmm3
+%define xd1 ymm4
+%define xd2 ymm5
+%define xd3 ymm6
+%define xd4 ymm7
+%define xd5 ymm8
+%define xd6 xd1
+
+align 16
+global gf_6vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_6vect_mad_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_6vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_6vect_mad_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec_i, 5 ;Multiply by 32
+ sal vec, 5 ;Multiply by 32
+ lea tmp, [mul_array + vec_i]
+ mov vec_i, vec
+ mov mul_array, vec
+ sal vec_i, 1
+ sal mul_array, 1
+ add vec_i, vec ;vec_i=vec*96
+ add mul_array, vec_i ;vec_i=vec*160
+
+ vmovdqu xgft1_lo, [tmp] ;Load array Ax{00}, Ax{01}, ..., Ax{0f}
+ ; " Ax{00}, Ax{10}, ..., Ax{f0}
+ vmovdqu xgft2_lo, [tmp+vec] ;Load array Bx{00}, Bx{01}, ..., Bx{0f}
+ ; " Bx{00}, Bx{10}, ..., Bx{f0}
+ vmovdqu xgft3_lo, [tmp+2*vec] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ vmovdqu xgft4_lo, [tmp+vec_i] ;Load array Fx{00}, Fx{01}, ..., Fx{0f}
+ ; " Fx{00}, Fx{10}, ..., Fx{f0}
+ vmovdqu xgft5_lo, [tmp+4*vec] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ vmovdqu xgft6_lo, [tmp+mul_array] ;Load array Dx{00}, Dx{01}, ..., Dx{0f}
+ ; " Dx{00}, Dx{10}, ..., Dx{f0}
+
+ mov dest2, [dest1+PS] ; reuse tmp3
+ mov dest3, [dest1+2*PS] ; reuse tmp2
+ mov dest4, [dest1+3*PS] ; reuse mul_array
+ mov dest5, [dest1+4*PS] ; reuse vec
+ mov dest6, [dest1+5*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop32:
+ XLDR x0, [src+pos] ;Get next source vector
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+ XLDR xd2, [dest2+pos] ;Get next dest vector
+ XLDR xd3, [dest3+pos] ;Get next dest vector
+ XLDR xd4, [dest4+pos] ;Get next dest vector
+ XLDR xd5, [dest5+pos] ;Get next dest vector
+
+ vpand xtmpl, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xtmpl, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xtmpl, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ ;dest1
+ vperm2i128 xtmph, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd1, xd1, xtmph ;xd1 += partial
+
+ XSTR [dest1+pos], xd1 ;Store result into dest1
+
+ ;dest2
+ vperm2i128 xtmph, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd2, xd2, xtmph ;xd2 += partial
+
+ ;dest3
+ vperm2i128 xtmph, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd3, xd3, xtmph ;xd3 += partial
+
+ XLDR xd6, [dest6+pos] ;reuse xd1. Get next dest vector
+
+ ;dest4
+ vperm2i128 xtmph, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd4, xd4, xtmph ;xd4 += partial
+
+ ;dest5
+ vperm2i128 xtmph, xgft5_lo, xgft5_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd5, xd5, xtmph ;xd5 += partial
+
+ ;dest6
+ vperm2i128 xtmph, xgft6_lo, xgft6_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft6_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd6, xd6, xtmph ;xd6 += partial
+
+ XSTR [dest2+pos], xd2 ;Store result into dest2
+ XSTR [dest3+pos], xd3 ;Store result into dest3
+ XSTR [dest4+pos], xd4 ;Store result into dest4
+ XSTR [dest5+pos], xd5 ;Store result into dest5
+ XSTR [dest6+pos], xd6 ;Store result into dest6
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan32:
+ ;; Tail len
+ ;; Do one more overlap pass
+ mov tmp.b, 0x1f
+ vpinsrb xtmphx, xtmphx, tmp.w, 0
+ vpbroadcastb xtmph, xtmphx ;Construct mask 0x1f1f1f...
+
+ mov tmp, len ;Overlapped offset length-32
+
+ XLDR x0, [src+tmp] ;Get next source vector
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;Get next dest vector
+ XLDR xd3, [dest3+tmp] ;Get next dest vector
+ XLDR xd4, [dest4+tmp] ;Get next dest vector
+ XLDR xd5, [dest5+tmp] ;Get next dest vector
+
+ sub len, pos
+
+ vpinsrb xtmplx, xtmplx, len.w, 15
+ vinserti128 xtmpl, xtmpl, xtmplx, 1 ;swapped to xtmplx | xtmplx
+ vpshufb xtmpl, xtmpl, xtmph ;Broadcast len to all bytes. xtmph=0x1f1f1f...
+ vpcmpgtb xtmpl, xtmpl, [constip32]
+
+ vpand xtmph, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vperm2i128 xtmpa, xtmph, x0, 0x30 ;swap xtmpa from 1lo|2lo to 1lo|2hi
+ vperm2i128 x0, xtmph, x0, 0x12 ;swap x0 from 1hi|2hi to 1hi|2lo
+
+ ;dest1
+ vperm2i128 xtmph, xgft1_lo, xgft1_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xgft1_lo ;GF add high and low partials
+ vpand xtmph, xtmph, xtmpl
+ vpxor xd1, xd1, xtmph ;xd1 += partial
+
+ XSTR [dest1+tmp], xd1 ;Store result into dest1
+
+ ;dest2
+ vperm2i128 xtmph, xgft2_lo, xgft2_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xgft2_lo, xgft2_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xgft2_lo ;GF add high and low partials
+ vpand xtmph, xtmph, xtmpl
+ vpxor xd2, xd2, xtmph ;xd2 += partial
+
+ ;dest3
+ vperm2i128 xtmph, xgft3_lo, xgft3_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xgft3_lo, xgft3_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xgft3_lo ;GF add high and low partials
+ vpand xtmph, xtmph, xtmpl
+ vpxor xd3, xd3, xtmph ;xd3 += partial
+
+ XLDR xd6, [dest6+tmp] ;reuse xd1. Get next dest vector
+
+ ;dest4
+ vperm2i128 xtmph, xgft4_lo, xgft4_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xgft4_lo, xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xgft4_lo ;GF add high and low partials
+ vpand xtmph, xtmph, xtmpl
+ vpxor xd4, xd4, xtmph ;xd4 += partial
+
+ ;dest5
+ vperm2i128 xtmph, xgft5_lo, xgft5_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xgft5_lo, xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xgft5_lo ;GF add high and low partials
+ vpand xtmph, xtmph, xtmpl
+ vpxor xd5, xd5, xtmph ;xd5 += partial
+
+ ;dest6
+ vperm2i128 xtmph, xgft6_lo, xgft6_lo, 0x01 ; swapped to hi | lo
+ vpshufb xtmph, xtmph, x0 ;Lookup mul table of high nibble
+ vpshufb xgft6_lo, xgft6_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xgft6_lo ;GF add high and low partials
+ vpand xtmph, xtmph, xtmpl
+ vpxor xd6, xd6, xtmph ;xd6 += partial
+
+ XSTR [dest2+tmp], xd2 ;Store result into dest2
+ XSTR [dest3+tmp], xd3 ;Store result into dest3
+ XSTR [dest4+tmp], xd4 ;Store result into dest4
+ XSTR [dest5+tmp], xd5 ;Store result into dest5
+ XSTR [dest6+tmp], xd6 ;Store result into dest6
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+align 32
+constip32:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+ dq 0xe8e9eaebecedeeef, 0xe0e1e2e3e4e5e6e7
+
+;;; func core, ver, snum
+slversion gf_6vect_mad_avx2, 04, 01, 0211
diff --git a/contrib/libs/isa-l/erasure_code/gf_6vect_mad_sse.asm b/contrib/libs/isa-l/erasure_code/gf_6vect_mad_sse.asm
new file mode 100644
index 0000000000..8e0fc0e0a4
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_6vect_mad_sse.asm
@@ -0,0 +1,411 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_6vect_mad_sse(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%define PS 8
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp2 r10
+ %define tmp3 r13
+ %define tmp4 r14
+ %define tmp5 rdi
+ %define return rax
+ %define return.w eax
+ %define stack_size 16*10 + 5*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ movdqa [rsp+16*3],xmm9
+ movdqa [rsp+16*4],xmm10
+ movdqa [rsp+16*5],xmm11
+ movdqa [rsp+16*6],xmm12
+ movdqa [rsp+16*7],xmm13
+ movdqa [rsp+16*8],xmm14
+ movdqa [rsp+16*9],xmm15
+ save_reg r12, 10*16 + 0*8
+ save_reg r13, 10*16 + 1*8
+ save_reg r14, 10*16 + 2*8
+ save_reg r15, 10*16 + 3*8
+ save_reg rdi, 10*16 + 4*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ movdqa xmm9, [rsp+16*3]
+ movdqa xmm10, [rsp+16*4]
+ movdqa xmm11, [rsp+16*5]
+ movdqa xmm12, [rsp+16*6]
+ movdqa xmm13, [rsp+16*7]
+ movdqa xmm14, [rsp+16*8]
+ movdqa xmm15, [rsp+16*9]
+ mov r12, [rsp + 10*16 + 0*8]
+ mov r13, [rsp + 10*16 + 1*8]
+ mov r14, [rsp + 10*16 + 2*8]
+ mov r15, [rsp + 10*16 + 3*8]
+ mov rdi, [rsp + 10*16 + 4*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp2 r10
+ %define tmp3 r12
+ %define tmp4 r13
+ %define tmp5 r14
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %macro FUNC_SAVE 0
+ push r12
+ push r13
+ push r14
+ %endmacro
+ %macro FUNC_RESTORE 0
+ pop r14
+ pop r13
+ pop r12
+ %endmacro
+%endif
+
+;;; gf_6vect_mad_sse(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest1 arg5
+%define pos return
+%define pos.w return.w
+
+%define dest2 mul_array
+%define dest3 tmp2
+%define dest4 tmp4
+%define dest5 tmp5
+%define dest6 vec_i
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft4_lo xmm14
+%define xgft4_hi xmm13
+%define xgft5_lo xmm12
+%define xgft5_hi xmm11
+%define xgft6_lo xmm10
+%define xgft6_hi xmm9
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph1 xmm2
+%define xtmpl1 xmm3
+%define xtmph2 xmm4
+%define xtmpl2 xmm5
+%define xtmph3 xmm6
+%define xtmpl3 xmm7
+%define xd1 xmm8
+%define xd2 xtmpl1
+%define xd3 xtmph1
+
+
+align 16
+global gf_6vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_6vect_mad_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_6vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_6vect_mad_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+ mov tmp, vec
+ sal vec_i, 5 ;Multiply by 32
+ lea tmp3, [mul_array + vec_i]
+ sal tmp, 6 ;Multiply by 64
+
+ sal vec, 5 ;Multiply by 32
+ lea vec_i, [tmp + vec] ;vec_i = 96
+ lea mul_array, [tmp + vec_i] ;mul_array = 160
+
+ movdqu xgft5_lo, [tmp3+2*tmp] ;Load array Ex{00}, Ex{01}, ..., Ex{0f}
+ movdqu xgft5_hi, [tmp3+2*tmp+16] ; " Ex{00}, Ex{10}, ..., Ex{f0}
+ movdqu xgft4_lo, [tmp3+vec_i] ;Load array Dx{00}, Dx{01}, Dx{02}, ...
+ movdqu xgft4_hi, [tmp3+vec_i+16] ; " Dx{00}, Dx{10}, Dx{20}, ... , Dx{f0}
+ movdqu xgft6_lo, [tmp3+mul_array] ;Load array Fx{00}, Fx{01}, ..., Fx{0f}
+ movdqu xgft6_hi, [tmp3+mul_array+16] ; " Fx{00}, Fx{10}, ..., Fx{f0}
+
+ mov dest2, [dest1+PS]
+ mov dest3, [dest1+2*PS]
+ mov dest4, [dest1+3*PS] ; reuse mul_array
+ mov dest5, [dest1+4*PS]
+ mov dest6, [dest1+5*PS] ; reuse vec_i
+ mov dest1, [dest1]
+
+.loop16:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ movdqu xtmpl1, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xtmph1, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xtmpl2, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xtmph2, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ movdqu xtmpl3, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ movdqu xtmph3, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ XLDR xd1, [dest1+pos] ;Get next dest vector
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ;dest1
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pxor xd1, xtmph1
+
+ XLDR xd2, [dest2+pos] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+pos] ;reuse xtmph1. Get next dest3 vector
+
+ ;dest2
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pxor xd2, xtmph2
+
+ ;dest3
+ pshufb xtmph3, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph3, xtmpl3 ;GF add high and low partials
+ pxor xd3, xtmph3
+
+ XSTR [dest1+pos], xd1 ;Store result into dest1
+ XSTR [dest2+pos], xd2 ;Store result into dest2
+ XSTR [dest3+pos], xd3 ;Store result into dest3
+
+ movdqa xtmph1, xgft4_hi ;Reload const array registers
+ movdqa xtmpl1, xgft4_lo ;Reload const array registers
+ movdqa xtmph2, xgft5_hi ;Reload const array registers
+ movdqa xtmpl2, xgft5_lo ;Reload const array registers
+ movdqa xtmph3, xgft6_hi ;Reload const array registers
+ movdqa xtmpl3, xgft6_lo ;Reload const array registers
+
+ ;dest4
+ XLDR xd1, [dest4+pos] ;Get next dest vector
+ pshufb xtmph1, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl1, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph1, xtmpl1 ;GF add high and low partials
+ pxor xd1, xtmph1
+
+ XLDR xd2, [dest5+pos] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest6+pos] ;reuse xtmph1. Get next dest vector
+
+ ;dest5
+ pshufb xtmph2, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl2, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph2, xtmpl2 ;GF add high and low partials
+ pxor xd2, xtmph2
+
+ ;dest6
+ pshufb xtmph3, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl3, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph3, xtmpl3 ;GF add high and low partials
+ pxor xd3, xtmph3
+
+ XSTR [dest4+pos], xd1 ;Store result into dest4
+ XSTR [dest5+pos], xd2 ;Store result into dest5
+ XSTR [dest6+pos], xd3 ;Store result into dest6
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+.lessthan16:
+ ;; Tail len
+ ;; Do one more overlap pass
+ ;; Overlapped offset length-16
+ mov tmp, len ;Backup len as len=rdi
+
+ XLDR x0, [src+tmp] ;Get next source vector
+ XLDR xd1, [dest4+tmp] ;Get next dest vector
+ XLDR xd2, [dest5+tmp] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest6+tmp] ;reuse xtmph1. Get next dest vector
+
+ sub len, pos
+
+ movdqa xtmph3, [constip16] ;Load const of i + 16
+ pinsrb xtmpl3, len.w, 15
+ pshufb xtmpl3, xmask0f ;Broadcast len to all bytes
+ pcmpgtb xtmpl3, xtmph3
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ ;dest4
+ pshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ pand xgft4_hi, xtmpl3
+ pxor xd1, xgft4_hi
+
+ ;dest5
+ pshufb xgft5_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft5_hi, xgft5_lo ;GF add high and low partials
+ pand xgft5_hi, xtmpl3
+ pxor xd2, xgft5_hi
+
+ ;dest6
+ pshufb xgft6_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft6_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft6_hi, xgft6_lo ;GF add high and low partials
+ pand xgft6_hi, xtmpl3
+ pxor xd3, xgft6_hi
+
+ XSTR [dest4+tmp], xd1 ;Store result into dest4
+ XSTR [dest5+tmp], xd2 ;Store result into dest5
+ XSTR [dest6+tmp], xd3 ;Store result into dest6
+
+ movdqu xgft4_lo, [tmp3] ;Load array Ax{00}, Ax{01}, Ax{02}, ...
+ movdqu xgft4_hi, [tmp3+16] ; " Ax{00}, Ax{10}, Ax{20}, ... , Ax{f0}
+ movdqu xgft5_lo, [tmp3+vec] ;Load array Bx{00}, Bx{01}, Bx{02}, ...
+ movdqu xgft5_hi, [tmp3+vec+16] ; " Bx{00}, Bx{10}, Bx{20}, ... , Bx{f0}
+ movdqu xgft6_lo, [tmp3+2*vec] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ movdqu xgft6_hi, [tmp3+2*vec+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ XLDR xd1, [dest1+tmp] ;Get next dest vector
+ XLDR xd2, [dest2+tmp] ;reuse xtmpl1. Get next dest vector
+ XLDR xd3, [dest3+tmp] ;reuse xtmph1. Get next dest3 vector
+
+ ;dest1
+ pshufb xgft4_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft4_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft4_hi, xgft4_lo ;GF add high and low partials
+ pand xgft4_hi, xtmpl3
+ pxor xd1, xgft4_hi
+
+ ;dest2
+ pshufb xgft5_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft5_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft5_hi, xgft5_lo ;GF add high and low partials
+ pand xgft5_hi, xtmpl3
+ pxor xd2, xgft5_hi
+
+ ;dest3
+ pshufb xgft6_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft6_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft6_hi, xgft6_lo ;GF add high and low partials
+ pand xgft6_hi, xtmpl3
+ pxor xd3, xgft6_hi
+
+ XSTR [dest1+tmp], xd1 ;Store result into dest1
+ XSTR [dest2+tmp], xd2 ;Store result into dest2
+ XSTR [dest3+tmp], xd3 ;Store result into dest3
+
+.return_pass:
+ FUNC_RESTORE
+ mov return, 0
+ ret
+
+.return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+constip16:
+ dq 0xf8f9fafbfcfdfeff, 0xf0f1f2f3f4f5f6f7
+
+;;; func core, ver, snum
+slversion gf_6vect_mad_sse, 00, 01, 020f
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx.asm b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx.asm
new file mode 100644
index 0000000000..dc1eebb972
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx.asm
@@ -0,0 +1,276 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_dot_prod_avx(len, vec, *g_tbls, **buffs, *dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r9
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved and loaded
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 rdi ; must be saved and loaded
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define frame_size 2*8
+ %define arg(x) [rsp + frame_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ rex_push_reg r12
+ push_reg rdi
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop rdi
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+
+ %define trans ecx ;trans is for the variables in stack
+ %define arg0 trans
+ %define arg0_m arg(0)
+ %define arg1 trans
+ %define arg1_m arg(1)
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 ebx
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 esi
+ %define return eax
+ %macro SLDR 2 ;; stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ push esi
+ push edi
+ push ebx
+ mov arg3, arg(3)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ mov esp, ebp
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+
+%define vec_i tmp2
+%define ptr tmp3
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define vec_m arg1_m
+ %define len_m arg0_m
+ %define dest_m arg4_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ; 64-bit code
+ default rel
+ [bits 64]
+%endif
+
+section .text
+
+%define xmask0f xmm5
+%define xgft_lo xmm4
+%define xgft_hi xmm3
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xp xmm2
+
+align 16
+global gf_vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_dot_prod_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_dot_prod_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_dot_prod_avx)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+.loop16:
+ vpxor xp, xp
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+
+ mov ptr, [src+vec_i*PS]
+ vmovdqu xgft_lo, [tmp] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ vmovdqu xgft_hi, [tmp+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ add tmp, 32
+ add vec_i, 1
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xgft_hi, xgft_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft_lo, xgft_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft_hi, xgft_hi, xgft_lo ;GF add high and low partials
+ vpxor xp, xp, xgft_hi ;xp += partial
+
+ SLDR vec, vec_m
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest, dest_m
+ XSTR [dest+pos], xp
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ SLDR len, len_m
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f:
+dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_vect_dot_prod_avx, 02, 05, 0061
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx2.asm
new file mode 100644
index 0000000000..986fd28a4e
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx2.asm
@@ -0,0 +1,285 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_dot_prod_avx2(len, vec, *g_tbls, **buffs, *dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 r9
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved and loaded
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define tmp2 r10
+ %define tmp3 rdi ; must be saved and loaded
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define frame_size 2*8
+ %define arg(x) [rsp + frame_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ rex_push_reg r12
+ push_reg rdi
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop rdi
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+
+ %define trans ecx ;trans is for the variables in stack
+ %define arg0 trans
+ %define arg0_m arg(0)
+ %define arg1 trans
+ %define arg1_m arg(1)
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 ebx
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define tmp edx
+ %define tmp.w edx
+ %define tmp.b dl
+ %define tmp2 edi
+ %define tmp3 esi
+ %define return eax
+ %macro SLDR 2 ;stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ push esi
+ push edi
+ push ebx
+ mov arg3, arg(3)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ mov esp, ebp
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+
+%define vec_i tmp2
+%define ptr tmp3
+%define pos return
+
+%ifidn PS,4 ;32-bit code
+ %define vec_m arg1_m
+ %define len_m arg0_m
+ %define dest_m arg4_m
+%endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%ifidn PS,8 ;64-bit code
+ default rel
+ [bits 64]
+%endif
+
+section .text
+
+%define xmask0f ymm3
+%define xmask0fx xmm3
+%define xgft_lo ymm4
+%define xgft_hi ymm5
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xp ymm2
+
+align 16
+global gf_vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_dot_prod_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_dot_prod_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_dot_prod_avx2)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 32
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+.loop32:
+ vpxor xp, xp
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+
+ mov ptr, [src+vec_i*PS]
+
+ vmovdqu xgft_lo, [tmp] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ vperm2i128 xgft_hi, xgft_lo, xgft_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft_lo, xgft_lo, xgft_lo, 0x00 ; swapped to lo | lo
+
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ add tmp, 32
+ add vec_i, 1
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xgft_hi, xgft_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft_lo, xgft_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xgft_hi, xgft_hi, xgft_lo ;GF add high and low partials
+ vpxor xp, xp, xgft_hi ;xp += partial
+
+ SLDR vec, vec_m
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest, dest_m
+ XSTR [dest+pos], xp
+
+ add pos, 32 ;Loop on 32 bytes at a time
+ SLDR len, len_m
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-32
+ jmp .loop32 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_vect_dot_prod_avx2, 04, 05, 0190
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx512.asm
new file mode 100644
index 0000000000..405c1e48e2
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_avx512.asm
@@ -0,0 +1,245 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_dot_prod_avx512(len, vec, *g_tbls, **buffs, *dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved, loaded and restored
+ %define arg5 r15 ; must be saved and restored
+ %define tmp r11
+ %define tmp2 r10
+ %define return rax
+ %define PS 8
+ %define LOG_PS 3
+ %define stack_size 0*16 + 3*8 ; must be an odd multiple of 8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_reg r12, 9*16 + 0*8
+ save_reg r15, 9*16 + 3*8
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ mov r12, [rsp + 9*16 + 0*8]
+ mov r15, [rsp + 9*16 + 3*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest1 arg4
+%define ptr arg5
+%define vec_i tmp2
+%define pos return
+
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+%define xmask0f zmm5
+%define xgft1_lo zmm4
+%define xgft1_loy ymm4
+%define xgft1_hi zmm3
+%define x0 zmm0
+%define xgft1_loy ymm4
+%define x0y ymm0
+%define xtmpa zmm1
+%define xp1 zmm2
+%define xp1y ymm2
+
+default rel
+[bits 64]
+section .text
+
+align 16
+global gf_vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_dot_prod_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_dot_prod_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_dot_prod_avx512)
+%endif
+
+ FUNC_SAVE
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ sal vec, LOG_PS ;vec *= PS. Make vec_i count by PS
+ sub len, 64
+ jl .len_lt_64
+
+.loop64:
+ vpxorq xp1, xp1, xp1
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+ mov ptr, [src+vec_i]
+ XLDR x0, [ptr+pos] ;Get next source vector
+ add vec_i, PS
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vmovdqu8 xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ add tmp, 32
+
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x55
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x00
+
+ vpshufb xgft1_hi, xgft1_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xgft1_lo, xgft1_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xgft1_hi, xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxorq xp1, xp1, xgft1_hi ;xp1 += partial
+
+ cmp vec_i, vec
+ jl .next_vect
+
+ XSTR [dest1+pos], xp1
+
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+
+.len_lt_64: ; 32-byte version
+ add len, 32
+ jl .return_fail
+
+.loop32:
+ vpxorq xp1, xp1, xp1
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect2:
+ mov ptr, [src+vec_i]
+ XLDR x0y, [ptr+pos] ;Get next source vector 32B
+ add vec_i, PS
+ vpsraw xtmpa, x0, 4 ;Shift to put high nibble into bits 4-0
+ vshufi64x2 x0, x0, xtmpa, 0x44 ;put x0 = xl:xh
+ vpandq x0, x0, xmask0f ;Mask bits 4-0
+ vmovdqu8 xgft1_loy, [tmp] ;Load array Ax{00}..{0f}, Ax{00}..{f0}
+ add tmp, 32
+ vshufi64x2 xgft1_lo, xgft1_lo, xgft1_lo, 0x50 ;=AlAh:AlAh
+ vpshufb xgft1_lo, xgft1_lo, x0 ;Lookup mul table
+ vshufi64x2 xgft1_hi, xgft1_lo, xgft1_lo, 0x0e ;=xh:
+ vpxorq xgft1_hi, xgft1_hi, xgft1_lo ;GF add high and low partials
+ vpxorq xp1, xp1, xgft1_hi ;xp1 += partial
+ cmp vec_i, vec
+ jl .next_vect2
+
+ XSTR [dest1+pos], xp1y
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-32
+ jmp .loop32 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_vect_dot_prod_avx512
+no_gf_vect_dot_prod_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_sse.asm b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_sse.asm
new file mode 100644
index 0000000000..67f4a1a329
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_dot_prod_sse.asm
@@ -0,0 +1,276 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_dot_prod_sse(len, vec, *g_tbls, **buffs, *dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 r9
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+
+ %define arg4 r12 ; must be saved and loaded
+ %define tmp r11
+ %define tmp2 r10
+ %define tmp3 rdi ; must be saved and loaded
+ %define return rax
+ %macro SLDR 2
+ %endmacro
+ %define SSTR SLDR
+ %define PS 8
+ %define frame_size 2*8
+ %define arg(x) [rsp + frame_size + PS + PS*x]
+
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ rex_push_reg r12
+ push_reg rdi
+ end_prolog
+ mov arg4, arg(4)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop rdi
+ pop r12
+ %endmacro
+%endif
+
+%ifidn __OUTPUT_FORMAT__, elf32
+
+;;;================== High Address;
+;;; arg4
+;;; arg3
+;;; arg2
+;;; arg1
+;;; arg0
+;;; return
+;;;<================= esp of caller
+;;; ebp
+;;;<================= ebp = esp
+;;; esi
+;;; edi
+;;; ebx
+;;;<================= esp of callee
+;;;
+;;;================== Low Address;
+
+ %define PS 4
+ %define LOG_PS 2
+ %define func(x) x:
+ %define arg(x) [ebp + PS*2 + PS*x]
+
+ %define trans ecx ;trans is for the variables in stack
+ %define arg0 trans
+ %define arg0_m arg(0)
+ %define arg1 trans
+ %define arg1_m arg(1)
+ %define arg2 arg2_m
+ %define arg2_m arg(2)
+ %define arg3 ebx
+ %define arg4 trans
+ %define arg4_m arg(4)
+ %define tmp edx
+ %define tmp2 edi
+ %define tmp3 esi
+ %define return eax
+ %macro SLDR 2 ;; stack load/restore
+ mov %1, %2
+ %endmacro
+ %define SSTR SLDR
+
+ %macro FUNC_SAVE 0
+ push ebp
+ mov ebp, esp
+ push esi
+ push edi
+ push ebx
+ mov arg3, arg(3)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ pop ebx
+ pop edi
+ pop esi
+ mov esp, ebp
+ pop ebp
+ %endmacro
+
+%endif ; output formats
+
+%define len arg0
+%define vec arg1
+%define mul_array arg2
+%define src arg3
+%define dest arg4
+
+%define vec_i tmp2
+%define ptr tmp3
+%define pos return
+
+ %ifidn PS,4 ;32-bit code
+ %define vec_m arg1_m
+ %define len_m arg0_m
+ %define dest_m arg4_m
+ %endif
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+%ifidn PS,8 ;64-bit code
+ default rel
+ [bits 64]
+%endif
+
+section .text
+
+%define xmask0f xmm5
+%define xgft_lo xmm4
+%define xgft_hi xmm3
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xp xmm2
+
+align 16
+global gf_vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_dot_prod_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_dot_prod_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_dot_prod_sse)
+%endif
+
+ FUNC_SAVE
+ SLDR len, len_m
+ sub len, 16
+ SSTR len_m, len
+ jl .return_fail
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+.loop16:
+ pxor xp, xp
+ mov tmp, mul_array
+ xor vec_i, vec_i
+
+.next_vect:
+
+ mov ptr, [src+vec_i*PS]
+ movdqu xgft_lo, [tmp] ;Load array Cx{00}, Cx{01}, ..., Cx{0f}
+ movdqu xgft_hi, [tmp+16] ; " Cx{00}, Cx{10}, ..., Cx{f0}
+ XLDR x0, [ptr+pos] ;Get next source vector
+
+ add tmp, 32
+ add vec_i, 1
+
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+
+ pshufb xgft_hi, x0 ;Lookup mul table of high nibble
+ pshufb xgft_lo, xtmpa ;Lookup mul table of low nibble
+ pxor xgft_hi, xgft_lo ;GF add high and low partials
+ pxor xp, xgft_hi ;xp += partial
+
+ SLDR vec, vec_m
+ cmp vec_i, vec
+ jl .next_vect
+
+ SLDR dest, dest_m
+ XSTR [dest+pos], xp
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ SLDR len, len_m
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ jmp .loop16 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_vect_dot_prod_sse, 00, 05, 0060
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx.asm b/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx.asm
new file mode 100644
index 0000000000..1a252c474f
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx.asm
@@ -0,0 +1,201 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_mad_avx(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+ %define PS 8
+ %define stack_size 16*3 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ save_reg r12, 3*16 + 0*8
+ save_reg r15, 3*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ mov r12, [rsp + 3*16 + 0*8]
+ mov r15, [rsp + 3*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_vect_mad_avx(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest arg5
+%define pos return
+%define pos.w return.w
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm8
+%define xgft_lo xmm7
+%define xgft_hi xmm6
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph xmm2
+%define xtmpl xmm3
+%define xd xmm4
+%define xtmpd xmm5
+
+align 16
+global gf_vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_mad_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_mad_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_mad_avx)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+
+ xor pos, pos
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+
+ sal vec_i, 5 ;Multiply by 32
+ vmovdqu xgft_lo, [vec_i+mul_array] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ vmovdqu xgft_hi, [vec_i+mul_array+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+
+ XLDR xtmpd, [dest+len] ;backup the last 16 bytes in dest
+
+.loop16:
+ XLDR xd, [dest+pos] ;Get next dest vector
+.loop16_overlap:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xtmph, xgft_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd, xd, xtmph ;xd += partial
+
+ XSTR [dest+pos], xd
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ vmovdqa xd, xtmpd ;Restore xd
+ jmp .loop16_overlap ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_vect_mad_avx, 02, 01, 0201
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx2.asm b/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx2.asm
new file mode 100644
index 0000000000..9b24c6e62a
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx2.asm
@@ -0,0 +1,208 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_mad_avx2(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12 ; must be saved and loaded
+ %define arg5 r15
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define return rax
+ %define return.w eax
+ %define PS 8
+ %define stack_size 16*3 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+ %macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ save_reg r12, 3*16 + 0*8
+ save_reg r15, 3*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ mov r12, [rsp + 3*16 + 0*8]
+ mov r15, [rsp + 3*16 + 1*8]
+ add rsp, stack_size
+ %endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+
+ %define tmp r11
+ %define tmp.w r11d
+ %define tmp.b r11b
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+
+;;; gf_vect_mad_avx2(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest arg5
+%define pos return
+%define pos.w return.w
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu
+ %define XSTR vmovdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f ymm8
+%define xmask0fx xmm8
+%define xgft_lo ymm7
+%define xgft_hi ymm6
+
+%define x0 ymm0
+%define xtmpa ymm1
+%define xtmph ymm2
+%define xtmpl ymm3
+%define xd ymm4
+%define xtmpd ymm5
+
+align 16
+global gf_vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_mad_avx2)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_mad_avx2:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_mad_avx2)
+%endif
+
+ FUNC_SAVE
+ sub len, 32
+ jl .return_fail
+ xor pos, pos
+ mov tmp.b, 0x0f
+ vpinsrb xmask0fx, xmask0fx, tmp.w, 0
+ vpbroadcastb xmask0f, xmask0fx ;Construct mask 0x0f0f0f...
+
+ sal vec_i, 5 ;Multiply by 32
+ vmovdqu xgft_lo, [vec_i+mul_array] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+ vperm2i128 xgft_hi, xgft_lo, xgft_lo, 0x11 ; swapped to hi | hi
+ vperm2i128 xgft_lo, xgft_lo, xgft_lo, 0x00 ; swapped to lo | lo
+
+ XLDR xtmpd, [dest+len] ;backup the last 32 bytes in dest
+
+.loop32:
+ XLDR xd, [dest+pos] ;Get next dest vector
+.loop32_overlap:
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vpand xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xtmph, xgft_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl, xgft_lo, xtmpa ;Lookup mul table of low nibble
+ vpxor xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxor xd, xd, xtmph ;xd += partial
+
+ XSTR [dest+pos], xd
+ add pos, 32 ;Loop on 32 bytes at a time
+ cmp pos, len
+ jle .loop32
+
+ lea tmp, [len + 32]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-32
+ vmovdqa xd, xtmpd ;Restore xd
+ jmp .loop32_overlap ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+;;; func core, ver, snum
+slversion gf_vect_mad_avx2, 04, 01, 0202
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx512.asm b/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx512.asm
new file mode 100644
index 0000000000..adc2acf3e8
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_mad_avx512.asm
@@ -0,0 +1,198 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_mad_avx512(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifdef HAVE_AS_KNOWS_AVX512
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12 ; must be saved and loaded
+ %define arg5 r15
+ %define tmp r11
+ %define return rax
+ %define PS 8
+ %define stack_size 16*3 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+ %macro FUNC_SAVE 0
+ sub rsp, stack_size
+ vmovdqa [rsp+16*0],xmm6
+ vmovdqa [rsp+16*1],xmm7
+ vmovdqa [rsp+16*2],xmm8
+ save_reg r12, 3*16 + 0*8
+ save_reg r15, 3*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp+16*0]
+ vmovdqa xmm7, [rsp+16*1]
+ vmovdqa xmm8, [rsp+16*2]
+ mov r12, [rsp + 3*16 + 0*8]
+ mov r15, [rsp + 3*16 + 1*8]
+ add rsp, stack_size
+ %endmacro
+%endif
+
+;;; gf_vect_mad_avx512(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest arg5
+%define pos return
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR vmovdqu8
+ %define XSTR vmovdqu8
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+ %else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+ %endif
+%endif
+
+
+default rel
+
+[bits 64]
+section .text
+
+%define x0 zmm0
+%define xtmpa zmm1
+%define xtmph zmm2
+%define xtmpl zmm3
+%define xd zmm4
+%define xtmpd zmm5
+%define xgft_hi zmm6
+%define xgft_lo zmm7
+%define xgft_loy ymm7
+%define xmask0f zmm8
+
+align 16
+global gf_vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_mad_avx512)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_mad_avx512:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_mad_avx512)
+%endif
+
+ FUNC_SAVE
+ sub len, 64
+ jl .return_fail
+ xor pos, pos
+ mov tmp, 0x0f
+ vpbroadcastb xmask0f, tmp ;Construct mask 0x0f0f0f...
+ sal vec_i, 5 ;Multiply by 32
+ vmovdqu8 xgft_loy, [vec_i+mul_array] ;Load array Cx{00}..{0f}, Cx{00}..{f0}
+ vshufi64x2 xgft_hi, xgft_lo, xgft_lo, 0x55
+ vshufi64x2 xgft_lo, xgft_lo, xgft_lo, 0x00
+ mov tmp, -1
+ kmovq k1, tmp
+
+.loop64:
+ XLDR xd, [dest+pos] ;Get next dest vector
+ XLDR x0, [src+pos] ;Get next source vector
+
+ vpandq xtmpa, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpandq x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+
+ vpshufb xtmph {k1}{z}, xgft_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmpl {k1}{z}, xgft_lo, xtmpa ;Lookup mul table of low nibble
+ vpxorq xtmph, xtmph, xtmpl ;GF add high and low partials
+ vpxorq xd, xd, xtmph ;xd += partial
+
+ XSTR [dest+pos], xd
+ add pos, 64 ;Loop on 64 bytes at a time
+ cmp pos, len
+ jle .loop64
+
+ lea tmp, [len + 64]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, (1 << 63)
+ lea tmp, [len + 64 - 1]
+ and tmp, 63
+ sarx pos, pos, tmp
+ kmovq k1, pos
+ mov pos, len ;Overlapped offset length-64
+ jmp .loop64 ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+%else
+%ifidn __OUTPUT_FORMAT__, win64
+global no_gf_vect_mad_avx512
+no_gf_vect_mad_avx512:
+%endif
+%endif ; ifdef HAVE_AS_KNOWS_AVX512
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_mad_sse.asm b/contrib/libs/isa-l/erasure_code/gf_vect_mad_sse.asm
new file mode 100644
index 0000000000..ea48612324
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_mad_sse.asm
@@ -0,0 +1,202 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_mad_sse(len, vec, vec_i, mul_array, src, dest);
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg0.w ecx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define arg4 r12
+ %define arg5 r15
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+ %define PS 8
+ %define stack_size 16*3 + 3*8
+ %define arg(x) [rsp + stack_size + PS + PS*x]
+ %define func(x) proc_frame x
+
+%macro FUNC_SAVE 0
+ sub rsp, stack_size
+ movdqa [rsp+16*0],xmm6
+ movdqa [rsp+16*1],xmm7
+ movdqa [rsp+16*2],xmm8
+ save_reg r12, 3*16 + 0*8
+ save_reg r15, 3*16 + 1*8
+ end_prolog
+ mov arg4, arg(4)
+ mov arg5, arg(5)
+%endmacro
+
+%macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp+16*0]
+ movdqa xmm7, [rsp+16*1]
+ movdqa xmm8, [rsp+16*2]
+ mov r12, [rsp + 3*16 + 0*8]
+ mov r15, [rsp + 3*16 + 1*8]
+ add rsp, stack_size
+%endmacro
+
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg0.w edi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define return.w eax
+
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+%endif
+
+;;; gf_vect_mad_sse(len, vec, vec_i, mul_array, src, dest)
+%define len arg0
+%define len.w arg0.w
+%define vec arg1
+%define vec_i arg2
+%define mul_array arg3
+%define src arg4
+%define dest arg5
+%define pos return
+%define pos.w return.w
+
+%ifndef EC_ALIGNED_ADDR
+;;; Use Un-aligned load/store
+ %define XLDR movdqu
+ %define XSTR movdqu
+%else
+;;; Use Non-temporal load/stor
+ %ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+ %else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+ %endif
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm8
+%define xgft_lo xmm7
+%define xgft_hi xmm6
+
+%define x0 xmm0
+%define xtmpa xmm1
+%define xtmph xmm2
+%define xtmpl xmm3
+%define xd xmm4
+%define xtmpd xmm5
+
+
+align 16
+global gf_vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_mad_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_mad_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_mad_sse)
+%endif
+
+ FUNC_SAVE
+ sub len, 16
+ jl .return_fail
+
+ xor pos, pos
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ sal vec_i, 5 ;Multiply by 32
+ movdqu xgft_lo, [vec_i+mul_array] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ movdqu xgft_hi, [vec_i+mul_array+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+
+ XLDR xtmpd, [dest+len] ;backup the last 16 bytes in dest
+
+.loop16:
+ XLDR xd, [dest+pos] ;Get next dest vector
+.loop16_overlap:
+ XLDR x0, [src+pos] ;Get next source vector
+ movdqa xtmph, xgft_hi ;Reload const array registers
+ movdqa xtmpl, xgft_lo
+ movdqa xtmpa, x0 ;Keep unshifted copy of src
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand xtmpa, xmask0f ;Mask low src nibble in bits 4-0
+ pshufb xtmph, x0 ;Lookup mul table of high nibble
+ pshufb xtmpl, xtmpa ;Lookup mul table of low nibble
+ pxor xtmph, xtmpl ;GF add high and low partials
+
+ pxor xd, xtmph
+ XSTR [dest+pos], xd ;Store result
+
+ add pos, 16 ;Loop on 16 bytes at a time
+ cmp pos, len
+ jle .loop16
+
+ lea tmp, [len + 16]
+ cmp pos, tmp
+ je .return_pass
+
+ ;; Tail len
+ mov pos, len ;Overlapped offset length-16
+ movdqa xd, xtmpd ;Restore xd
+ jmp .loop16_overlap ;Do one more overlap pass
+
+.return_pass:
+ mov return, 0
+ FUNC_RESTORE
+ ret
+
+.return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f: dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_vect_mad_sse, 00, 01, 0200
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_mul_avx.asm b/contrib/libs/isa-l/erasure_code/gf_vect_mul_avx.asm
new file mode 100644
index 0000000000..86121b298a
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_mul_avx.asm
@@ -0,0 +1,168 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_mul_avx(len, mul_array, src, dest)
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+
+%elifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define return rax
+ %define stack_size 5*16 + 8 ; must be an odd multiple of 8
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm13, 2*16
+ save_xmm128 xmm14, 3*16
+ save_xmm128 xmm15, 4*16
+ end_prolog
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ vmovdqa xmm6, [rsp + 0*16]
+ vmovdqa xmm7, [rsp + 1*16]
+ vmovdqa xmm13, [rsp + 2*16]
+ vmovdqa xmm14, [rsp + 3*16]
+ vmovdqa xmm15, [rsp + 4*16]
+ add rsp, stack_size
+ %endmacro
+
+%endif
+
+
+%define len arg0
+%define mul_array arg1
+%define src arg2
+%define dest arg3
+%define pos return
+
+
+;;; Use Non-temporal load/stor
+%ifdef NO_NT_LDST
+ %define XLDR vmovdqa
+ %define XSTR vmovdqa
+%else
+ %define XLDR vmovntdqa
+ %define XSTR vmovntdq
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft_lo xmm14
+%define xgft_hi xmm13
+
+%define x0 xmm0
+%define xtmp1a xmm1
+%define xtmp1b xmm2
+%define xtmp1c xmm3
+%define x1 xmm4
+%define xtmp2a xmm5
+%define xtmp2b xmm6
+%define xtmp2c xmm7
+
+align 16
+global gf_vect_mul_avx:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_mul_avx)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_mul_avx:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_mul_avx)
+%endif
+ FUNC_SAVE
+ mov pos, 0
+ vmovdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ vmovdqu xgft_lo, [mul_array] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ vmovdqu xgft_hi, [mul_array+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+
+loop32:
+ XLDR x0, [src+pos] ;Get next source vector
+ XLDR x1, [src+pos+16] ;Get next source vector + 16B ahead
+ add pos, 32 ;Loop on 16 bytes at a time
+ cmp pos, len
+ vpand xtmp1a, x0, xmask0f ;Mask low src nibble in bits 4-0
+ vpand xtmp2a, x1, xmask0f
+ vpsraw x0, x0, 4 ;Shift to put high nibble into bits 4-0
+ vpsraw x1, x1, 4
+ vpand x0, x0, xmask0f ;Mask high src nibble in bits 4-0
+ vpand x1, x1, xmask0f
+ vpshufb xtmp1b, xgft_hi, x0 ;Lookup mul table of high nibble
+ vpshufb xtmp1c, xgft_lo, xtmp1a ;Lookup mul table of low nibble
+ vpshufb xtmp2b, xgft_hi, x1 ;Lookup mul table of high nibble
+ vpshufb xtmp2c, xgft_lo, xtmp2a ;Lookup mul table of low nibble
+ vpxor xtmp1b, xtmp1b, xtmp1c ;GF add high and low partials
+ vpxor xtmp2b, xtmp2b, xtmp2c
+ XSTR [dest+pos-32], xtmp1b ;Store result
+ XSTR [dest+pos-16], xtmp2b ;Store +16B result
+ jl loop32
+
+
+return_pass:
+ FUNC_RESTORE
+ sub pos, len
+ ret
+
+return_fail:
+ FUNC_RESTORE
+ mov return, 1
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+
+mask0f:
+dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_vect_mul_avx, 01, 03, 0036
diff --git a/contrib/libs/isa-l/erasure_code/gf_vect_mul_sse.asm b/contrib/libs/isa-l/erasure_code/gf_vect_mul_sse.asm
new file mode 100644
index 0000000000..01a3269d65
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/gf_vect_mul_sse.asm
@@ -0,0 +1,175 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;
+;;; gf_vect_mul_sse(len, mul_array, src, dest)
+;;;
+
+%include "reg_sizes.asm"
+
+%ifidn __OUTPUT_FORMAT__, elf64
+ %define arg0 rdi
+ %define arg1 rsi
+ %define arg2 rdx
+ %define arg3 rcx
+ %define arg4 r8
+ %define arg5 r9
+ %define tmp r11
+ %define return rax
+ %define func(x) x:
+ %define FUNC_SAVE
+ %define FUNC_RESTORE
+
+%elifidn __OUTPUT_FORMAT__, win64
+ %define arg0 rcx
+ %define arg1 rdx
+ %define arg2 r8
+ %define arg3 r9
+ %define return rax
+ %define stack_size 5*16 + 8 ; must be an odd multiple of 8
+ %define func(x) proc_frame x
+ %macro FUNC_SAVE 0
+ alloc_stack stack_size
+ save_xmm128 xmm6, 0*16
+ save_xmm128 xmm7, 1*16
+ save_xmm128 xmm13, 2*16
+ save_xmm128 xmm14, 3*16
+ save_xmm128 xmm15, 4*16
+ end_prolog
+ %endmacro
+
+ %macro FUNC_RESTORE 0
+ movdqa xmm6, [rsp + 0*16]
+ movdqa xmm7, [rsp + 1*16]
+ movdqa xmm13, [rsp + 2*16]
+ movdqa xmm14, [rsp + 3*16]
+ movdqa xmm15, [rsp + 4*16]
+ add rsp, stack_size
+ %endmacro
+
+%endif
+
+
+%define len arg0
+%define mul_array arg1
+%define src arg2
+%define dest arg3
+%define pos return
+
+
+;;; Use Non-temporal load/stor
+%ifdef NO_NT_LDST
+ %define XLDR movdqa
+ %define XSTR movdqa
+%else
+ %define XLDR movntdqa
+ %define XSTR movntdq
+%endif
+
+default rel
+
+[bits 64]
+section .text
+
+%define xmask0f xmm15
+%define xgft_lo xmm14
+%define xgft_hi xmm13
+
+%define x0 xmm0
+%define xtmp1a xmm1
+%define xtmp1b xmm2
+%define xtmp1c xmm3
+%define x1 xmm4
+%define xtmp2a xmm5
+%define xtmp2b xmm6
+%define xtmp2c xmm7
+
+
+align 16
+global gf_vect_mul_sse:ISAL_SYM_TYPE_FUNCTION
+func(gf_vect_mul_sse)
+%ifidn __OUTPUT_FORMAT__, macho64
+global _gf_vect_mul_sse:ISAL_SYM_TYPE_FUNCTION
+func(_gf_vect_mul_sse)
+%endif
+
+ FUNC_SAVE
+ mov pos, 0
+ movdqa xmask0f, [mask0f] ;Load mask of lower nibble in each byte
+ movdqu xgft_lo, [mul_array] ;Load array Cx{00}, Cx{01}, Cx{02}, ...
+ movdqu xgft_hi, [mul_array+16] ; " Cx{00}, Cx{10}, Cx{20}, ... , Cx{f0}
+
+loop32:
+ XLDR x0, [src+pos] ;Get next source vector
+ XLDR x1, [src+pos+16] ;Get next source vector + 16B ahead
+ movdqa xtmp1b, xgft_hi ;Reload const array registers
+ movdqa xtmp1c, xgft_lo
+ movdqa xtmp2b, xgft_hi
+ movdqa xtmp2c, xgft_lo
+ movdqa xtmp1a, x0 ;Keep unshifted copy of src
+ movdqa xtmp2a, x1
+ psraw x0, 4 ;Shift to put high nibble into bits 4-0
+ psraw x1, 4
+ pand xtmp1a, xmask0f ;Mask low src nibble in bits 4-0
+ pand xtmp2a, xmask0f
+ pand x0, xmask0f ;Mask high src nibble in bits 4-0
+ pand x1, xmask0f
+ pshufb xtmp1b, x0 ;Lookup mul table of high nibble
+ pshufb xtmp1c, xtmp1a ;Lookup mul table of low nibble
+ pshufb xtmp2b, x1
+ pshufb xtmp2c, xtmp2a
+ pxor xtmp1b, xtmp1c ;GF add high and low partials
+ pxor xtmp2b, xtmp2c
+ XSTR [dest+pos], xtmp1b ;Store result
+ XSTR [dest+pos+16], xtmp2b ;Store +16B result
+ add pos, 32 ;Loop on 32 bytes at at time
+ cmp pos, len
+ jl loop32
+
+
+return_pass:
+ sub pos, len
+ FUNC_RESTORE
+ ret
+
+return_fail:
+ mov return, 1
+ FUNC_RESTORE
+ ret
+
+endproc_frame
+
+section .data
+
+align 16
+mask0f:
+dq 0x0f0f0f0f0f0f0f0f, 0x0f0f0f0f0f0f0f0f
+
+;;; func core, ver, snum
+slversion gf_vect_mul_sse, 00, 03, 0034
diff --git a/contrib/libs/isa-l/erasure_code/ya.make b/contrib/libs/isa-l/erasure_code/ya.make
new file mode 100644
index 0000000000..2326f814bb
--- /dev/null
+++ b/contrib/libs/isa-l/erasure_code/ya.make
@@ -0,0 +1,81 @@
+LIBRARY(isa-l_ec)
+
+LICENSE(BSD-3-Clause)
+
+LICENSE_TEXTS(.yandex_meta/licenses.list.txt)
+
+VERSION(2.28)
+
+NO_UTIL()
+
+NO_COMPILER_WARNINGS()
+
+ADDINCL(
+ contrib/libs/isa-l/include
+ FOR
+ asm
+ contrib/libs/isa-l/include
+)
+
+IF (OS_DARWIN)
+ SRCS(
+ ec_multibinary_darwin.asm
+ )
+ELSE()
+ SRCS(
+ ec_multibinary.asm
+ )
+ENDIF()
+
+SRCS(
+ ec_base.c
+ ec_highlevel_func.c
+ gf_vect_mul_sse.asm
+ gf_vect_mul_avx.asm
+ gf_vect_dot_prod_sse.asm
+ gf_vect_dot_prod_avx.asm
+ gf_vect_dot_prod_avx2.asm
+ gf_vect_dot_prod_avx512.asm
+ gf_2vect_dot_prod_sse.asm
+ gf_2vect_dot_prod_avx.asm
+ gf_2vect_dot_prod_avx2.asm
+ gf_2vect_dot_prod_avx512.asm
+ gf_3vect_dot_prod_sse.asm
+ gf_3vect_dot_prod_avx.asm
+ gf_3vect_dot_prod_avx2.asm
+ gf_3vect_dot_prod_avx512.asm
+ gf_4vect_dot_prod_sse.asm
+ gf_4vect_dot_prod_avx.asm
+ gf_4vect_dot_prod_avx2.asm
+ gf_4vect_dot_prod_avx512.asm
+ gf_5vect_dot_prod_sse.asm
+ gf_5vect_dot_prod_avx.asm
+ gf_5vect_dot_prod_avx2.asm
+ gf_6vect_dot_prod_sse.asm
+ gf_6vect_dot_prod_avx.asm
+ gf_6vect_dot_prod_avx2.asm
+ gf_vect_mad_sse.asm
+ gf_vect_mad_avx.asm
+ gf_vect_mad_avx2.asm
+ gf_vect_mad_avx512.asm
+ gf_2vect_mad_sse.asm
+ gf_2vect_mad_avx.asm
+ gf_2vect_mad_avx2.asm
+ gf_2vect_mad_avx512.asm
+ gf_3vect_mad_sse.asm
+ gf_3vect_mad_avx.asm
+ gf_3vect_mad_avx2.asm
+ gf_3vect_mad_avx512.asm
+ gf_4vect_mad_sse.asm
+ gf_4vect_mad_avx.asm
+ gf_4vect_mad_avx2.asm
+ gf_4vect_mad_avx512.asm
+ gf_5vect_mad_sse.asm
+ gf_5vect_mad_avx.asm
+ gf_5vect_mad_avx2.asm
+ gf_6vect_mad_sse.asm
+ gf_6vect_mad_avx.asm
+ gf_6vect_mad_avx2.asm
+)
+
+END()
diff --git a/contrib/libs/isa-l/include/erasure_code.h b/contrib/libs/isa-l/include/erasure_code.h
new file mode 100644
index 0000000000..04fdfb1bc2
--- /dev/null
+++ b/contrib/libs/isa-l/include/erasure_code.h
@@ -0,0 +1,944 @@
+/**********************************************************************
+ Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**********************************************************************/
+
+
+#ifndef _ERASURE_CODE_H_
+#define _ERASURE_CODE_H_
+
+/**
+ * @file erasure_code.h
+ * @brief Interface to functions supporting erasure code encode and decode.
+ *
+ * This file defines the interface to optimized functions used in erasure
+ * codes. Encode and decode of erasures in GF(2^8) are made by calculating the
+ * dot product of the symbols (bytes in GF(2^8)) across a set of buffers and a
+ * set of coefficients. Values for the coefficients are determined by the type
+ * of erasure code. Using a general dot product means that any sequence of
+ * coefficients may be used including erasure codes based on random
+ * coefficients.
+ * Multiple versions of dot product are supplied to calculate 1-6 output
+ * vectors in one pass.
+ * Base GF multiply and divide functions can be sped up by defining
+ * GF_LARGE_TABLES at the expense of memory size.
+ *
+ */
+
+#include "gf_vect_mul.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Initialize tables for fast Erasure Code encode and decode.
+ *
+ * Generates the expanded tables needed for fast encode or decode for erasure
+ * codes on blocks of data. 32bytes is generated for each input coefficient.
+ *
+ * @param k The number of vector sources or rows in the generator matrix
+ * for coding.
+ * @param rows The number of output vectors to concurrently encode/decode.
+ * @param a Pointer to sets of arrays of input coefficients used to encode
+ * or decode data.
+ * @param gftbls Pointer to start of space for concatenated output tables
+ * generated from input coefficients. Must be of size 32*k*rows.
+ * @returns none
+ */
+
+void ec_init_tables(int k, int rows, unsigned char* a, unsigned char* gftbls);
+
+/**
+ * @brief Generate or decode erasure codes on blocks of data, runs appropriate version.
+ *
+ * Given a list of source data blocks, generate one or multiple blocks of
+ * encoded data as specified by a matrix of GF(2^8) coefficients. When given a
+ * suitable set of coefficients, this function will perform the fast generation
+ * or decoding of Reed-Solomon type erasure codes.
+ *
+ * This function determines what instruction sets are enabled and
+ * selects the appropriate version at runtime.
+ *
+ * @param len Length of each block of data (vector) of source or dest data.
+ * @param k The number of vector sources or rows in the generator matrix
+ * for coding.
+ * @param rows The number of output vectors to concurrently encode/decode.
+ * @param gftbls Pointer to array of input tables generated from coding
+ * coefficients in ec_init_tables(). Must be of size 32*k*rows
+ * @param data Array of pointers to source input buffers.
+ * @param coding Array of pointers to coded output buffers.
+ * @returns none
+ */
+
+void ec_encode_data(int len, int k, int rows, unsigned char *gftbls, unsigned char **data,
+ unsigned char **coding);
+
+/**
+ * @brief Generate or decode erasure codes on blocks of data, runs baseline version.
+ *
+ * Baseline version of ec_encode_data() with same parameters.
+ */
+void ec_encode_data_base(int len, int srcs, int dests, unsigned char *v, unsigned char **src,
+ unsigned char **dest);
+
+/**
+ * @brief Generate update for encode or decode of erasure codes from single source, runs appropriate version.
+ *
+ * Given one source data block, update one or multiple blocks of encoded data as
+ * specified by a matrix of GF(2^8) coefficients. When given a suitable set of
+ * coefficients, this function will perform the fast generation or decoding of
+ * Reed-Solomon type erasure codes from one input source at a time.
+ *
+ * This function determines what instruction sets are enabled and selects the
+ * appropriate version at runtime.
+ *
+ * @param len Length of each block of data (vector) of source or dest data.
+ * @param k The number of vector sources or rows in the generator matrix
+ * for coding.
+ * @param rows The number of output vectors to concurrently encode/decode.
+ * @param vec_i The vector index corresponding to the single input source.
+ * @param g_tbls Pointer to array of input tables generated from coding
+ * coefficients in ec_init_tables(). Must be of size 32*k*rows
+ * @param data Pointer to single input source used to update output parity.
+ * @param coding Array of pointers to coded output buffers.
+ * @returns none
+ */
+void ec_encode_data_update(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding);
+
+/**
+ * @brief Generate update for encode or decode of erasure codes from single source.
+ *
+ * Baseline version of ec_encode_data_update().
+ */
+
+void ec_encode_data_update_base(int len, int k, int rows, int vec_i, unsigned char *v,
+ unsigned char *data, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product, runs baseline version.
+ *
+ * Does a GF(2^8) dot product across each byte of the input array and a constant
+ * set of coefficients to produce each byte of the output. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 32*vlen byte constant array based on the input coefficients.
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 32*vlen byte array of pre-calculated constants based
+ * on the array of input coefficients. Only elements 32*CONST*j + 1
+ * of this array are used, where j = (0, 1, 2...) and CONST is the
+ * number of elements in the array of input coefficients. The
+ * elements used correspond to the original input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Pointer to destination data array.
+ * @returns none
+ */
+
+
+void gf_vect_dot_prod_base(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char *dest);
+
+/**
+ * @brief GF(2^8) vector dot product, runs appropriate version.
+ *
+ * Does a GF(2^8) dot product across each byte of the input array and a constant
+ * set of coefficients to produce each byte of the output. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 32*vlen byte constant array based on the input coefficients.
+ *
+ * This function determines what instruction sets are enabled and
+ * selects the appropriate version at runtime.
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 32*vlen byte array of pre-calculated constants based
+ * on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Pointer to destination data array.
+ * @returns none
+ */
+
+void gf_vect_dot_prod(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char *dest);
+
+/**
+ * @brief GF(2^8) vector multiply accumulate, runs appropriate version.
+ *
+ * Does a GF(2^8) multiply across each byte of input source with expanded
+ * constant and add to destination array. Can be used for erasure coding encode
+ * and decode update when only one source is available at a time. Function
+ * requires pre-calculation of a 32*vec byte constant array based on the input
+ * coefficients.
+ *
+ * This function determines what instruction sets are enabled and selects the
+ * appropriate version at runtime.
+ *
+ * @param len Length of each vector in bytes. Must be >= 64.
+ * @param vec The number of vector sources or rows in the generator matrix
+ * for coding.
+ * @param vec_i The vector index corresponding to the single input source.
+ * @param gftbls Pointer to array of input tables generated from coding
+ * coefficients in ec_init_tables(). Must be of size 32*vec.
+ * @param src Array of pointers to source inputs.
+ * @param dest Pointer to destination data array.
+ * @returns none
+ */
+
+void gf_vect_mad(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char *dest);
+
+/**
+ * @brief GF(2^8) vector multiply accumulate, baseline version.
+ *
+ * Baseline version of gf_vect_mad() with same parameters.
+ */
+
+void gf_vect_mad_base(int len, int vec, int vec_i, unsigned char *v, unsigned char *src,
+ unsigned char *dest);
+
+// x86 only
+#if defined(__i386__) || defined(__x86_64__)
+
+/**
+ * @brief Generate or decode erasure codes on blocks of data.
+ *
+ * Arch specific version of ec_encode_data() with same parameters.
+ * @requires SSE4.1
+ */
+void ec_encode_data_sse(int len, int k, int rows, unsigned char *gftbls, unsigned char **data,
+ unsigned char **coding);
+
+/**
+ * @brief Generate or decode erasure codes on blocks of data.
+ *
+ * Arch specific version of ec_encode_data() with same parameters.
+ * @requires AVX
+ */
+void ec_encode_data_avx(int len, int k, int rows, unsigned char *gftbls, unsigned char **data,
+ unsigned char **coding);
+
+/**
+ * @brief Generate or decode erasure codes on blocks of data.
+ *
+ * Arch specific version of ec_encode_data() with same parameters.
+ * @requires AVX2
+ */
+void ec_encode_data_avx2(int len, int k, int rows, unsigned char *gftbls, unsigned char **data,
+ unsigned char **coding);
+
+/**
+ * @brief Generate update for encode or decode of erasure codes from single source.
+ *
+ * Arch specific version of ec_encode_data_update() with same parameters.
+ * @requires SSE4.1
+ */
+
+void ec_encode_data_update_sse(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding);
+
+/**
+ * @brief Generate update for encode or decode of erasure codes from single source.
+ *
+ * Arch specific version of ec_encode_data_update() with same parameters.
+ * @requires AVX
+ */
+
+void ec_encode_data_update_avx(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding);
+
+/**
+ * @brief Generate update for encode or decode of erasure codes from single source.
+ *
+ * Arch specific version of ec_encode_data_update() with same parameters.
+ * @requires AVX2
+ */
+
+void ec_encode_data_update_avx2(int len, int k, int rows, int vec_i, unsigned char *g_tbls,
+ unsigned char *data, unsigned char **coding);
+
+/**
+ * @brief GF(2^8) vector dot product.
+ *
+ * Does a GF(2^8) dot product across each byte of the input array and a constant
+ * set of coefficients to produce each byte of the output. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 32*vlen byte constant array based on the input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 32*vlen byte array of pre-calculated constants based
+ * on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Pointer to destination data array.
+ * @returns none
+ */
+
+void gf_vect_dot_prod_sse(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char *dest);
+
+/**
+ * @brief GF(2^8) vector dot product.
+ *
+ * Does a GF(2^8) dot product across each byte of the input array and a constant
+ * set of coefficients to produce each byte of the output. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 32*vlen byte constant array based on the input coefficients.
+ * @requires AVX
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 32*vlen byte array of pre-calculated constants based
+ * on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Pointer to destination data array.
+ * @returns none
+ */
+
+void gf_vect_dot_prod_avx(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char *dest);
+
+/**
+ * @brief GF(2^8) vector dot product.
+ *
+ * Does a GF(2^8) dot product across each byte of the input array and a constant
+ * set of coefficients to produce each byte of the output. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 32*vlen byte constant array based on the input coefficients.
+ * @requires AVX2
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 32*vlen byte array of pre-calculated constants based
+ * on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Pointer to destination data array.
+ * @returns none
+ */
+
+void gf_vect_dot_prod_avx2(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char *dest);
+
+/**
+ * @brief GF(2^8) vector dot product with two outputs.
+ *
+ * Vector dot product optimized to calculate two outputs at a time. Does two
+ * GF(2^8) dot products across each byte of the input array and two constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 2*32*vlen byte constant array based on the two sets of input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 2*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_2vect_dot_prod_sse(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with two outputs.
+ *
+ * Vector dot product optimized to calculate two outputs at a time. Does two
+ * GF(2^8) dot products across each byte of the input array and two constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 2*32*vlen byte constant array based on the two sets of input coefficients.
+ * @requires AVX
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 2*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_2vect_dot_prod_avx(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with two outputs.
+ *
+ * Vector dot product optimized to calculate two outputs at a time. Does two
+ * GF(2^8) dot products across each byte of the input array and two constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 2*32*vlen byte constant array based on the two sets of input coefficients.
+ * @requires AVX2
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 2*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_2vect_dot_prod_avx2(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with three outputs.
+ *
+ * Vector dot product optimized to calculate three outputs at a time. Does three
+ * GF(2^8) dot products across each byte of the input array and three constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 3*32*vlen byte constant array based on the three sets of input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 3*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_3vect_dot_prod_sse(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with three outputs.
+ *
+ * Vector dot product optimized to calculate three outputs at a time. Does three
+ * GF(2^8) dot products across each byte of the input array and three constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 3*32*vlen byte constant array based on the three sets of input coefficients.
+ * @requires AVX
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 3*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_3vect_dot_prod_avx(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with three outputs.
+ *
+ * Vector dot product optimized to calculate three outputs at a time. Does three
+ * GF(2^8) dot products across each byte of the input array and three constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 3*32*vlen byte constant array based on the three sets of input coefficients.
+ * @requires AVX2
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 3*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_3vect_dot_prod_avx2(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with four outputs.
+ *
+ * Vector dot product optimized to calculate four outputs at a time. Does four
+ * GF(2^8) dot products across each byte of the input array and four constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 4*32*vlen byte constant array based on the four sets of input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 4*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_4vect_dot_prod_sse(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with four outputs.
+ *
+ * Vector dot product optimized to calculate four outputs at a time. Does four
+ * GF(2^8) dot products across each byte of the input array and four constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 4*32*vlen byte constant array based on the four sets of input coefficients.
+ * @requires AVX
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 4*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_4vect_dot_prod_avx(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with four outputs.
+ *
+ * Vector dot product optimized to calculate four outputs at a time. Does four
+ * GF(2^8) dot products across each byte of the input array and four constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 4*32*vlen byte constant array based on the four sets of input coefficients.
+ * @requires AVX2
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 4*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_4vect_dot_prod_avx2(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with five outputs.
+ *
+ * Vector dot product optimized to calculate five outputs at a time. Does five
+ * GF(2^8) dot products across each byte of the input array and five constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 5*32*vlen byte constant array based on the five sets of input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 5*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_5vect_dot_prod_sse(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with five outputs.
+ *
+ * Vector dot product optimized to calculate five outputs at a time. Does five
+ * GF(2^8) dot products across each byte of the input array and five constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 5*32*vlen byte constant array based on the five sets of input coefficients.
+ * @requires AVX
+ *
+ * @param len Length of each vector in bytes. Must >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 5*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_5vect_dot_prod_avx(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with five outputs.
+ *
+ * Vector dot product optimized to calculate five outputs at a time. Does five
+ * GF(2^8) dot products across each byte of the input array and five constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 5*32*vlen byte constant array based on the five sets of input coefficients.
+ * @requires AVX2
+ *
+ * @param len Length of each vector in bytes. Must >= 32.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 5*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_5vect_dot_prod_avx2(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with six outputs.
+ *
+ * Vector dot product optimized to calculate six outputs at a time. Does six
+ * GF(2^8) dot products across each byte of the input array and six constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 6*32*vlen byte constant array based on the six sets of input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 6*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_6vect_dot_prod_sse(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with six outputs.
+ *
+ * Vector dot product optimized to calculate six outputs at a time. Does six
+ * GF(2^8) dot products across each byte of the input array and six constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 6*32*vlen byte constant array based on the six sets of input coefficients.
+ * @requires AVX
+ *
+ * @param len Length of each vector in bytes. Must be >= 16.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 6*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_6vect_dot_prod_avx(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector dot product with six outputs.
+ *
+ * Vector dot product optimized to calculate six outputs at a time. Does six
+ * GF(2^8) dot products across each byte of the input array and six constant
+ * sets of coefficients to produce each byte of the outputs. Can be used for
+ * erasure coding encode and decode. Function requires pre-calculation of a
+ * 6*32*vlen byte constant array based on the six sets of input coefficients.
+ * @requires AVX2
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vlen Number of vector sources.
+ * @param gftbls Pointer to 6*32*vlen byte array of pre-calculated constants
+ * based on the array of input coefficients.
+ * @param src Array of pointers to source inputs.
+ * @param dest Array of pointers to destination data buffers.
+ * @returns none
+ */
+
+void gf_6vect_dot_prod_avx2(int len, int vlen, unsigned char *gftbls,
+ unsigned char **src, unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply accumulate, arch specific version.
+ *
+ * Arch specific version of gf_vect_mad() with same parameters.
+ * @requires SSE4.1
+ */
+
+void gf_vect_mad_sse(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char *dest);
+/**
+ * @brief GF(2^8) vector multiply accumulate, arch specific version.
+ *
+ * Arch specific version of gf_vect_mad() with same parameters.
+ * @requires AVX
+ */
+
+void gf_vect_mad_avx(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char *dest);
+
+/**
+ * @brief GF(2^8) vector multiply accumulate, arch specific version.
+ *
+ * Arch specific version of gf_vect_mad() with same parameters.
+ * @requires AVX2
+ */
+
+void gf_vect_mad_avx2(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char *dest);
+
+
+/**
+ * @brief GF(2^8) vector multiply with 2 accumulate. SSE version.
+ *
+ * Does a GF(2^8) multiply across each byte of input source with expanded
+ * constants and add to destination arrays. Can be used for erasure coding
+ * encode and decode update when only one source is available at a
+ * time. Function requires pre-calculation of a 32*vec byte constant array based
+ * on the input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vec The number of vector sources or rows in the generator matrix
+ * for coding.
+ * @param vec_i The vector index corresponding to the single input source.
+ * @param gftbls Pointer to array of input tables generated from coding
+ * coefficients in ec_init_tables(). Must be of size 32*vec.
+ * @param src Pointer to source input array.
+ * @param dest Array of pointers to destination input/outputs.
+ * @returns none
+ */
+
+void gf_2vect_mad_sse(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 2 accumulate. AVX version of gf_2vect_mad_sse().
+ * @requires AVX
+ */
+void gf_2vect_mad_avx(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+/**
+ * @brief GF(2^8) vector multiply with 2 accumulate. AVX2 version of gf_2vect_mad_sse().
+ * @requires AVX2
+ */
+void gf_2vect_mad_avx2(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 3 accumulate. SSE version.
+ *
+ * Does a GF(2^8) multiply across each byte of input source with expanded
+ * constants and add to destination arrays. Can be used for erasure coding
+ * encode and decode update when only one source is available at a
+ * time. Function requires pre-calculation of a 32*vec byte constant array based
+ * on the input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vec The number of vector sources or rows in the generator matrix
+ * for coding.
+ * @param vec_i The vector index corresponding to the single input source.
+ * @param gftbls Pointer to array of input tables generated from coding
+ * coefficients in ec_init_tables(). Must be of size 32*vec.
+ * @param src Pointer to source input array.
+ * @param dest Array of pointers to destination input/outputs.
+ * @returns none
+ */
+
+void gf_3vect_mad_sse(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 3 accumulate. AVX version of gf_3vect_mad_sse().
+ * @requires AVX
+ */
+void gf_3vect_mad_avx(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 3 accumulate. AVX2 version of gf_3vect_mad_sse().
+ * @requires AVX2
+ */
+void gf_3vect_mad_avx2(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 4 accumulate. SSE version.
+ *
+ * Does a GF(2^8) multiply across each byte of input source with expanded
+ * constants and add to destination arrays. Can be used for erasure coding
+ * encode and decode update when only one source is available at a
+ * time. Function requires pre-calculation of a 32*vec byte constant array based
+ * on the input coefficients.
+ * @requires SSE4.1
+ *
+ * @param len Length of each vector in bytes. Must be >= 32.
+ * @param vec The number of vector sources or rows in the generator matrix
+ * for coding.
+ * @param vec_i The vector index corresponding to the single input source.
+ * @param gftbls Pointer to array of input tables generated from coding
+ * coefficients in ec_init_tables(). Must be of size 32*vec.
+ * @param src Pointer to source input array.
+ * @param dest Array of pointers to destination input/outputs.
+ * @returns none
+ */
+
+void gf_4vect_mad_sse(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 4 accumulate. AVX version of gf_4vect_mad_sse().
+ * @requires AVX
+ */
+void gf_4vect_mad_avx(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+/**
+ * @brief GF(2^8) vector multiply with 4 accumulate. AVX2 version of gf_4vect_mad_sse().
+ * @requires AVX2
+ */
+void gf_4vect_mad_avx2(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 5 accumulate. SSE version.
+ * @requires SSE4.1
+ */
+void gf_5vect_mad_sse(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 5 accumulate. AVX version.
+ * @requires AVX
+ */
+void gf_5vect_mad_avx(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+/**
+ * @brief GF(2^8) vector multiply with 5 accumulate. AVX2 version.
+ * @requires AVX2
+ */
+void gf_5vect_mad_avx2(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 6 accumulate. SSE version.
+ * @requires SSE4.1
+ */
+void gf_6vect_mad_sse(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+/**
+ * @brief GF(2^8) vector multiply with 6 accumulate. AVX version.
+ * @requires AVX
+ */
+void gf_6vect_mad_avx(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+/**
+ * @brief GF(2^8) vector multiply with 6 accumulate. AVX2 version.
+ * @requires AVX2
+ */
+void gf_6vect_mad_avx2(int len, int vec, int vec_i, unsigned char *gftbls, unsigned char *src,
+ unsigned char **dest);
+
+#endif
+
+/**********************************************************************
+ * The remaining are lib support functions used in GF(2^8) operations.
+ */
+
+/**
+ * @brief Single element GF(2^8) multiply.
+ *
+ * @param a Multiplicand a
+ * @param b Multiplicand b
+ * @returns Product of a and b in GF(2^8)
+ */
+
+unsigned char gf_mul_erasure(unsigned char a, unsigned char b);
+
+/**
+ * @brief Single element GF(2^8) inverse.
+ *
+ * @param a Input element
+ * @returns Field element b such that a x b = {1}
+ */
+
+unsigned char gf_inv(unsigned char a);
+
+/**
+ * @brief Generate a matrix of coefficients to be used for encoding.
+ *
+ * Vandermonde matrix example of encoding coefficients where high portion of
+ * matrix is identity matrix I and lower portion is constructed as 2^{i*(j-k+1)}
+ * i:{0,k-1} j:{k,m-1}. Commonly used method for choosing coefficients in
+ * erasure encoding but does not guarantee invertable for every sub matrix. For
+ * large pairs of m and k it is possible to find cases where the decode matrix
+ * chosen from sources and parity is not invertable. Users may want to adjust
+ * for certain pairs m and k. If m and k satisfy one of the following
+ * inequalities, no adjustment is required:
+ *
+ * - k <= 3
+ * - k = 4, m <= 25
+ * - k = 5, m <= 10
+ * - k <= 21, m-k = 4
+ * - m - k <= 3.
+ *
+ * @param a [m x k] array to hold coefficients
+ * @param m number of rows in matrix corresponding to srcs + parity.
+ * @param k number of columns in matrix corresponding to srcs.
+ * @returns none
+ */
+
+void gf_gen_rs_matrix(unsigned char *a, int m, int k);
+
+/**
+ * @brief Generate a Cauchy matrix of coefficients to be used for encoding.
+ *
+ * Cauchy matrix example of encoding coefficients where high portion of matrix
+ * is identity matrix I and lower portion is constructed as 1/(i + j) | i != j,
+ * i:{0,k-1} j:{k,m-1}. Any sub-matrix of a Cauchy matrix should be invertable.
+ *
+ * @param a [m x k] array to hold coefficients
+ * @param m number of rows in matrix corresponding to srcs + parity.
+ * @param k number of columns in matrix corresponding to srcs.
+ * @returns none
+ */
+
+void gf_gen_cauchy1_matrix(unsigned char *a, int m, int k);
+
+/**
+ * @brief Invert a matrix in GF(2^8)
+ *
+ * @param in input matrix
+ * @param out output matrix such that [in] x [out] = [I] - identity matrix
+ * @param n size of matrix [nxn]
+ * @returns 0 successful, other fail on singular input matrix
+ */
+
+int gf_invert_matrix(unsigned char *in, unsigned char *out, const int n);
+
+
+/*************************************************************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_ERASURE_CODE_H_
diff --git a/contrib/libs/isa-l/include/gf_vect_mul.h b/contrib/libs/isa-l/include/gf_vect_mul.h
new file mode 100644
index 0000000000..70a0ab2ed3
--- /dev/null
+++ b/contrib/libs/isa-l/include/gf_vect_mul.h
@@ -0,0 +1,152 @@
+/**********************************************************************
+ Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**********************************************************************/
+
+
+#ifndef _GF_VECT_MUL_H
+#define _GF_VECT_MUL_H
+
+/**
+ * @file gf_vect_mul.h
+ * @brief Interface to functions for vector (block) multiplication in GF(2^8).
+ *
+ * This file defines the interface to routines used in fast RAID rebuild and
+ * erasure codes.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// x86 only
+#if defined(__i386__) || defined(__x86_64__)
+
+ /**
+ * @brief GF(2^8) vector multiply by constant.
+ *
+ * Does a GF(2^8) vector multiply b = Ca where a and b are arrays and C
+ * is a single field element in GF(2^8). Can be used for RAID6 rebuild
+ * and partial write functions. Function requires pre-calculation of a
+ * 32-element constant array based on constant C. gftbl(C) = {C{00},
+ * C{01}, C{02}, ... , C{0f} }, {C{00}, C{10}, C{20}, ... , C{f0} }. Len
+ * and src must be aligned to 32B.
+ * @requires SSE4.1
+ *
+ * @param len Length of vector in bytes. Must be aligned to 32B.
+ * @param gftbl Pointer to 32-byte array of pre-calculated constants based on C.
+ * @param src Pointer to src data array. Must be aligned to 32B.
+ * @param dest Pointer to destination data array. Must be aligned to 32B.
+ * @returns 0 pass, other fail
+ */
+
+int gf_vect_mul_sse(int len, unsigned char *gftbl, void *src, void *dest);
+
+
+ /**
+ * @brief GF(2^8) vector multiply by constant.
+ *
+ * Does a GF(2^8) vector multiply b = Ca where a and b are arrays and C
+ * is a single field element in GF(2^8). Can be used for RAID6 rebuild
+ * and partial write functions. Function requires pre-calculation of a
+ * 32-element constant array based on constant C. gftbl(C) = {C{00},
+ * C{01}, C{02}, ... , C{0f} }, {C{00}, C{10}, C{20}, ... , C{f0} }. Len
+ * and src must be aligned to 32B.
+ * @requires AVX
+ *
+ * @param len Length of vector in bytes. Must be aligned to 32B.
+ * @param gftbl Pointer to 32-byte array of pre-calculated constants based on C.
+ * @param src Pointer to src data array. Must be aligned to 32B.
+ * @param dest Pointer to destination data array. Must be aligned to 32B.
+ * @returns 0 pass, other fail
+ */
+
+int gf_vect_mul_avx(int len, unsigned char *gftbl, void *src, void *dest);
+
+#endif
+
+/**
+ * @brief GF(2^8) vector multiply by constant, runs appropriate version.
+ *
+ * Does a GF(2^8) vector multiply b = Ca where a and b are arrays and C
+ * is a single field element in GF(2^8). Can be used for RAID6 rebuild
+ * and partial write functions. Function requires pre-calculation of a
+ * 32-element constant array based on constant C. gftbl(C) = {C{00},
+ * C{01}, C{02}, ... , C{0f} }, {C{00}, C{10}, C{20}, ... , C{f0} }.
+ * Len and src must be aligned to 32B.
+ *
+ * This function determines what instruction sets are enabled
+ * and selects the appropriate version at runtime.
+ *
+ * @param len Length of vector in bytes. Must be aligned to 32B.
+ * @param gftbl Pointer to 32-byte array of pre-calculated constants based on C.
+ * @param src Pointer to src data array. Must be aligned to 32B.
+ * @param dest Pointer to destination data array. Must be aligned to 32B.
+ * @returns 0 pass, other fail
+ */
+
+int gf_vect_mul(int len, unsigned char *gftbl, void *src, void *dest);
+
+
+/**
+ * @brief Initialize 32-byte constant array for GF(2^8) vector multiply
+ *
+ * Calculates array {C{00}, C{01}, C{02}, ... , C{0f} }, {C{00}, C{10},
+ * C{20}, ... , C{f0} } as required by other fast vector multiply
+ * functions.
+ * @param c Constant input.
+ * @param gftbl Table output.
+ */
+
+void gf_vect_mul_init(unsigned char c, unsigned char* gftbl);
+
+
+/**
+ * @brief GF(2^8) vector multiply by constant, runs baseline version.
+ *
+ * Does a GF(2^8) vector multiply b = Ca where a and b are arrays and C
+ * is a single field element in GF(2^8). Can be used for RAID6 rebuild
+ * and partial write functions. Function requires pre-calculation of a
+ * 32-element constant array based on constant C. gftbl(C) = {C{00},
+ * C{01}, C{02}, ... , C{0f} }, {C{00}, C{10}, C{20}, ... , C{f0} }. Len
+ * and src must be aligned to 32B.
+ *
+ * @param len Length of vector in bytes. Must be aligned to 32B.
+ * @param a Pointer to 32-byte array of pre-calculated constants based on C.
+ * only use 2nd element is used.
+ * @param src Pointer to src data array. Must be aligned to 32B.
+ * @param dest Pointer to destination data array. Must be aligned to 32B.
+ */
+
+void gf_vect_mul_base(int len, unsigned char *a, unsigned char *src,
+ unsigned char *dest);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_GF_VECT_MUL_H
diff --git a/contrib/libs/isa-l/include/multibinary.asm b/contrib/libs/isa-l/include/multibinary.asm
new file mode 100644
index 0000000000..2cad1c51be
--- /dev/null
+++ b/contrib/libs/isa-l/include/multibinary.asm
@@ -0,0 +1,399 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+%ifndef _MULTIBINARY_ASM_
+%define _MULTIBINARY_ASM_
+
+%ifidn __OUTPUT_FORMAT__, elf32
+ %define mbin_def_ptr dd
+ %define mbin_ptr_sz dword
+ %define mbin_rdi edi
+ %define mbin_rsi esi
+ %define mbin_rax eax
+ %define mbin_rbx ebx
+ %define mbin_rcx ecx
+ %define mbin_rdx edx
+%else
+ %define mbin_def_ptr dq
+ %define mbin_ptr_sz qword
+ %define mbin_rdi rdi
+ %define mbin_rsi rsi
+ %define mbin_rax rax
+ %define mbin_rbx rbx
+ %define mbin_rcx rcx
+ %define mbin_rdx rdx
+%endif
+
+%ifndef AS_FEATURE_LEVEL
+%define AS_FEATURE_LEVEL 4
+%endif
+
+;;;;
+; multibinary macro:
+; creates the visable entry point that uses HW optimized call pointer
+; creates the init of the HW optimized call pointer
+;;;;
+%macro mbin_interface 1
+ ;;;;
+ ; *_dispatched is defaulted to *_mbinit and replaced on first call.
+ ; Therefore, *_dispatch_init is only executed on first call.
+ ;;;;
+ section .data
+ %1_dispatched:
+ mbin_def_ptr %1_mbinit
+
+ section .text
+ global %1:ISAL_SYM_TYPE_FUNCTION
+ %1_mbinit:
+ ;;; only called the first time to setup hardware match
+ call %1_dispatch_init
+ ;;; falls thru to execute the hw optimized code
+ %1:
+ jmp mbin_ptr_sz [%1_dispatched]
+%endmacro
+
+;;;;;
+; mbin_dispatch_init parameters
+; Use this function when SSE/00/01 is a minimum requirement
+; 1-> function name
+; 2-> SSE/00/01 optimized function used as base
+; 3-> AVX or AVX/02 opt func
+; 4-> AVX2 or AVX/04 opt func
+;;;;;
+%macro mbin_dispatch_init 4
+ section .text
+ %1_dispatch_init:
+ push mbin_rsi
+ push mbin_rax
+ push mbin_rbx
+ push mbin_rcx
+ push mbin_rdx
+ lea mbin_rsi, [%2 WRT_OPT] ; Default to SSE 00/01
+
+ mov eax, 1
+ cpuid
+ and ecx, (FLAG_CPUID1_ECX_AVX | FLAG_CPUID1_ECX_OSXSAVE)
+ cmp ecx, (FLAG_CPUID1_ECX_AVX | FLAG_CPUID1_ECX_OSXSAVE)
+ lea mbin_rbx, [%3 WRT_OPT] ; AVX (gen2) opt func
+ jne _%1_init_done ; AVX is not available so end
+ mov mbin_rsi, mbin_rbx
+
+ ;; Try for AVX2
+ xor ecx, ecx
+ mov eax, 7
+ cpuid
+ test ebx, FLAG_CPUID7_EBX_AVX2
+ lea mbin_rbx, [%4 WRT_OPT] ; AVX (gen4) opt func
+ cmovne mbin_rsi, mbin_rbx
+
+ ;; Does it have xmm and ymm support
+ xor ecx, ecx
+ xgetbv
+ and eax, FLAG_XGETBV_EAX_XMM_YMM
+ cmp eax, FLAG_XGETBV_EAX_XMM_YMM
+ je _%1_init_done
+ lea mbin_rsi, [%2 WRT_OPT]
+
+ _%1_init_done:
+ pop mbin_rdx
+ pop mbin_rcx
+ pop mbin_rbx
+ pop mbin_rax
+ mov [%1_dispatched], mbin_rsi
+ pop mbin_rsi
+ ret
+%endmacro
+
+;;;;;
+; mbin_dispatch_init2 parameters
+; Cases where only base functions are available
+; 1-> function name
+; 2-> base function
+;;;;;
+%macro mbin_dispatch_init2 2
+ section .text
+ %1_dispatch_init:
+ push mbin_rsi
+ lea mbin_rsi, [%2 WRT_OPT] ; Default
+ mov [%1_dispatched], mbin_rsi
+ pop mbin_rsi
+ ret
+%endmacro
+
+;;;;;
+; mbin_dispatch_init_clmul 3 parameters
+; Use this case for CRC which needs both SSE4_1 and CLMUL
+; 1-> function name
+; 2-> base function
+; 3-> SSE4_1 and CLMUL optimized function
+;;;;;
+%macro mbin_dispatch_init_clmul 3
+ section .text
+ %1_dispatch_init:
+ push mbin_rsi
+ push mbin_rax
+ push mbin_rbx
+ push mbin_rcx
+ push mbin_rdx
+ lea mbin_rsi, [%2 WRT_OPT] ; Default - use base function
+
+ mov eax, 1
+ cpuid
+ lea mbin_rbx, [%3 WRT_OPT] ; SSE opt func
+
+ ; Test for SSE4.2
+ test ecx, FLAG_CPUID1_ECX_SSE4_1
+ jz _%1_init_done
+ test ecx, FLAG_CPUID1_ECX_CLMUL
+ cmovne mbin_rsi, mbin_rbx
+ _%1_init_done:
+ pop mbin_rdx
+ pop mbin_rcx
+ pop mbin_rbx
+ pop mbin_rax
+ mov [%1_dispatched], mbin_rsi
+ pop mbin_rsi
+ ret
+%endmacro
+
+;;;;;
+; mbin_dispatch_init5 parameters
+; 1-> function name
+; 2-> base function
+; 3-> SSE4_2 or 00/01 optimized function
+; 4-> AVX/02 opt func
+; 5-> AVX2/04 opt func
+;;;;;
+%macro mbin_dispatch_init5 5
+ section .text
+ %1_dispatch_init:
+ push mbin_rsi
+ push mbin_rax
+ push mbin_rbx
+ push mbin_rcx
+ push mbin_rdx
+ lea mbin_rsi, [%2 WRT_OPT] ; Default - use base function
+
+ mov eax, 1
+ cpuid
+ ; Test for SSE4.2
+ test ecx, FLAG_CPUID1_ECX_SSE4_2
+ lea mbin_rbx, [%3 WRT_OPT] ; SSE opt func
+ cmovne mbin_rsi, mbin_rbx
+
+ and ecx, (FLAG_CPUID1_ECX_AVX | FLAG_CPUID1_ECX_OSXSAVE)
+ cmp ecx, (FLAG_CPUID1_ECX_AVX | FLAG_CPUID1_ECX_OSXSAVE)
+ lea mbin_rbx, [%4 WRT_OPT] ; AVX (gen2) opt func
+ jne _%1_init_done ; AVX is not available so end
+ mov mbin_rsi, mbin_rbx
+
+ ;; Try for AVX2
+ xor ecx, ecx
+ mov eax, 7
+ cpuid
+ test ebx, FLAG_CPUID7_EBX_AVX2
+ lea mbin_rbx, [%5 WRT_OPT] ; AVX (gen4) opt func
+ cmovne mbin_rsi, mbin_rbx
+
+ ;; Does it have xmm and ymm support
+ xor ecx, ecx
+ xgetbv
+ and eax, FLAG_XGETBV_EAX_XMM_YMM
+ cmp eax, FLAG_XGETBV_EAX_XMM_YMM
+ je _%1_init_done
+ lea mbin_rsi, [%3 WRT_OPT]
+
+ _%1_init_done:
+ pop mbin_rdx
+ pop mbin_rcx
+ pop mbin_rbx
+ pop mbin_rax
+ mov [%1_dispatched], mbin_rsi
+ pop mbin_rsi
+ ret
+%endmacro
+
+%if AS_FEATURE_LEVEL >= 6
+;;;;;
+; mbin_dispatch_init6 parameters
+; 1-> function name
+; 2-> base function
+; 3-> SSE4_2 or 00/01 optimized function
+; 4-> AVX/02 opt func
+; 5-> AVX2/04 opt func
+; 6-> AVX512/06 opt func
+;;;;;
+%macro mbin_dispatch_init6 6
+ section .text
+ %1_dispatch_init:
+ push mbin_rsi
+ push mbin_rax
+ push mbin_rbx
+ push mbin_rcx
+ push mbin_rdx
+ push mbin_rdi
+ lea mbin_rsi, [%2 WRT_OPT] ; Default - use base function
+
+ mov eax, 1
+ cpuid
+ mov ebx, ecx ; save cpuid1.ecx
+ test ecx, FLAG_CPUID1_ECX_SSE4_2
+ je _%1_init_done ; Use base function if no SSE4_2
+ lea mbin_rsi, [%3 WRT_OPT] ; SSE possible so use 00/01 opt
+
+ ;; Test for XMM_YMM support/AVX
+ test ecx, FLAG_CPUID1_ECX_OSXSAVE
+ je _%1_init_done
+ xor ecx, ecx
+ xgetbv ; xcr -> edx:eax
+ mov edi, eax ; save xgetvb.eax
+
+ and eax, FLAG_XGETBV_EAX_XMM_YMM
+ cmp eax, FLAG_XGETBV_EAX_XMM_YMM
+ jne _%1_init_done
+ test ebx, FLAG_CPUID1_ECX_AVX
+ je _%1_init_done
+ lea mbin_rsi, [%4 WRT_OPT] ; AVX/02 opt
+
+ ;; Test for AVX2
+ xor ecx, ecx
+ mov eax, 7
+ cpuid
+ test ebx, FLAG_CPUID7_EBX_AVX2
+ je _%1_init_done ; No AVX2 possible
+ lea mbin_rsi, [%5 WRT_OPT] ; AVX2/04 opt func
+
+ ;; Test for AVX512
+ and edi, FLAG_XGETBV_EAX_ZMM_OPM
+ cmp edi, FLAG_XGETBV_EAX_ZMM_OPM
+ jne _%1_init_done ; No AVX512 possible
+ and ebx, FLAGS_CPUID7_EBX_AVX512_G1
+ cmp ebx, FLAGS_CPUID7_EBX_AVX512_G1
+ lea mbin_rbx, [%6 WRT_OPT] ; AVX512/06 opt
+ cmove mbin_rsi, mbin_rbx
+
+ _%1_init_done:
+ pop mbin_rdi
+ pop mbin_rdx
+ pop mbin_rcx
+ pop mbin_rbx
+ pop mbin_rax
+ mov [%1_dispatched], mbin_rsi
+ pop mbin_rsi
+ ret
+%endmacro
+
+%else
+%macro mbin_dispatch_init6 6
+ mbin_dispatch_init5 %1, %2, %3, %4, %5
+%endmacro
+%endif
+
+%if AS_FEATURE_LEVEL >= 10
+;;;;;
+; mbin_dispatch_init7 parameters
+; 1-> function name
+; 2-> base function
+; 3-> SSE4_2 or 00/01 optimized function
+; 4-> AVX/02 opt func
+; 5-> AVX2/04 opt func
+; 6-> AVX512/06 opt func
+; 7-> AVX512 Update/10 opt func
+;;;;;
+%macro mbin_dispatch_init7 7
+ section .text
+ %1_dispatch_init:
+ push mbin_rsi
+ push mbin_rax
+ push mbin_rbx
+ push mbin_rcx
+ push mbin_rdx
+ push mbin_rdi
+ lea mbin_rsi, [%2 WRT_OPT] ; Default - use base function
+
+ mov eax, 1
+ cpuid
+ mov ebx, ecx ; save cpuid1.ecx
+ test ecx, FLAG_CPUID1_ECX_SSE4_2
+ je _%1_init_done ; Use base function if no SSE4_2
+ lea mbin_rsi, [%3 WRT_OPT] ; SSE possible so use 00/01 opt
+
+ ;; Test for XMM_YMM support/AVX
+ test ecx, FLAG_CPUID1_ECX_OSXSAVE
+ je _%1_init_done
+ xor ecx, ecx
+ xgetbv ; xcr -> edx:eax
+ mov edi, eax ; save xgetvb.eax
+
+ and eax, FLAG_XGETBV_EAX_XMM_YMM
+ cmp eax, FLAG_XGETBV_EAX_XMM_YMM
+ jne _%1_init_done
+ test ebx, FLAG_CPUID1_ECX_AVX
+ je _%1_init_done
+ lea mbin_rsi, [%4 WRT_OPT] ; AVX/02 opt
+
+ ;; Test for AVX2
+ xor ecx, ecx
+ mov eax, 7
+ cpuid
+ test ebx, FLAG_CPUID7_EBX_AVX2
+ je _%1_init_done ; No AVX2 possible
+ lea mbin_rsi, [%5 WRT_OPT] ; AVX2/04 opt func
+
+ ;; Test for AVX512
+ and edi, FLAG_XGETBV_EAX_ZMM_OPM
+ cmp edi, FLAG_XGETBV_EAX_ZMM_OPM
+ jne _%1_init_done ; No AVX512 possible
+ and ebx, FLAGS_CPUID7_EBX_AVX512_G1
+ cmp ebx, FLAGS_CPUID7_EBX_AVX512_G1
+ lea mbin_rbx, [%6 WRT_OPT] ; AVX512/06 opt
+ cmove mbin_rsi, mbin_rbx
+
+ and ecx, FLAGS_CPUID7_ECX_AVX512_G2
+ cmp ecx, FLAGS_CPUID7_ECX_AVX512_G2
+ lea mbin_rbx, [%7 WRT_OPT] ; AVX512/06 opt
+ cmove mbin_rsi, mbin_rbx
+
+ _%1_init_done:
+ pop mbin_rdi
+ pop mbin_rdx
+ pop mbin_rcx
+ pop mbin_rbx
+ pop mbin_rax
+ mov [%1_dispatched], mbin_rsi
+ pop mbin_rsi
+ ret
+%endmacro
+%else
+%macro mbin_dispatch_init7 7
+ mbin_dispatch_init6 %1, %2, %3, %4, %5, %6
+%endmacro
+%endif
+
+%endif ; ifndef _MULTIBINARY_ASM_
diff --git a/contrib/libs/isa-l/include/reg_sizes.asm b/contrib/libs/isa-l/include/reg_sizes.asm
new file mode 100644
index 0000000000..fec6a8aafb
--- /dev/null
+++ b/contrib/libs/isa-l/include/reg_sizes.asm
@@ -0,0 +1,248 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Copyright(c) 2011-2015 Intel Corporation All rights reserved.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions
+; are met:
+; * Redistributions of source code must retain the above copyright
+; notice, this list of conditions and the following disclaimer.
+; * Redistributions in binary form must reproduce the above copyright
+; notice, this list of conditions and the following disclaimer in
+; the documentation and/or other materials provided with the
+; distribution.
+; * Neither the name of Intel Corporation nor the names of its
+; contributors may be used to endorse or promote products derived
+; from this software without specific prior written permission.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+%ifndef _REG_SIZES_ASM_
+%define _REG_SIZES_ASM_
+
+%ifdef __NASM_VER__
+%ifidn __OUTPUT_FORMAT__, win64
+%error nasm not supported in windows
+%else
+%define endproc_frame
+%endif
+%endif
+
+%ifndef AS_FEATURE_LEVEL
+%define AS_FEATURE_LEVEL 4
+%endif
+
+%define EFLAGS_HAS_CPUID (1<<21)
+%define FLAG_CPUID1_ECX_CLMUL (1<<1)
+%define FLAG_CPUID1_EDX_SSE2 (1<<26)
+%define FLAG_CPUID1_ECX_SSE3 (1)
+%define FLAG_CPUID1_ECX_SSE4_1 (1<<19)
+%define FLAG_CPUID1_ECX_SSE4_2 (1<<20)
+%define FLAG_CPUID1_ECX_POPCNT (1<<23)
+%define FLAG_CPUID1_ECX_AESNI (1<<25)
+%define FLAG_CPUID1_ECX_OSXSAVE (1<<27)
+%define FLAG_CPUID1_ECX_AVX (1<<28)
+%define FLAG_CPUID1_EBX_AVX2 (1<<5)
+
+%define FLAG_CPUID7_EBX_AVX2 (1<<5)
+%define FLAG_CPUID7_EBX_AVX512F (1<<16)
+%define FLAG_CPUID7_EBX_AVX512DQ (1<<17)
+%define FLAG_CPUID7_EBX_AVX512IFMA (1<<21)
+%define FLAG_CPUID7_EBX_AVX512PF (1<<26)
+%define FLAG_CPUID7_EBX_AVX512ER (1<<27)
+%define FLAG_CPUID7_EBX_AVX512CD (1<<28)
+%define FLAG_CPUID7_EBX_AVX512BW (1<<30)
+%define FLAG_CPUID7_EBX_AVX512VL (1<<31)
+
+%define FLAG_CPUID7_ECX_AVX512VBMI (1<<1)
+%define FLAG_CPUID7_ECX_AVX512VBMI2 (1 << 6)
+%define FLAG_CPUID7_ECX_GFNI (1 << 8)
+%define FLAG_CPUID7_ECX_VAES (1 << 9)
+%define FLAG_CPUID7_ECX_VPCLMULQDQ (1 << 10)
+%define FLAG_CPUID7_ECX_VNNI (1 << 11)
+%define FLAG_CPUID7_ECX_BITALG (1 << 12)
+%define FLAG_CPUID7_ECX_VPOPCNTDQ (1 << 14)
+
+%define FLAGS_CPUID7_EBX_AVX512_G1 (FLAG_CPUID7_EBX_AVX512F | FLAG_CPUID7_EBX_AVX512VL | FLAG_CPUID7_EBX_AVX512BW | FLAG_CPUID7_EBX_AVX512CD | FLAG_CPUID7_EBX_AVX512DQ)
+%define FLAGS_CPUID7_ECX_AVX512_G2 (FLAG_CPUID7_ECX_AVX512VBMI2 | FLAG_CPUID7_ECX_GFNI | FLAG_CPUID7_ECX_VAES | FLAG_CPUID7_ECX_VPCLMULQDQ | FLAG_CPUID7_ECX_VNNI | FLAG_CPUID7_ECX_BITALG | FLAG_CPUID7_ECX_VPOPCNTDQ)
+
+%define FLAG_XGETBV_EAX_XMM (1<<1)
+%define FLAG_XGETBV_EAX_YMM (1<<2)
+%define FLAG_XGETBV_EAX_XMM_YMM 0x6
+%define FLAG_XGETBV_EAX_ZMM_OPM 0xe0
+
+%define FLAG_CPUID1_EAX_AVOTON 0x000406d0
+%define FLAG_CPUID1_EAX_STEP_MASK 0xfffffff0
+
+; define d and w variants for registers
+
+%define raxd eax
+%define raxw ax
+%define raxb al
+
+%define rbxd ebx
+%define rbxw bx
+%define rbxb bl
+
+%define rcxd ecx
+%define rcxw cx
+%define rcxb cl
+
+%define rdxd edx
+%define rdxw dx
+%define rdxb dl
+
+%define rsid esi
+%define rsiw si
+%define rsib sil
+
+%define rdid edi
+%define rdiw di
+%define rdib dil
+
+%define rbpd ebp
+%define rbpw bp
+%define rbpb bpl
+
+%define ymm0x xmm0
+%define ymm1x xmm1
+%define ymm2x xmm2
+%define ymm3x xmm3
+%define ymm4x xmm4
+%define ymm5x xmm5
+%define ymm6x xmm6
+%define ymm7x xmm7
+%define ymm8x xmm8
+%define ymm9x xmm9
+%define ymm10x xmm10
+%define ymm11x xmm11
+%define ymm12x xmm12
+%define ymm13x xmm13
+%define ymm14x xmm14
+%define ymm15x xmm15
+
+%define zmm0x xmm0
+%define zmm1x xmm1
+%define zmm2x xmm2
+%define zmm3x xmm3
+%define zmm4x xmm4
+%define zmm5x xmm5
+%define zmm6x xmm6
+%define zmm7x xmm7
+%define zmm8x xmm8
+%define zmm9x xmm9
+%define zmm10x xmm10
+%define zmm11x xmm11
+%define zmm12x xmm12
+%define zmm13x xmm13
+%define zmm14x xmm14
+%define zmm15x xmm15
+%define zmm16x xmm16
+%define zmm17x xmm17
+%define zmm18x xmm18
+%define zmm19x xmm19
+%define zmm20x xmm20
+%define zmm21x xmm21
+%define zmm22x xmm22
+%define zmm23x xmm23
+%define zmm24x xmm24
+%define zmm25x xmm25
+%define zmm26x xmm26
+%define zmm27x xmm27
+%define zmm28x xmm28
+%define zmm29x xmm29
+%define zmm30x xmm30
+%define zmm31x xmm31
+
+%define zmm0y ymm0
+%define zmm1y ymm1
+%define zmm2y ymm2
+%define zmm3y ymm3
+%define zmm4y ymm4
+%define zmm5y ymm5
+%define zmm6y ymm6
+%define zmm7y ymm7
+%define zmm8y ymm8
+%define zmm9y ymm9
+%define zmm10y ymm10
+%define zmm11y ymm11
+%define zmm12y ymm12
+%define zmm13y ymm13
+%define zmm14y ymm14
+%define zmm15y ymm15
+%define zmm16y ymm16
+%define zmm17y ymm17
+%define zmm18y ymm18
+%define zmm19y ymm19
+%define zmm20y ymm20
+%define zmm21y ymm21
+%define zmm22y ymm22
+%define zmm23y ymm23
+%define zmm24y ymm24
+%define zmm25y ymm25
+%define zmm26y ymm26
+%define zmm27y ymm27
+%define zmm28y ymm28
+%define zmm29y ymm29
+%define zmm30y ymm30
+%define zmm31y ymm31
+
+%define DWORD(reg) reg %+ d
+%define WORD(reg) reg %+ w
+%define BYTE(reg) reg %+ b
+
+%define XWORD(reg) reg %+ x
+
+%ifidn __OUTPUT_FORMAT__,elf32
+section .note.GNU-stack noalloc noexec nowrite progbits
+section .text
+%endif
+%ifidn __OUTPUT_FORMAT__,elf64
+section .note.GNU-stack noalloc noexec nowrite progbits
+section .text
+%endif
+
+%ifdef REL_TEXT
+ %define WRT_OPT
+%elifidn __OUTPUT_FORMAT__, elf64
+ %define WRT_OPT wrt ..plt
+%else
+ %define WRT_OPT
+%endif
+
+%ifidn __OUTPUT_FORMAT__, macho64
+ %define elf64 macho64
+ mac_equ equ 1
+ %ifdef __NASM_VER__
+ %define ISAL_SYM_TYPE_FUNCTION
+ %define ISAL_SYM_TYPE_DATA_INTERNAL
+ %else
+ %define ISAL_SYM_TYPE_FUNCTION function
+ %define ISAL_SYM_TYPE_DATA_INTERNAL data internal
+ %endif
+%else
+ %define ISAL_SYM_TYPE_FUNCTION function
+ %define ISAL_SYM_TYPE_DATA_INTERNAL data internal
+%endif
+
+%macro slversion 4
+ section .text
+ global %1_slver_%2%3%4
+ global %1_slver
+ %1_slver:
+ %1_slver_%2%3%4:
+ dw 0x%4
+ db 0x%3, 0x%2
+%endmacro
+
+%endif ; ifndef _REG_SIZES_ASM_
diff --git a/contrib/libs/pfr/CMakeLists.linux-aarch64.txt b/contrib/libs/pfr/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..a959a566f2
--- /dev/null
+++ b/contrib/libs/pfr/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,18 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(contrib-libs-pfr INTERFACE)
+target_include_directories(contrib-libs-pfr INTERFACE
+ ${CMAKE_SOURCE_DIR}/contrib/libs/pfr/include
+)
+target_link_libraries(contrib-libs-pfr INTERFACE
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+)
diff --git a/contrib/libs/pfr/CMakeLists.linux-x86_64.txt b/contrib/libs/pfr/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..a959a566f2
--- /dev/null
+++ b/contrib/libs/pfr/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,18 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(contrib-libs-pfr INTERFACE)
+target_include_directories(contrib-libs-pfr INTERFACE
+ ${CMAKE_SOURCE_DIR}/contrib/libs/pfr/include
+)
+target_link_libraries(contrib-libs-pfr INTERFACE
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+)
diff --git a/contrib/libs/pfr/CMakeLists.txt b/contrib/libs/pfr/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/contrib/libs/pfr/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/contrib/libs/pfr/LICENSE_1_0.txt b/contrib/libs/pfr/LICENSE_1_0.txt
new file mode 100644
index 0000000000..36b7cd93cd
--- /dev/null
+++ b/contrib/libs/pfr/LICENSE_1_0.txt
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/contrib/libs/pfr/README.md b/contrib/libs/pfr/README.md
new file mode 100644
index 0000000000..0a83f4b983
--- /dev/null
+++ b/contrib/libs/pfr/README.md
@@ -0,0 +1,104 @@
+# [PFR](https://apolukhin.github.io/pfr_non_boost/)
+
+This is a C++14 library for very basic reflection that gives you access to structure elements by index and provides other `std::tuple` like methods for user defined types without any macro or boilerplate code.
+
+[Boost.PFR](https://boost.org/libs/pfr) is a part of the [Boost C++ Libraries](https://github.com/boostorg). However, Boost.PFR is a header only library that does not depend on Boost. You can just copy the content of the "include" folder from the github into your project, and the library will work fine.
+
+For a version of the library without `boost::` namespace see [PFR](https://github.com/apolukhin/pfr_non_boost).
+
+### Test results
+
+Branches | Build | Tests coverage | More info
+----------------|-------------- | -------------- |-----------
+Develop: | [![CI](https://github.com/boostorg/pfr/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/boostorg/pfr/actions/workflows/ci.yml) [![Build status](https://ci.appveyor.com/api/projects/status/0mavmnkdmltcdmqa/branch/develop?svg=true)](https://ci.appveyor.com/project/apolukhin/pfr/branch/develop) | [![Coverage Status](https://coveralls.io/repos/github/apolukhin/magic_get/badge.png?branch=develop)](https://coveralls.io/github/apolukhin/magic_get?branch=develop) | [details...](https://www.boost.org/development/tests/develop/developer/pfr.html)
+Master: | [![CI](https://github.com/boostorg/pfr/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/boostorg/pfr/actions/workflows/ci.yml) [![Build status](https://ci.appveyor.com/api/projects/status/0mavmnkdmltcdmqa/branch/master?svg=true)](https://ci.appveyor.com/project/apolukhin/pfr/branch/master) | [![Coverage Status](https://coveralls.io/repos/github/apolukhin/magic_get/badge.png?branch=master)](https://coveralls.io/github/apolukhin/magic_get?branch=master) | [details...](https://www.boost.org/development/tests/master/developer/pfr.html)
+
+[Latest developer documentation](https://www.boost.org/doc/libs/develop/doc/html/boost_pfr.html)
+
+### Motivating Example #0
+```c++
+#include <iostream>
+#include <fstream>
+#include <string>
+
+#include "pfr.hpp"
+
+struct some_person {
+ std::string name;
+ unsigned birth_year;
+};
+
+int main(int argc, const char* argv[]) {
+ some_person val{"Edgar Allan Poe", 1809};
+
+ std::cout << pfr::get<0>(val) // No macro!
+ << " was born in " << pfr::get<1>(val); // Works with any aggregate initializables!
+
+ if (argc > 1) {
+ std::ofstream ofs(argv[1]);
+ ofs << pfr::io(val); // File now contains: {"Edgar Allan Poe", 1809}
+ }
+}
+```
+Outputs:
+```
+Edgar Allan Poe was born in 1809
+```
+
+
+### Motivating Example #1
+```c++
+#include <iostream>
+#include "pfr/precise.hpp"
+
+struct my_struct { // no ostream operator defined!
+ int i;
+ char c;
+ double d;
+};
+
+int main() {
+ my_struct s{100, 'H', 3.141593};
+ std::cout << "my_struct has " << pfr::tuple_size<my_struct>::value
+ << " fields: " << pfr::io(s) << "\n";
+}
+
+```
+
+Outputs:
+```
+my_struct has 3 fields: {100, H, 3.14159}
+```
+
+### Motivating Example #2
+
+```c++
+#include <iostream>
+#include "pfr/precise.hpp"
+
+struct my_struct { // no ostream operator defined!
+ std::string s;
+ int i;
+};
+
+int main() {
+ my_struct s{{"Das ist fantastisch!"}, 100};
+ std::cout << "my_struct has " << pfr::tuple_size<my_struct>::value
+ << " fields: " << pfr::io(s) << "\n";
+}
+
+```
+
+Outputs:
+```
+my_struct has 2 fields: {"Das ist fantastisch!", 100}
+```
+
+
+### Requirements and Limitations
+
+[See docs](https://www.boost.org/doc/libs/develop/doc/html/boost_pfr.html).
+
+### License
+
+Distributed under the [Boost Software License, Version 1.0](https://boost.org/LICENSE_1_0.txt).
diff --git a/contrib/libs/pfr/include/pfr/detail/config.hpp b/contrib/libs/pfr/include/pfr/detail/config.hpp
new file mode 100644
index 0000000000..ec2143d9ba
--- /dev/null
+++ b/contrib/libs/pfr/include/pfr/detail/config.hpp
@@ -0,0 +1,91 @@
+// Copyright (c) 2016-2021 Antony Polukhin
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef PFR_DETAIL_CONFIG_HPP
+#define PFR_DETAIL_CONFIG_HPP
+#pragma once
+
+#include <type_traits> // to get non standard platform macro definitions (__GLIBCXX__ for example)
+
+// Reminder:
+// * MSVC++ 14.2 _MSC_VER == 1927 <- Loophole is known to work (Visual Studio ????)
+// * MSVC++ 14.1 _MSC_VER == 1916 <- Loophole is known to NOT work (Visual Studio 2017)
+// * MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
+// * MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
+
+#if defined(_MSC_VER)
+# if !defined(_MSVC_LANG) || _MSC_VER <= 1900
+# error Boost.PFR library requires more modern MSVC compiler.
+# endif
+#elif __cplusplus < 201402L
+# error Boost.PFR library requires at least C++14.
+#endif
+
+#ifndef PFR_USE_LOOPHOLE
+# if defined(_MSC_VER)
+# if _MSC_VER >= 1927
+# define PFR_USE_LOOPHOLE 1
+# else
+# define PFR_USE_LOOPHOLE 0
+# endif
+# elif defined(__clang_major__) && __clang_major__ >= 8
+# define PFR_USE_LOOPHOLE 0
+# else
+# define PFR_USE_LOOPHOLE 1
+# endif
+#endif
+
+#ifndef PFR_USE_CPP17
+# ifdef __cpp_structured_bindings
+# define PFR_USE_CPP17 1
+# elif defined(_MSVC_LANG)
+# if _MSVC_LANG >= 201703L
+# define PFR_USE_CPP17 1
+# else
+# define PFR_USE_CPP17 0
+# endif
+# else
+# define PFR_USE_CPP17 0
+# endif
+#endif
+
+#if (!PFR_USE_CPP17 && !PFR_USE_LOOPHOLE)
+# if (defined(_MSC_VER) && _MSC_VER < 1916) ///< in Visual Studio 2017 v15.9 PFR library with classic engine normally works
+# error Boost.PFR requires /std:c++latest or /std:c++17 flags on your compiler.
+# endif
+#endif
+
+#ifndef PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE
+// Assume that libstdc++ since GCC-7.3 does not have linear instantiation depth in std::make_integral_sequence
+# if defined( __GLIBCXX__) && __GLIBCXX__ >= 20180101
+# define PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE 1
+# elif defined(_MSC_VER)
+# define PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE 1
+//# elif other known working lib
+# else
+# define PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE 0
+# endif
+#endif
+
+#ifndef PFR_HAS_GUARANTEED_COPY_ELISION
+# if defined(__cpp_guaranteed_copy_elision) && (!defined(_MSC_VER) || _MSC_VER > 1928)
+# define PFR_HAS_GUARANTEED_COPY_ELISION 1
+# else
+# define PFR_HAS_GUARANTEED_COPY_ELISION 0
+# endif
+#endif
+
+#if defined(__has_cpp_attribute)
+# if __has_cpp_attribute(maybe_unused)
+# define PFR_MAYBE_UNUSED [[maybe_unused]]
+# endif
+#endif
+
+#ifndef PFR_MAYBE_UNUSED
+# define PFR_MAYBE_UNUSED
+#endif
+
+
+#endif // PFR_DETAIL_CONFIG_HPP
diff --git a/contrib/libs/pfr/include/pfr/detail/fields_count.hpp b/contrib/libs/pfr/include/pfr/detail/fields_count.hpp
new file mode 100644
index 0000000000..d4ad82353a
--- /dev/null
+++ b/contrib/libs/pfr/include/pfr/detail/fields_count.hpp
@@ -0,0 +1,330 @@
+// Copyright (c) 2016-2021 Antony Polukhin
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef PFR_DETAIL_FIELDS_COUNT_HPP
+#define PFR_DETAIL_FIELDS_COUNT_HPP
+#pragma once
+
+#include <pfr/detail/config.hpp>
+#include <pfr/detail/make_integer_sequence.hpp>
+#include <pfr/detail/size_t_.hpp>
+#include <pfr/detail/unsafe_declval.hpp>
+
+#include <climits> // CHAR_BIT
+#include <type_traits>
+#include <utility> // metaprogramming stuff
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wmissing-braces"
+# pragma clang diagnostic ignored "-Wundefined-inline"
+# pragma clang diagnostic ignored "-Wundefined-internal"
+# pragma clang diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+namespace pfr { namespace detail {
+
+///////////////////// Structure that can be converted to reference to anything
+struct ubiq_lref_constructor {
+ std::size_t ignore;
+ template <class Type> constexpr operator Type&() const && noexcept { // tweak for template_unconstrained.cpp like cases
+ return detail::unsafe_declval<Type&>();
+ }
+
+ template <class Type> constexpr operator Type&() const & noexcept { // tweak for optional_chrono.cpp like cases
+ return detail::unsafe_declval<Type&>();
+ }
+};
+
+///////////////////// Structure that can be converted to rvalue reference to anything
+struct ubiq_rref_constructor {
+ std::size_t ignore;
+ template <class Type> /*constexpr*/ operator Type() const && noexcept { // Allows initialization of rvalue reference fields and move-only types
+ return detail::unsafe_declval<Type>();
+ }
+};
+
+
+#ifndef __cpp_lib_is_aggregate
+///////////////////// Hand-made is_aggregate_initializable_n<T> trait
+
+// Structure that can be converted to reference to anything except reference to T
+template <class T, bool IsCopyConstructible>
+struct ubiq_constructor_except {
+ std::size_t ignore;
+ template <class Type> constexpr operator std::enable_if_t<!std::is_same<T, Type>::value, Type&> () const noexcept; // Undefined
+};
+
+template <class T>
+struct ubiq_constructor_except<T, false> {
+ std::size_t ignore;
+ template <class Type> constexpr operator std::enable_if_t<!std::is_same<T, Type>::value, Type&&> () const noexcept; // Undefined
+};
+
+
+// `std::is_constructible<T, ubiq_constructor_except<T>>` consumes a lot of time, so we made a separate lazy trait for it.
+template <std::size_t N, class T> struct is_single_field_and_aggregate_initializable: std::false_type {};
+template <class T> struct is_single_field_and_aggregate_initializable<1, T>: std::integral_constant<
+ bool, !std::is_constructible<T, ubiq_constructor_except<T, std::is_copy_constructible<T>::value>>::value
+> {};
+
+// Hand-made is_aggregate<T> trait:
+// Before C++20 aggregates could be constructed from `decltype(ubiq_?ref_constructor{I})...` but type traits report that
+// there's no constructor from `decltype(ubiq_?ref_constructor{I})...`
+// Special case for N == 1: `std::is_constructible<T, ubiq_?ref_constructor>` returns true if N == 1 and T is copy/move constructible.
+template <class T, std::size_t N>
+struct is_aggregate_initializable_n {
+ template <std::size_t ...I>
+ static constexpr bool is_not_constructible_n(std::index_sequence<I...>) noexcept {
+ return (!std::is_constructible<T, decltype(ubiq_lref_constructor{I})...>::value && !std::is_constructible<T, decltype(ubiq_rref_constructor{I})...>::value)
+ || is_single_field_and_aggregate_initializable<N, T>::value
+ ;
+ }
+
+ static constexpr bool value =
+ std::is_empty<T>::value
+ || std::is_array<T>::value
+ || std::is_fundamental<T>::value
+ || is_not_constructible_n(detail::make_index_sequence<N>{})
+ ;
+};
+
+#endif // #ifndef __cpp_lib_is_aggregate
+
+///////////////////// Detect aggregates with inheritance
+template <class Derived, class U>
+constexpr bool static_assert_non_inherited() noexcept {
+ static_assert(
+ !std::is_base_of<U, Derived>::value,
+ "====================> Boost.PFR: Boost.PFR: Inherited types are not supported."
+ );
+ return true;
+}
+
+template <class Derived>
+struct ubiq_lref_base_asserting {
+ template <class Type> constexpr operator Type&() const && // tweak for template_unconstrained.cpp like cases
+ noexcept(detail::static_assert_non_inherited<Derived, Type>()) // force the computation of assert function
+ {
+ return detail::unsafe_declval<Type&>();
+ }
+
+ template <class Type> constexpr operator Type&() const & // tweak for optional_chrono.cpp like cases
+ noexcept(detail::static_assert_non_inherited<Derived, Type>()) // force the computation of assert function
+ {
+ return detail::unsafe_declval<Type&>();
+ }
+};
+
+template <class Derived>
+struct ubiq_rref_base_asserting {
+ template <class Type> /*constexpr*/ operator Type() const && // Allows initialization of rvalue reference fields and move-only types
+ noexcept(detail::static_assert_non_inherited<Derived, Type>()) // force the computation of assert function
+ {
+ return detail::unsafe_declval<Type>();
+ }
+};
+
+template <class T, std::size_t I0, std::size_t... I, class /*Enable*/ = typename std::enable_if<std::is_copy_constructible<T>::value>::type>
+constexpr auto assert_first_not_base(std::index_sequence<I0, I...>) noexcept
+ -> typename std::add_pointer<decltype(T{ ubiq_lref_base_asserting<T>{}, ubiq_lref_constructor{I}... })>::type
+{
+ return nullptr;
+}
+
+template <class T, std::size_t I0, std::size_t... I, class /*Enable*/ = typename std::enable_if<!std::is_copy_constructible<T>::value>::type>
+constexpr auto assert_first_not_base(std::index_sequence<I0, I...>) noexcept
+ -> typename std::add_pointer<decltype(T{ ubiq_rref_base_asserting<T>{}, ubiq_rref_constructor{I}... })>::type
+{
+ return nullptr;
+}
+
+template <class T>
+constexpr void* assert_first_not_base(std::index_sequence<>) noexcept
+{
+ return nullptr;
+}
+
+///////////////////// Helper for SFINAE on fields count
+template <class T, std::size_t... I, class /*Enable*/ = typename std::enable_if<std::is_copy_constructible<T>::value>::type>
+constexpr auto enable_if_constructible_helper(std::index_sequence<I...>) noexcept
+ -> typename std::add_pointer<decltype(T{ ubiq_lref_constructor{I}... })>::type;
+
+template <class T, std::size_t... I, class /*Enable*/ = typename std::enable_if<!std::is_copy_constructible<T>::value>::type>
+constexpr auto enable_if_constructible_helper(std::index_sequence<I...>) noexcept
+ -> typename std::add_pointer<decltype(T{ ubiq_rref_constructor{I}... })>::type;
+
+template <class T, std::size_t N, class /*Enable*/ = decltype( enable_if_constructible_helper<T>(detail::make_index_sequence<N>()) ) >
+using enable_if_constructible_helper_t = std::size_t;
+
+///////////////////// Helpers for range size detection
+template <std::size_t Begin, std::size_t Last>
+using is_one_element_range = std::integral_constant<bool, Begin == Last>;
+
+using multi_element_range = std::false_type;
+using one_element_range = std::true_type;
+
+///////////////////// Non greedy fields count search. Templates instantiation depth is log(sizeof(T)), templates instantiation count is log(sizeof(T)).
+template <class T, std::size_t Begin, std::size_t Middle>
+constexpr std::size_t detect_fields_count(detail::one_element_range, long) noexcept {
+ static_assert(
+ Begin == Middle,
+ "====================> Boost.PFR: Internal logic error."
+ );
+ return Begin;
+}
+
+template <class T, std::size_t Begin, std::size_t Middle>
+constexpr std::size_t detect_fields_count(detail::multi_element_range, int) noexcept;
+
+template <class T, std::size_t Begin, std::size_t Middle>
+constexpr auto detect_fields_count(detail::multi_element_range, long) noexcept
+ -> detail::enable_if_constructible_helper_t<T, Middle>
+{
+ constexpr std::size_t next_v = Middle + (Middle - Begin + 1) / 2;
+ return detail::detect_fields_count<T, Middle, next_v>(detail::is_one_element_range<Middle, next_v>{}, 1L);
+}
+
+template <class T, std::size_t Begin, std::size_t Middle>
+constexpr std::size_t detect_fields_count(detail::multi_element_range, int) noexcept {
+ constexpr std::size_t next_v = Begin + (Middle - Begin) / 2;
+ return detail::detect_fields_count<T, Begin, next_v>(detail::is_one_element_range<Begin, next_v>{}, 1L);
+}
+
+///////////////////// Greedy search. Templates instantiation depth is log(sizeof(T)), templates instantiation count is log(sizeof(T))*T in worst case.
+template <class T, std::size_t N>
+constexpr auto detect_fields_count_greedy_remember(long) noexcept
+ -> detail::enable_if_constructible_helper_t<T, N>
+{
+ return N;
+}
+
+template <class T, std::size_t N>
+constexpr std::size_t detect_fields_count_greedy_remember(int) noexcept {
+ return 0;
+}
+
+template <class T, std::size_t Begin, std::size_t Last>
+constexpr std::size_t detect_fields_count_greedy(detail::one_element_range) noexcept {
+ static_assert(
+ Begin == Last,
+ "====================> Boost.PFR: Internal logic error."
+ );
+ return detail::detect_fields_count_greedy_remember<T, Begin>(1L);
+}
+
+template <class T, std::size_t Begin, std::size_t Last>
+constexpr std::size_t detect_fields_count_greedy(detail::multi_element_range) noexcept {
+ constexpr std::size_t middle = Begin + (Last - Begin) / 2;
+ constexpr std::size_t fields_count_big_range = detail::detect_fields_count_greedy<T, middle + 1, Last>(
+ detail::is_one_element_range<middle + 1, Last>{}
+ );
+
+ constexpr std::size_t small_range_begin = (fields_count_big_range ? 0 : Begin);
+ constexpr std::size_t small_range_last = (fields_count_big_range ? 0 : middle);
+ constexpr std::size_t fields_count_small_range = detail::detect_fields_count_greedy<T, small_range_begin, small_range_last>(
+ detail::is_one_element_range<small_range_begin, small_range_last>{}
+ );
+ return fields_count_big_range ? fields_count_big_range : fields_count_small_range;
+}
+
+///////////////////// Choosing between array size, greedy and non greedy search.
+template <class T, std::size_t N>
+constexpr auto detect_fields_count_dispatch(size_t_<N>, long, long) noexcept
+ -> typename std::enable_if<std::is_array<T>::value, std::size_t>::type
+{
+ return sizeof(T) / sizeof(typename std::remove_all_extents<T>::type);
+}
+
+template <class T, std::size_t N>
+constexpr auto detect_fields_count_dispatch(size_t_<N>, long, int) noexcept
+ -> decltype(sizeof(T{}))
+{
+ constexpr std::size_t middle = N / 2 + 1;
+ return detail::detect_fields_count<T, 0, middle>(detail::multi_element_range{}, 1L);
+}
+
+template <class T, std::size_t N>
+constexpr std::size_t detect_fields_count_dispatch(size_t_<N>, int, int) noexcept {
+ // T is not default aggregate initialzable. It means that at least one of the members is not default constructible,
+ // so we have to check all the aggregate initializations for T up to N parameters and return the bigest succeeded
+ // (we can not use binary search for detecting fields count).
+ return detail::detect_fields_count_greedy<T, 0, N>(detail::multi_element_range{});
+}
+
+///////////////////// Returns fields count
+template <class T>
+constexpr std::size_t fields_count() noexcept {
+ using type = std::remove_cv_t<T>;
+
+ static_assert(
+ !std::is_reference<type>::value,
+ "====================> Boost.PFR: Attempt to get fields count on a reference. This is not allowed because that could hide an issue and different library users expect different behavior in that case."
+ );
+
+#if !PFR_HAS_GUARANTEED_COPY_ELISION
+ static_assert(
+ std::is_copy_constructible<std::remove_all_extents_t<type>>::value || (
+ std::is_move_constructible<std::remove_all_extents_t<type>>::value
+ && std::is_move_assignable<std::remove_all_extents_t<type>>::value
+ ),
+ "====================> Boost.PFR: Type and each field in the type must be copy constructible (or move constructible and move assignable)."
+ );
+#endif // #if !PFR_HAS_GUARANTEED_COPY_ELISION
+
+ static_assert(
+ !std::is_polymorphic<type>::value,
+ "====================> Boost.PFR: Type must have no virtual function, because otherwise it is not aggregate initializable."
+ );
+
+#ifdef __cpp_lib_is_aggregate
+ static_assert(
+ std::is_aggregate<type>::value // Does not return `true` for built-in types.
+ || std::is_scalar<type>::value,
+ "====================> Boost.PFR: Type must be aggregate initializable."
+ );
+#endif
+
+// Can't use the following. See the non_std_layout.cpp test.
+//#if !PFR_USE_CPP17
+// static_assert(
+// std::is_standard_layout<type>::value, // Does not return `true` for structs that have non standard layout members.
+// "Type must be aggregate initializable."
+// );
+//#endif
+
+#if defined(_MSC_VER) && (_MSC_VER <= 1920)
+ // Workaround for msvc compilers. Versions <= 1920 have a limit of max 1024 elements in template parameter pack
+ constexpr std::size_t max_fields_count = (sizeof(type) * CHAR_BIT >= 1024 ? 1024 : sizeof(type) * CHAR_BIT);
+#else
+ constexpr std::size_t max_fields_count = (sizeof(type) * CHAR_BIT); // We multiply by CHAR_BIT because the type may have bitfields in T
+#endif
+
+ constexpr std::size_t result = detail::detect_fields_count_dispatch<type>(size_t_<max_fields_count>{}, 1L, 1L);
+
+ detail::assert_first_not_base<type>(detail::make_index_sequence<result>{});
+
+#ifndef __cpp_lib_is_aggregate
+ static_assert(
+ is_aggregate_initializable_n<type, result>::value,
+ "====================> Boost.PFR: Types with user specified constructors (non-aggregate initializable types) are not supported."
+ );
+#endif
+
+ static_assert(
+ result != 0 || std::is_empty<type>::value || std::is_fundamental<type>::value || std::is_reference<type>::value,
+ "====================> Boost.PFR: If there's no other failed static asserts then something went wrong. Please report this issue to the github along with the structure you're reflecting."
+ );
+
+ return result;
+}
+
+}} // namespace pfr::detail
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+#endif // PFR_DETAIL_FIELDS_COUNT_HPP
diff --git a/contrib/libs/pfr/include/pfr/detail/make_integer_sequence.hpp b/contrib/libs/pfr/include/pfr/detail/make_integer_sequence.hpp
new file mode 100644
index 0000000000..b1af047d2f
--- /dev/null
+++ b/contrib/libs/pfr/include/pfr/detail/make_integer_sequence.hpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2018 Sergei Fedorov
+// Copyright (c) 2019-2021 Antony Polukhin
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef PFR_DETAIL_MAKE_INTEGER_SEQUENCE_HPP
+#define PFR_DETAIL_MAKE_INTEGER_SEQUENCE_HPP
+#pragma once
+
+#include <pfr/detail/config.hpp>
+
+#include <type_traits>
+#include <utility>
+#include <cstddef>
+
+namespace pfr { namespace detail {
+
+#if PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE == 0
+
+#ifdef __has_builtin
+# if __has_builtin(__make_integer_seq)
+# define PFR_USE_MAKE_INTEGER_SEQ_BUILTIN
+# endif
+#endif
+
+#ifdef PFR_USE_MAKE_INTEGER_SEQ_BUILTIN
+
+using std::integer_sequence;
+
+// Clang unable to use namespace qualified std::integer_sequence in __make_integer_seq.
+template <typename T, T N>
+using make_integer_sequence = __make_integer_seq<integer_sequence, T, N>;
+
+#undef PFR_USE_MAKE_INTEGER_SEQ_BUILTIN
+
+#else
+
+template <typename T, typename U>
+struct join_sequences;
+
+template <typename T, T... A, T... B>
+struct join_sequences<std::integer_sequence<T, A...>, std::integer_sequence<T, B...>> {
+ using type = std::integer_sequence<T, A..., B...>;
+};
+
+template <typename T, T Min, T Max>
+struct build_sequence_impl {
+ static_assert(Min < Max, "Start of range must be less than its end");
+ static constexpr T size = Max - Min;
+ using type = typename join_sequences<
+ typename build_sequence_impl<T, Min, Min + size / 2>::type,
+ typename build_sequence_impl<T, Min + size / 2 + 1, Max>::type
+ >::type;
+};
+
+template <typename T, T V>
+struct build_sequence_impl<T, V, V> {
+ using type = std::integer_sequence<T, V>;
+};
+
+template <typename T, std::size_t N>
+struct make_integer_sequence_impl : build_sequence_impl<T, 0, N - 1> {};
+
+template <typename T>
+struct make_integer_sequence_impl<T, 0> {
+ using type = std::integer_sequence<T>;
+};
+
+template <typename T, T N>
+using make_integer_sequence = typename make_integer_sequence_impl<T, N>::type;
+
+#endif // !defined PFR_USE_MAKE_INTEGER_SEQ_BUILTIN
+#else // PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE == 1
+
+template <typename T, T N>
+using make_integer_sequence = std::make_integer_sequence<T, N>;
+
+#endif // PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE == 1
+
+template <std::size_t N>
+using make_index_sequence = make_integer_sequence<std::size_t, N>;
+
+template <typename... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+}} // namespace pfr::detail
+
+#endif
+
diff --git a/contrib/libs/pfr/include/pfr/detail/sequence_tuple.hpp b/contrib/libs/pfr/include/pfr/detail/sequence_tuple.hpp
new file mode 100644
index 0000000000..3ccd130914
--- /dev/null
+++ b/contrib/libs/pfr/include/pfr/detail/sequence_tuple.hpp
@@ -0,0 +1,127 @@
+// Copyright (c) 2016-2021 Antony Polukhin
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef PFR_DETAIL_SEQUENCE_TUPLE_HPP
+#define PFR_DETAIL_SEQUENCE_TUPLE_HPP
+#pragma once
+
+#include <pfr/detail/config.hpp>
+#include <pfr/detail/make_integer_sequence.hpp>
+
+#include <utility> // metaprogramming stuff
+#include <cstddef> // std::size_t
+
+///////////////////// Tuple that holds its values in the supplied order
+namespace pfr { namespace detail { namespace sequence_tuple {
+
+template <std::size_t N, class T>
+struct base_from_member {
+ T value;
+};
+
+template <class I, class ...Tail>
+struct tuple_base;
+
+
+
+template <std::size_t... I, class ...Tail>
+struct tuple_base< std::index_sequence<I...>, Tail... >
+ : base_from_member<I , Tail>...
+{
+ static constexpr std::size_t size_v = sizeof...(I);
+
+ // We do not use `noexcept` in the following functions, because if user forget to put one then clang will issue an error:
+ // "error: exception specification of explicitly defaulted default constructor does not match the calculated one".
+ constexpr tuple_base() = default;
+ constexpr tuple_base(tuple_base&&) = default;
+ constexpr tuple_base(const tuple_base&) = default;
+
+ constexpr tuple_base(Tail... v) noexcept
+ : base_from_member<I, Tail>{ v }...
+ {}
+};
+
+template <>
+struct tuple_base<std::index_sequence<> > {
+ static constexpr std::size_t size_v = 0;
+};
+
+template <std::size_t N, class T>
+constexpr T& get_impl(base_from_member<N, T>& t) noexcept {
+ return t.value;
+}
+
+template <std::size_t N, class T>
+constexpr const T& get_impl(const base_from_member<N, T>& t) noexcept {
+ return t.value;
+}
+
+template <std::size_t N, class T>
+constexpr volatile T& get_impl(volatile base_from_member<N, T>& t) noexcept {
+ return t.value;
+}
+
+template <std::size_t N, class T>
+constexpr const volatile T& get_impl(const volatile base_from_member<N, T>& t) noexcept {
+ return t.value;
+}
+
+template <std::size_t N, class T>
+constexpr T&& get_impl(base_from_member<N, T>&& t) noexcept {
+ return std::forward<T>(t.value);
+}
+
+
+template <class ...Values>
+struct tuple: tuple_base<
+ detail::index_sequence_for<Values...>,
+ Values...>
+{
+ using tuple_base<
+ detail::index_sequence_for<Values...>,
+ Values...
+ >::tuple_base;
+};
+
+
+template <std::size_t N, class ...T>
+constexpr decltype(auto) get(tuple<T...>& t) noexcept {
+ static_assert(N < tuple<T...>::size_v, "====================> Boost.PFR: Tuple index out of bounds");
+ return sequence_tuple::get_impl<N>(t);
+}
+
+template <std::size_t N, class ...T>
+constexpr decltype(auto) get(const tuple<T...>& t) noexcept {
+ static_assert(N < tuple<T...>::size_v, "====================> Boost.PFR: Tuple index out of bounds");
+ return sequence_tuple::get_impl<N>(t);
+}
+
+template <std::size_t N, class ...T>
+constexpr decltype(auto) get(const volatile tuple<T...>& t) noexcept {
+ static_assert(N < tuple<T...>::size_v, "====================> Boost.PFR: Tuple index out of bounds");
+ return sequence_tuple::get_impl<N>(t);
+}
+
+template <std::size_t N, class ...T>
+constexpr decltype(auto) get(volatile tuple<T...>& t) noexcept {
+ static_assert(N < tuple<T...>::size_v, "====================> Boost.PFR: Tuple index out of bounds");
+ return sequence_tuple::get_impl<N>(t);
+}
+
+template <std::size_t N, class ...T>
+constexpr decltype(auto) get(tuple<T...>&& t) noexcept {
+ static_assert(N < tuple<T...>::size_v, "====================> Boost.PFR: Tuple index out of bounds");
+ return sequence_tuple::get_impl<N>(std::move(t));
+}
+
+template <std::size_t I, class T>
+using tuple_element = std::remove_reference< decltype(
+ ::pfr::detail::sequence_tuple::get<I>( std::declval<T>() )
+ ) >;
+
+
+}}} // namespace pfr::detail::sequence_tuple
+
+#endif // PFR_CORE_HPP
diff --git a/contrib/libs/pfr/include/pfr/detail/size_t_.hpp b/contrib/libs/pfr/include/pfr/detail/size_t_.hpp
new file mode 100644
index 0000000000..da54096b0e
--- /dev/null
+++ b/contrib/libs/pfr/include/pfr/detail/size_t_.hpp
@@ -0,0 +1,18 @@
+// Copyright (c) 2016-2021 Antony Polukhin
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef PFR_DETAIL_SIZE_T_HPP
+#define PFR_DETAIL_SIZE_T_HPP
+#pragma once
+
+namespace pfr { namespace detail {
+
+///////////////////// General utility stuff
+template <std::size_t Index>
+using size_t_ = std::integral_constant<std::size_t, Index >;
+
+}} // namespace pfr::detail
+
+#endif // PFR_DETAIL_SIZE_T_HPP
diff --git a/contrib/libs/pfr/include/pfr/detail/unsafe_declval.hpp b/contrib/libs/pfr/include/pfr/detail/unsafe_declval.hpp
new file mode 100644
index 0000000000..3742abcd2a
--- /dev/null
+++ b/contrib/libs/pfr/include/pfr/detail/unsafe_declval.hpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2019-2021 Antony Polukhin.
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef PFR_DETAIL_UNSAFE_DECLVAL_HPP
+#define PFR_DETAIL_UNSAFE_DECLVAL_HPP
+
+#include <pfr/detail/config.hpp>
+
+#include <type_traits>
+
+namespace pfr { namespace detail {
+
+// This function serves as a link-time assert. If linker requires it, then
+// `unsafe_declval()` is used at runtime.
+void report_if_you_see_link_error_with_this_function() noexcept;
+
+// For returning non default constructible types. Do NOT use at runtime!
+//
+// GCCs std::declval may not be used in potentionally evaluated contexts,
+// so we reinvent it.
+template <class T>
+constexpr T unsafe_declval() noexcept {
+ report_if_you_see_link_error_with_this_function();
+
+ typename std::remove_reference<T>::type* ptr = 0;
+ ptr += 42; // suppresses 'null pointer dereference' warnings
+ return static_cast<T>(*ptr);
+}
+
+}} // namespace pfr::detail
+
+
+#endif // PFR_DETAIL_UNSAFE_DECLVAL_HPP
+
diff --git a/contrib/libs/pfr/include/pfr/tuple_size.hpp b/contrib/libs/pfr/include/pfr/tuple_size.hpp
new file mode 100644
index 0000000000..7ad8f08993
--- /dev/null
+++ b/contrib/libs/pfr/include/pfr/tuple_size.hpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2016-2021 Antony Polukhin
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+
+#ifndef PFR_TUPLE_SIZE_HPP
+#define PFR_TUPLE_SIZE_HPP
+#pragma once
+
+#include <pfr/detail/config.hpp>
+
+#include <type_traits>
+#include <utility> // metaprogramming stuff
+
+#include <pfr/detail/sequence_tuple.hpp>
+#include <pfr/detail/fields_count.hpp>
+
+/// \file pfr/tuple_size.hpp
+/// Contains tuple-like interfaces to get fields count \forcedlink{tuple_size}, \forcedlink{tuple_size_v}.
+///
+/// \b Synopsis:
+namespace pfr {
+
+/// Has a static const member variable `value` that contains fields count in a T.
+/// Works for any T that supports aggregate initialization.
+///
+/// \b Example:
+/// \code
+/// std::array<int, pfr::tuple_size<my_structure>::value > a;
+/// \endcode
+template <class T>
+using tuple_size = detail::size_t_< pfr::detail::fields_count<T>() >;
+
+
+/// `tuple_size_v` is a template variable that contains fields count in a T and
+/// works for any T that supports aggregate initialization.
+///
+/// \b Example:
+/// \code
+/// std::array<int, pfr::tuple_size_v<my_structure> > a;
+/// \endcode
+template <class T>
+constexpr std::size_t tuple_size_v = tuple_size<T>::value;
+
+} // namespace pfr
+
+#endif // PFR_TUPLE_SIZE_HPP
diff --git a/contrib/libs/pfr/ya.make b/contrib/libs/pfr/ya.make
new file mode 100644
index 0000000000..cdf2bfde6c
--- /dev/null
+++ b/contrib/libs/pfr/ya.make
@@ -0,0 +1,20 @@
+# Generated by devtools/yamaker from nixpkgs 22.05.
+
+LIBRARY()
+
+LICENSE(
+ BSL-1.0 AND
+ Public-Domain
+)
+
+LICENSE_TEXTS(.yandex_meta/licenses.list.txt)
+
+VERSION(2.0.3)
+
+ORIGINAL_SOURCE(https://github.com/apolukhin/pfr_non_boost/archive/2.0.3.tar.gz)
+
+ADDINCL(
+ GLOBAL contrib/libs/pfr/include
+)
+
+END()
diff --git a/library/cpp/erasure/README.md b/library/cpp/erasure/README.md
new file mode 100644
index 0000000000..7723bf7c23
--- /dev/null
+++ b/library/cpp/erasure/README.md
@@ -0,0 +1,9 @@
+# Erasure based codecs for arbitrary data
+
+C++ wrapper for LRC and Reed-Solomon erasure codecs.
+There are two backends for LRC: Jerasure(http://jerasure.org) and ISA-L(https://github.com/intel/isa-l). ISA-L is much faster - it condsiders different instrucion sets to optimize speed of encode and decode. The only limitations now are if you don't have SSE4.2 instruction set (then base variant is as slow as Jerasure) or if you run it on aarch64 architecture (however, 2.29 version will be going to support fast implementation). However, we still have to keep Jerasure because it is incompatible due to some optimization in it which affect data layout in coded blocks.
+Also see https://wiki.yandex-team.ru/yt/userdoc/erasure/, https://wiki.yandex-team.ru/ignatijjkolesnichenko/yt/erasure/.
+
+It is possible to use codecs LRC 2k-2-2 and Reed Solomon n-k for any data stream. All you need is to provide CodecTraits (see `codecs_ut.cpp` for examples). Note that ISA-L only supports WordSize equal to 8. If you use Jerasure codecs with bigger WordSize than `MaxWordSize` in `public.h`, codec is not guaranteed to be thread-safe.
+
+You can use interface in `codec.h` or use the exact codec from `lrc_isa.h`, `lrc_jerasure.h` and `reed_solomon.h` if you like.
diff --git a/library/cpp/erasure/codec.cpp b/library/cpp/erasure/codec.cpp
new file mode 100644
index 0000000000..5fbcd720b2
--- /dev/null
+++ b/library/cpp/erasure/codec.cpp
@@ -0,0 +1 @@
+#include "codec.h"
diff --git a/library/cpp/erasure/codec.h b/library/cpp/erasure/codec.h
new file mode 100644
index 0000000000..c03d25e9c8
--- /dev/null
+++ b/library/cpp/erasure/codec.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include "public.h"
+
+#include <optional>
+#include <vector>
+
+namespace NErasure {
+
+//! Describes a generic way to generate parity blocks from data blocks and
+//! to recover (repair) missing blocks.
+/*!
+ * Given N data blocks (numbered from 0 to N - 1) one can call #Encode to generate
+ * another M parity blocks (numbered from N to N + M - 1).
+ *
+ * If some of the resulting N + M blocks ever become missing one can attempt to
+ * repair the missing blocks by calling #Decode.
+ *
+ * Here N and M are fixed (codec-specific) parameters.
+ * Call #GetDataPartCount and #GetParityPartCount to figure out the
+ * the values for N and M, respectively.
+ *
+ */
+template <class TBlobType>
+struct ICodec {
+ //! Computes a sequence of parity blocks for given data blocks.
+ /*!
+ * The size of #blocks must be equal to #GetDataPartCount.
+ * The size of the returned array is equal to #GetParityPartCount.
+ */
+ virtual std::vector<TBlobType> Encode(const std::vector<TBlobType>& blocks) const = 0;
+
+ //! Decodes (repairs) missing blocks.
+ /*!
+ * #erasedIndices must contain the set of erased blocks indices.
+ * #blocks must contain known blocks (in the order specified by #GetRepairIndices).
+ * \returns The repaired blocks.
+ */
+ virtual std::vector<TBlobType> Decode(
+ const std::vector<TBlobType>& blocks,
+ const TPartIndexList& erasedIndices) const = 0;
+
+ //! Given a set of missing block indices, returns |true| if missing blocks can be repaired.
+ //! Due to performance reasons the elements of #erasedIndices must unique and sorted.
+ virtual bool CanRepair(const TPartIndexList& erasedIndices) const = 0;
+
+ //! Rapid version that works with set instead of list.
+ virtual bool CanRepair(const TPartIndexSet& erasedIndices) const = 0;
+
+ //! Given a set of missing block indices, checks if missing blocks can be repaired.
+ /*!
+ * \returns
+ * If repair is not possible, returns |std::nullopt|.
+ * Otherwise returns the indices of blocks (both data and parity) to be passed to #Decode
+ * (in this very order). Not all known blocks may be needed for repair.
+ */
+ virtual std::optional<TPartIndexList> GetRepairIndices(const TPartIndexList& erasedIndices) const = 0;
+
+ //! Returns the number of data blocks this codec can handle.
+ virtual int GetDataPartCount() const = 0;
+
+ //! Returns the number of parity blocks this codec can handle.
+ virtual int GetParityPartCount() const = 0;
+
+ //! Returns the maximum number of blocks that can always be repaired when missing.
+ virtual int GetGuaranteedRepairablePartCount() const = 0;
+
+ //! Every block passed to this codec must have size divisible by the result of #GetWordSize.
+ virtual int GetWordSize() const = 0;
+
+ // Extension methods
+
+ //! Returns the sum of #GetDataPartCount and #GetParityPartCount.
+ int GetTotalPartCount() const {
+ return GetDataPartCount() + GetParityPartCount();
+ }
+};
+
+} // namespace NErasure
+
diff --git a/library/cpp/erasure/helpers.cpp b/library/cpp/erasure/helpers.cpp
new file mode 100644
index 0000000000..74edeca52c
--- /dev/null
+++ b/library/cpp/erasure/helpers.cpp
@@ -0,0 +1,80 @@
+#include "helpers.h"
+
+#include <algorithm>
+#include <iterator>
+
+namespace NErasure {
+
+TPartIndexList MakeSegment(int begin, int end) {
+ TPartIndexList result(end - begin);
+ for (int i = begin; i < end; ++i) {
+ result[i - begin] = i;
+ }
+ return result;
+}
+
+TPartIndexList MakeSingleton(int elem) {
+ TPartIndexList result;
+ result.push_back(elem);
+ return result;
+}
+
+TPartIndexList Difference(int begin, int end, const TPartIndexList& subtrahend) {
+ size_t pos = 0;
+ TPartIndexList result;
+ for (int i = begin; i < end; ++i) {
+ while (pos < subtrahend.size() && subtrahend[pos] < i) {
+ pos += 1;
+ }
+ if (pos == subtrahend.size() || subtrahend[pos] != i) {
+ result.push_back(i);
+ }
+ }
+ return result;
+}
+
+TPartIndexList Difference(const TPartIndexList& first, const TPartIndexList& second) {
+ TPartIndexList result;
+ std::set_difference(first.begin(), first.end(), second.begin(), second.end(), std::back_inserter(result));
+ return result;
+}
+
+TPartIndexList Difference(const TPartIndexList& set, int subtrahend) {
+ return Difference(set, MakeSingleton(subtrahend));
+}
+
+TPartIndexList Intersection(const TPartIndexList& first, const TPartIndexList& second) {
+ TPartIndexList result;
+ std::set_intersection(first.begin(), first.end(), second.begin(), second.end(), std::back_inserter(result));
+ return result;
+}
+
+TPartIndexList Union(const TPartIndexList& first, const TPartIndexList& second) {
+ TPartIndexList result;
+ std::set_union(first.begin(), first.end(), second.begin(), second.end(), std::back_inserter(result));
+ return result;
+}
+
+bool Contains(const TPartIndexList& set, int elem) {
+ return std::binary_search(set.begin(), set.end(), elem);
+}
+
+TPartIndexList UniqueSortedIndices(const TPartIndexList& indices) {
+ TPartIndexList copy = indices;
+ std::sort(copy.begin(), copy.end());
+ copy.erase(std::unique(copy.begin(), copy.end()), copy.end());
+ return copy;
+}
+
+TPartIndexList ExtractRows(const TPartIndexList& matrix, int width, const TPartIndexList& rows) {
+ Y_ASSERT(matrix.size() % width == 0);
+ TPartIndexList result(width * rows.size());
+ for (size_t i = 0; i < rows.size(); ++i) {
+ auto start = matrix.begin() + rows[i] * width;
+ std::copy(start, start + width, result.begin() + i * width);
+ }
+ return result;
+}
+
+} // namespace NErasure
+
diff --git a/library/cpp/erasure/helpers.h b/library/cpp/erasure/helpers.h
new file mode 100644
index 0000000000..741186322a
--- /dev/null
+++ b/library/cpp/erasure/helpers.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "public.h"
+
+namespace NErasure {
+
+// All vectors here are assumed to be sorted.
+
+TPartIndexList MakeSegment(int begin, int end);
+
+TPartIndexList MakeSingleton(int elem);
+
+TPartIndexList Difference(int begin, int end, const TPartIndexList& subtrahend);
+
+TPartIndexList Difference(const TPartIndexList& first, const TPartIndexList& second);
+
+TPartIndexList Difference(const TPartIndexList& first, int elem);
+
+TPartIndexList Intersection(const TPartIndexList& first, const TPartIndexList& second);
+
+TPartIndexList Union(const TPartIndexList& first, const TPartIndexList& second);
+
+bool Contains(const TPartIndexList& set, int elem);
+
+TPartIndexList UniqueSortedIndices(const TPartIndexList& indices);
+
+TPartIndexList ExtractRows(const TPartIndexList& matrix, int width, const TPartIndexList& rows);
+
+} // namespace NErasure
+
diff --git a/library/cpp/erasure/isa_erasure.cpp b/library/cpp/erasure/isa_erasure.cpp
new file mode 100644
index 0000000000..0b0199934e
--- /dev/null
+++ b/library/cpp/erasure/isa_erasure.cpp
@@ -0,0 +1 @@
+#include "isa_erasure.h"
diff --git a/library/cpp/erasure/isa_erasure.h b/library/cpp/erasure/isa_erasure.h
new file mode 100644
index 0000000000..a7df61307f
--- /dev/null
+++ b/library/cpp/erasure/isa_erasure.h
@@ -0,0 +1,170 @@
+#pragma once
+
+#include "public.h"
+
+#include "helpers.h"
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <util/generic/array_ref.h>
+#include <util/generic/ptr.h>
+#include <util/generic/singleton.h>
+
+#include <vector>
+
+extern "C" {
+ #include <contrib/libs/isa-l/include/erasure_code.h>
+}
+
+namespace NErasure {
+
+template <class TBlobType>
+static inline unsigned char* ConstCast(typename TBlobType::const_iterator blobIter) {
+ return const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(blobIter));
+}
+
+template <int DataPartCount, int ParityPartCount, class TCodecTraits, class TBlobType = typename TCodecTraits::TBlobType, class TMutableBlobType = typename TCodecTraits::TMutableBlobType>
+std::vector<TBlobType> ISAErasureEncode(
+ const std::vector<unsigned char>& encodeGFTables,
+ const std::vector<TBlobType>& dataBlocks)
+{
+ YT_VERIFY(dataBlocks.size() == DataPartCount);
+
+ size_t blockLength = dataBlocks.front().Size();
+ for (size_t i = 1; i < dataBlocks.size(); ++i) {
+ YT_VERIFY(dataBlocks[i].Size() == blockLength);
+ }
+
+ std::vector<unsigned char*> dataPointers;
+ for (const auto& block : dataBlocks) {
+ dataPointers.emplace_back(ConstCast<TBlobType>(block.Begin()));
+ }
+
+ std::vector<TMutableBlobType> parities(ParityPartCount);
+ std::vector<unsigned char*> parityPointers(ParityPartCount);
+ for (size_t i = 0; i < ParityPartCount; ++i) {
+ parities[i] = TCodecTraits::AllocateBlob(blockLength);
+ parityPointers[i] = ConstCast<TBlobType>(parities[i].Begin());
+ memset(parityPointers[i], 0, blockLength);
+ }
+
+ ec_encode_data(
+ blockLength,
+ DataPartCount,
+ ParityPartCount,
+ const_cast<unsigned char*>(encodeGFTables.data()),
+ dataPointers.data(),
+ parityPointers.data());
+
+ return std::vector<TBlobType>(parities.begin(), parities.end());
+}
+
+template <int DataPartCount, int ParityPartCount, class TCodecTraits, class TBlobType = typename TCodecTraits::TBlobType, class TMutableBlobType = typename TCodecTraits::TMutableBlobType>
+std::vector<TBlobType> ISAErasureDecode(
+ const std::vector<TBlobType>& dataBlocks,
+ const TPartIndexList& erasedIndices,
+ TConstArrayRef<TPartIndexList> groups,
+ const std::vector<unsigned char>& fullGeneratorMatrix)
+{
+ YT_VERIFY(dataBlocks.size() >= DataPartCount);
+ YT_VERIFY(erasedIndices.size() <= ParityPartCount);
+
+ size_t blockLength = dataBlocks.front().Size();
+ for (size_t i = 1; i < dataBlocks.size(); ++i) {
+ YT_VERIFY(dataBlocks[i].Size() == blockLength);
+ }
+
+ std::vector<unsigned char> partialGeneratorMatrix(DataPartCount * DataPartCount, 0);
+
+ std::vector<unsigned char*> recoveryBlocks;
+ for (size_t i = 0; i < DataPartCount; ++i) {
+ recoveryBlocks.emplace_back(ConstCast<TBlobType>(dataBlocks[i].Begin()));
+ }
+
+ // Groups check is specific for LRC.
+ std::vector<int> isGroupHealthy(2, 1);
+ for (size_t i = 0; i < 2; ++i) {
+ for (const auto& index : erasedIndices) {
+ if (!groups.empty() && Contains(groups[0], index)) {
+ isGroupHealthy[0] = 0;
+ } else if (!groups.empty() && Contains(groups[1], index)) {
+ isGroupHealthy[1] = 0;
+ }
+ }
+ }
+
+ // When a group is healthy we cannot use its local parity, thus skip it using gap.
+ size_t gap = 0;
+ size_t decodeMatrixIndex = 0;
+ size_t erasedBlockIndex = 0;
+ while (decodeMatrixIndex < DataPartCount) {
+ size_t globalIndex = decodeMatrixIndex + erasedBlockIndex + gap;
+
+ if (erasedBlockIndex < erasedIndices.size() &&
+ globalIndex == static_cast<size_t>(erasedIndices[erasedBlockIndex]))
+ {
+ ++erasedBlockIndex;
+ continue;
+ }
+
+ if (!groups.empty() && globalIndex >= DataPartCount && globalIndex < DataPartCount + 2) {
+ if (Contains(groups[0], globalIndex) && isGroupHealthy[0]) {
+ ++gap;
+ continue;
+ }
+ if (Contains(groups[1], globalIndex) && isGroupHealthy[1]) {
+ ++gap;
+ continue;
+ }
+ }
+
+ memcpy(&partialGeneratorMatrix[decodeMatrixIndex * DataPartCount], &fullGeneratorMatrix[globalIndex * DataPartCount], DataPartCount);
+ ++decodeMatrixIndex;
+ }
+
+ std::vector<unsigned char> invertedGeneratorMatrix(DataPartCount * DataPartCount, 0);
+ int res = gf_invert_matrix(partialGeneratorMatrix.data(), invertedGeneratorMatrix.data(), DataPartCount);
+ YT_VERIFY(res == 0);
+
+ std::vector<unsigned char> decodeMatrix(DataPartCount * (DataPartCount + ParityPartCount), 0);
+
+ //! Some magical code from library example.
+ for (size_t i = 0; i < erasedIndices.size(); ++i) {
+ if (erasedIndices[i] < DataPartCount) {
+ memcpy(&decodeMatrix[i * DataPartCount], &invertedGeneratorMatrix[erasedIndices[i] * DataPartCount], DataPartCount);
+ } else {
+ for (int k = 0; k < DataPartCount; ++k) {
+ int val = 0;
+ for (int j = 0; j < DataPartCount; ++j) {
+ val ^= gf_mul_erasure(invertedGeneratorMatrix[j * DataPartCount + k], fullGeneratorMatrix[DataPartCount * erasedIndices[i] + j]);
+ }
+
+ decodeMatrix[DataPartCount * i + k] = val;
+ }
+ }
+ }
+
+ std::vector<unsigned char> decodeGFTables(DataPartCount * erasedIndices.size() * 32);
+ ec_init_tables(DataPartCount, erasedIndices.size(), decodeMatrix.data(), decodeGFTables.data());
+
+ std::vector<TMutableBlobType> recoveredParts;
+ std::vector<unsigned char*> recoveredPartsPointers;
+ for (size_t i = 0; i < erasedIndices.size(); ++i) {
+ recoveredParts.emplace_back(TCodecTraits::AllocateBlob(blockLength));
+ recoveredPartsPointers.emplace_back(ConstCast<TBlobType>(recoveredParts.back().Begin()));
+ memset(recoveredPartsPointers.back(), 0, blockLength);
+ }
+
+ ec_encode_data(
+ blockLength,
+ DataPartCount,
+ erasedIndices.size(),
+ decodeGFTables.data(),
+ recoveryBlocks.data(),
+ recoveredPartsPointers.data());
+
+ return std::vector<TBlobType>(recoveredParts.begin(), recoveredParts.end());
+}
+
+} // namespace NErasure
+
diff --git a/library/cpp/erasure/lrc.cpp b/library/cpp/erasure/lrc.cpp
new file mode 100644
index 0000000000..8c6a347091
--- /dev/null
+++ b/library/cpp/erasure/lrc.cpp
@@ -0,0 +1 @@
+#include "lrc.h"
diff --git a/library/cpp/erasure/lrc.h b/library/cpp/erasure/lrc.h
new file mode 100644
index 0000000000..15185a47f4
--- /dev/null
+++ b/library/cpp/erasure/lrc.h
@@ -0,0 +1,326 @@
+#pragma once
+
+#include "helpers.h"
+
+#include <library/cpp/sse/sse.h>
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <util/generic/array_ref.h>
+
+#include <algorithm>
+#include <optional>
+
+namespace NErasure {
+
+template <class TCodecTraits, class TBlobType = typename TCodecTraits::TBlobType>
+static inline TBlobType Xor(const std::vector<TBlobType>& refs) {
+ using TBufferType = typename TCodecTraits::TBufferType;
+ size_t size = refs.front().Size();
+ TBufferType result = TCodecTraits::AllocateBuffer(size); // this also fills the buffer with zeros
+ for (const TBlobType& ref : refs) {
+ const char* data = reinterpret_cast<const char*>(ref.Begin());
+ size_t pos = 0;
+#ifdef ARCADIA_SSE
+ for (; pos + sizeof(__m128i) <= size; pos += sizeof(__m128i)) {
+ __m128i* dst = reinterpret_cast<__m128i*>(result.Begin() + pos);
+ const __m128i* src = reinterpret_cast<const __m128i*>(data + pos);
+ _mm_storeu_si128(dst, _mm_xor_si128(_mm_loadu_si128(src), _mm_loadu_si128(dst)));
+ }
+#endif
+ for (; pos < size; ++pos) {
+ *(result.Begin() + pos) ^= data[pos];
+ }
+ }
+ return TCodecTraits::FromBufferToBlob(std::move(result));
+}
+
+//! Locally Reconstructable Codes
+/*!
+ * See https://www.usenix.org/conference/usenixfederatedconferencesweek/erasure-coding-windows-azure-storage
+ * for more details.
+ */
+template <int DataPartCount, int ParityPartCount, int WordSize, class TCodecTraits>
+class TLrcCodecBase
+ : public ICodec<typename TCodecTraits::TBlobType>
+{
+ static_assert(DataPartCount % 2 == 0, "Data part count must be even.");
+ static_assert(ParityPartCount == 4, "Now we only support n-2-2 scheme for LRC codec");
+ static_assert(1 + DataPartCount / 2 < (1 << (WordSize / 2)), "Data part count should be enough small to construct proper matrix.");
+public:
+ //! Main blob for storing data.
+ using TBlobType = typename TCodecTraits::TBlobType;
+ //! Main mutable blob for decoding data.
+ using TMutableBlobType = typename TCodecTraits::TMutableBlobType;
+
+ static constexpr ui64 RequiredDataAlignment = alignof(ui64);
+
+ TLrcCodecBase() {
+ Groups_[0] = MakeSegment(0, DataPartCount / 2);
+ // Xor.
+ Groups_[0].push_back(DataPartCount);
+
+ Groups_[1] = MakeSegment(DataPartCount / 2, DataPartCount);
+ // Xor.
+ Groups_[1].push_back(DataPartCount + 1);
+
+ constexpr int totalPartCount = DataPartCount + ParityPartCount;
+ if constexpr (totalPartCount <= BitmaskOptimizationThreshold) {
+ CanRepair_.resize(1 << totalPartCount);
+ for (int mask = 0; mask < (1 << totalPartCount); ++mask) {
+ TPartIndexList erasedIndices;
+ for (size_t i = 0; i < totalPartCount; ++i) {
+ if ((mask & (1 << i)) == 0) {
+ erasedIndices.push_back(i);
+ }
+ }
+ CanRepair_[mask] = CalculateCanRepair(erasedIndices);
+ }
+ }
+ }
+
+ /*! Note that if you want to restore any internal data, blocks offsets must by WordSize * sizeof(long) aligned.
+ * Though it is possible to restore unaligned data if no more than one index in each Group is failed. See unittests for this case.
+ */
+ std::vector<TBlobType> Decode(
+ const std::vector<TBlobType>& blocks,
+ const TPartIndexList& erasedIndices) const override
+ {
+ if (erasedIndices.empty()) {
+ return std::vector<TBlobType>();
+ }
+
+ size_t blockLength = blocks.front().Size();
+ for (size_t i = 1; i < blocks.size(); ++i) {
+ YT_VERIFY(blocks[i].Size() == blockLength);
+ }
+
+ TPartIndexList indices = UniqueSortedIndices(erasedIndices);
+
+ // We can restore one block by xor.
+ if (indices.size() == 1) {
+ int index = erasedIndices.front();
+ for (size_t i = 0; i < 2; ++i) {
+ if (Contains(Groups_[i], index)) {
+ return std::vector<TBlobType>(1, Xor<TCodecTraits>(blocks));
+ }
+ }
+ }
+
+ TPartIndexList recoveryIndices = GetRepairIndices(indices).value();
+ // We can restore two blocks from different groups using xor.
+ if (indices.size() == 2 &&
+ indices.back() < DataPartCount + 2 &&
+ recoveryIndices.back() < DataPartCount + 2)
+ {
+ std::vector<TBlobType> result;
+ for (int index : indices) {
+ for (size_t groupIndex = 0; groupIndex < 2; ++groupIndex) {
+ if (!Contains(Groups_[groupIndex], index)) {
+ continue;
+ }
+
+ std::vector<TBlobType> correspondingBlocks;
+ for (int pos : Groups_[groupIndex]) {
+ for (size_t i = 0; i < blocks.size(); ++i) {
+ if (recoveryIndices[i] != pos) {
+ continue;
+ }
+ correspondingBlocks.push_back(blocks[i]);
+ }
+ }
+
+ result.push_back(Xor<TCodecTraits>(correspondingBlocks));
+ }
+ }
+ return result;
+ }
+
+ return FallbackToCodecDecode(blocks, std::move(indices));
+ }
+
+ bool CanRepair(const TPartIndexList& erasedIndices) const final {
+ constexpr int totalPartCount = DataPartCount + ParityPartCount;
+ if constexpr (totalPartCount <= BitmaskOptimizationThreshold) {
+ int mask = (1 << (totalPartCount)) - 1;
+ for (int index : erasedIndices) {
+ mask -= (1 << index);
+ }
+ return CanRepair_[mask];
+ } else {
+ return CalculateCanRepair(erasedIndices);
+ }
+ }
+
+ bool CanRepair(const TPartIndexSet& erasedIndicesMask) const final {
+ constexpr int totalPartCount = DataPartCount + ParityPartCount;
+ if constexpr (totalPartCount <= BitmaskOptimizationThreshold) {
+ TPartIndexSet mask = erasedIndicesMask;
+ return CanRepair_[mask.flip().to_ulong()];
+ } else {
+ TPartIndexList erasedIndices;
+ for (size_t i = 0; i < erasedIndicesMask.size(); ++i) {
+ if (erasedIndicesMask[i]) {
+ erasedIndices.push_back(i);
+ }
+ }
+ return CalculateCanRepair(erasedIndices);
+ }
+ }
+
+ std::optional<TPartIndexList> GetRepairIndices(const TPartIndexList& erasedIndices) const final {
+ if (erasedIndices.empty()) {
+ return TPartIndexList();
+ }
+
+ TPartIndexList indices = UniqueSortedIndices(erasedIndices);
+
+ if (indices.size() > ParityPartCount) {
+ return std::nullopt;
+ }
+
+ // One erasure from data or xor blocks.
+ if (indices.size() == 1) {
+ int index = indices.front();
+ for (size_t i = 0; i < 2; ++i) {
+ if (Contains(Groups_[i], index)) {
+ return Difference(Groups_[i], index);
+ }
+ }
+ }
+
+ // Null if we have 4 erasures in one group.
+ if (indices.size() == ParityPartCount) {
+ bool intersectsAny = true;
+ for (size_t i = 0; i < 2; ++i) {
+ if (Intersection(indices, Groups_[i]).empty()) {
+ intersectsAny = false;
+ }
+ }
+ if (!intersectsAny) {
+ return std::nullopt;
+ }
+ }
+
+ // Calculate coverage of each group.
+ int groupCoverage[2] = {};
+ for (int index : indices) {
+ for (size_t i = 0; i < 2; ++i) {
+ if (Contains(Groups_[i], index)) {
+ ++groupCoverage[i];
+ }
+ }
+ }
+
+ // Two erasures, one in each group.
+ if (indices.size() == 2 && groupCoverage[0] == 1 && groupCoverage[1] == 1) {
+ return Difference(Union(Groups_[0], Groups_[1]), indices);
+ }
+
+ // Erasures in only parity blocks.
+ if (indices.front() >= DataPartCount) {
+ return MakeSegment(0, DataPartCount);
+ }
+
+ // Remove unnecessary xor parities.
+ TPartIndexList result = Difference(0, DataPartCount + ParityPartCount, indices);
+ for (size_t i = 0; i < 2; ++i) {
+ if (groupCoverage[i] == 0 && indices.size() <= 3) {
+ result = Difference(result, DataPartCount + i);
+ }
+ }
+ return result;
+ }
+
+ int GetDataPartCount() const override {
+ return DataPartCount;
+ }
+
+ int GetParityPartCount() const override {
+ return ParityPartCount;
+ }
+
+ int GetGuaranteedRepairablePartCount() const override {
+ return ParityPartCount - 1;
+ }
+
+ int GetWordSize() const override {
+ return WordSize * sizeof(long);
+ }
+
+ virtual ~TLrcCodecBase() = default;
+
+protected:
+ // Indices of data blocks and corresponding xor (we have two xor parities).
+ TConstArrayRef<TPartIndexList> GetXorGroups() const {
+ return Groups_;
+ }
+
+ virtual std::vector<TBlobType> FallbackToCodecDecode(
+ const std::vector<TBlobType>& /* blocks */,
+ TPartIndexList /* erasedIndices */) const = 0;
+
+ template <typename T>
+ void InitializeGeneratorMatrix(T* generatorMatrix, const std::function<T(T)>& GFSquare) {
+ for (int row = 0; row < ParityPartCount; ++row) {
+ for (int column = 0; column < DataPartCount; ++column) {
+ int index = row * DataPartCount + column;
+
+ bool isFirstHalf = column < DataPartCount / 2;
+ if (row == 0) generatorMatrix[index] = isFirstHalf ? 1 : 0;
+ if (row == 1) generatorMatrix[index] = isFirstHalf ? 0 : 1;
+
+ // Let alpha_i be coefficient of first half and beta_i of the second half.
+ // Then matrix is non-singular iff:
+ // a) alpha_i, beta_j != 0
+ // b) alpha_i != beta_j
+ // c) alpha_i + alpha_k != beta_j + beta_l
+ // for any i, j, k, l.
+ if (row == 2) {
+ int shift = isFirstHalf ? 1 : (1 << (WordSize / 2));
+ int relativeColumn = isFirstHalf ? column : (column - (DataPartCount / 2));
+ generatorMatrix[index] = shift * (1 + relativeColumn);
+ }
+
+ // The last row is the square of the row before last.
+ if (row == 3) {
+ auto prev = generatorMatrix[index - DataPartCount];
+ generatorMatrix[index] = GFSquare(prev);
+ }
+ }
+ }
+ }
+
+private:
+ bool CalculateCanRepair(const TPartIndexList& erasedIndices) const {
+ TPartIndexList indices = UniqueSortedIndices(erasedIndices);
+ if (indices.size() > ParityPartCount) {
+ return false;
+ }
+
+ if (indices.size() == 1) {
+ int index = indices.front();
+ for (size_t i = 0; i < 2; ++i) {
+ if (Contains(Groups_[i], index)) {
+ return true;
+ }
+ }
+ }
+
+ // If 4 indices miss in one block we cannot recover.
+ if (indices.size() == ParityPartCount) {
+ for (size_t i = 0; i < 2; ++i) {
+ if (Intersection(indices, Groups_[i]).empty()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ TPartIndexList Groups_[2];
+ std::vector<bool> CanRepair_;
+};
+
+} // namespace NErasure
diff --git a/library/cpp/erasure/lrc_isa.cpp b/library/cpp/erasure/lrc_isa.cpp
new file mode 100644
index 0000000000..2068f840c8
--- /dev/null
+++ b/library/cpp/erasure/lrc_isa.cpp
@@ -0,0 +1 @@
+#include "lrc_isa.h"
diff --git a/library/cpp/erasure/lrc_isa.h b/library/cpp/erasure/lrc_isa.h
new file mode 100644
index 0000000000..800dc3c5ca
--- /dev/null
+++ b/library/cpp/erasure/lrc_isa.h
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "lrc.h"
+#include "helpers.h"
+
+#include "isa_erasure.h"
+
+extern "C" {
+ #include <contrib/libs/isa-l/include/erasure_code.h>
+}
+
+#include <library/cpp/sse/sse.h>
+
+#include <util/generic/array_ref.h>
+
+#include <optional>
+#include <vector>
+
+namespace NErasure {
+
+template <int DataPartCount, int ParityPartCount, int WordSize, class TCodecTraits>
+class TLrcIsa
+ : public TLrcCodecBase<DataPartCount, ParityPartCount, WordSize, TCodecTraits>
+{
+ static_assert(WordSize == 8, "ISA-l erasure codes support computations only in GF(2^8)");
+public:
+ //! Main blob for storing data.
+ using TBlobType = typename TCodecTraits::TBlobType;
+ //! Main mutable blob for decoding data.
+ using TMutableBlobType = typename TCodecTraits::TMutableBlobType;
+
+ static constexpr ui64 RequiredDataAlignment = alignof(ui64);
+
+ TLrcIsa()
+ : TLrcCodecBase<DataPartCount, ParityPartCount, WordSize, TCodecTraits>()
+ {
+ EncodeGFTables_.resize(DataPartCount * ParityPartCount * 32, 0);
+ GeneratorMatrix_.resize((DataPartCount + ParityPartCount) * DataPartCount, 0);
+
+ for (int row = 0; row < DataPartCount; ++row) {
+ GeneratorMatrix_[row * DataPartCount + row] = 1;
+ }
+ this->template InitializeGeneratorMatrix<typename decltype(GeneratorMatrix_)::value_type>(
+ &GeneratorMatrix_[DataPartCount * DataPartCount],
+ std::bind(&gf_mul_erasure, std::placeholders::_1, std::placeholders::_1));
+
+ ec_init_tables(
+ DataPartCount,
+ ParityPartCount,
+ &GeneratorMatrix_.data()[DataPartCount * DataPartCount],
+ EncodeGFTables_.data());
+ }
+
+ std::vector<TBlobType> Encode(const std::vector<TBlobType>& blocks) const override {
+ return ISAErasureEncode<DataPartCount, ParityPartCount, TCodecTraits, TBlobType, TMutableBlobType>(EncodeGFTables_, blocks);
+ }
+
+ virtual ~TLrcIsa() = default;
+
+private:
+ std::vector<TBlobType> FallbackToCodecDecode(
+ const std::vector<TBlobType>& blocks,
+ TPartIndexList erasedIndices) const override
+ {
+ return ISAErasureDecode<DataPartCount, ParityPartCount, TCodecTraits, TBlobType, TMutableBlobType>(
+ blocks,
+ std::move(erasedIndices),
+ this->GetXorGroups(),
+ GeneratorMatrix_);
+ }
+
+ std::vector<unsigned char> GeneratorMatrix_;
+ std::vector<unsigned char> EncodeGFTables_;
+};
+
+} // NErasure
+
diff --git a/library/cpp/erasure/public.cpp b/library/cpp/erasure/public.cpp
new file mode 100644
index 0000000000..4df9bcaa18
--- /dev/null
+++ b/library/cpp/erasure/public.cpp
@@ -0,0 +1 @@
+#include "public.h"
diff --git a/library/cpp/erasure/public.h b/library/cpp/erasure/public.h
new file mode 100644
index 0000000000..d5cf01297b
--- /dev/null
+++ b/library/cpp/erasure/public.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <util/generic/buffer.h>
+#include <util/generic/yexception.h>
+#include <util/memory/blob.h>
+#include <util/string/cast.h>
+#include <util/system/src_root.h>
+
+#include <vector>
+
+#include <bitset>
+
+namespace NErasure {
+
+//! The maximum total number of blocks our erasure codec can handle.
+static constexpr int MaxTotalPartCount = 16;
+
+//! Max word size to use. `w` in jerasure notation
+static constexpr int MaxWordSize = 8;
+
+//! Max threshold to generate bitmask for CanRepair indices for LRC encoding.
+static constexpr int BitmaskOptimizationThreshold = 22;
+
+//! A vector type for holding block indexes.
+using TPartIndexList = std::vector<int>;
+
+//! Each bit corresponds to a possible block index.
+using TPartIndexSet = std::bitset<MaxTotalPartCount>;
+
+template <class TBlobType>
+struct ICodec;
+
+struct TDefaultCodecTraits {
+ using TBlobType = TBlob;
+ using TMutableBlobType = TBlob;
+ using TBufferType = TBuffer;
+
+ static inline TMutableBlobType AllocateBlob(size_t size) {
+ TBufferType buffer(size);
+ buffer.Resize(size);
+ // The buffer is cleared after this call so no use after free.
+ return TBlob::FromBuffer(buffer);
+ }
+
+ // AllocateBuffer must fill the memory with 0.
+ static inline TBufferType AllocateBuffer(size_t size) {
+ TBufferType buffer(size);
+ buffer.Fill(0, size);
+ return buffer;
+ }
+
+ static inline TBlobType FromBufferToBlob(TBufferType&& blob) {
+ return TBlobType::FromBuffer(blob);
+ }
+};
+
+} // namespace NErasure
diff --git a/library/cpp/erasure/reed_solomon.cpp b/library/cpp/erasure/reed_solomon.cpp
new file mode 100644
index 0000000000..68aa2acbfb
--- /dev/null
+++ b/library/cpp/erasure/reed_solomon.cpp
@@ -0,0 +1 @@
+#include "reed_solomon.h"
diff --git a/library/cpp/erasure/reed_solomon.h b/library/cpp/erasure/reed_solomon.h
new file mode 100644
index 0000000000..a2e268e81d
--- /dev/null
+++ b/library/cpp/erasure/reed_solomon.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "helpers.h"
+
+#include <algorithm>
+#include <optional>
+
+namespace NErasure {
+
+template <int DataPartCount, int ParityPartCount, int WordSize, class TCodecTraits>
+class TReedSolomonBase
+ : public ICodec<typename TCodecTraits::TBlobType>
+{
+public:
+ static constexpr ui64 RequiredDataAlignment = alignof(ui64);
+
+ bool CanRepair(const TPartIndexList& erasedIndices) const final {
+ return erasedIndices.size() <= ParityPartCount;
+ }
+
+ bool CanRepair(const TPartIndexSet& erasedIndices) const final {
+ return erasedIndices.count() <= static_cast<size_t>(ParityPartCount);
+ }
+
+ std::optional<TPartIndexList> GetRepairIndices(const TPartIndexList& erasedIndices) const final {
+ if (erasedIndices.empty()) {
+ return TPartIndexList();
+ }
+
+ TPartIndexList indices = erasedIndices;
+ std::sort(indices.begin(), indices.end());
+ indices.erase(std::unique(indices.begin(), indices.end()), indices.end());
+
+ if (indices.size() > static_cast<size_t>(ParityPartCount)) {
+ return std::nullopt;
+ }
+
+ return Difference(0, DataPartCount + ParityPartCount, indices);
+ }
+
+ int GetDataPartCount() const final {
+ return DataPartCount;
+ }
+
+ int GetParityPartCount() const final {
+ return ParityPartCount;
+ }
+
+ int GetGuaranteedRepairablePartCount() const final {
+ return ParityPartCount;
+ }
+
+ int GetWordSize() const final {
+ return WordSize * sizeof(long);
+ }
+
+ virtual ~TReedSolomonBase() = default;
+};
+
+} // namespace NErasure
diff --git a/library/cpp/erasure/reed_solomon_isa.cpp b/library/cpp/erasure/reed_solomon_isa.cpp
new file mode 100644
index 0000000000..eaffde54b5
--- /dev/null
+++ b/library/cpp/erasure/reed_solomon_isa.cpp
@@ -0,0 +1 @@
+#include "reed_solomon_isa.h"
diff --git a/library/cpp/erasure/reed_solomon_isa.h b/library/cpp/erasure/reed_solomon_isa.h
new file mode 100644
index 0000000000..73a3cd630f
--- /dev/null
+++ b/library/cpp/erasure/reed_solomon_isa.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "isa_erasure.h"
+#include "reed_solomon.h"
+
+extern "C" {
+ #include <contrib/libs/isa-l/include/erasure_code.h>
+}
+
+#include <util/generic/array_ref.h>
+
+#include <array>
+
+namespace NErasure {
+
+template <int DataPartCount, int ParityPartCount, int WordSize, class TCodecTraits>
+class TReedSolomonIsa
+ : public TReedSolomonBase<DataPartCount, ParityPartCount, WordSize, TCodecTraits>
+{
+ static_assert(WordSize == 8, "ISA-l erasure codes support computations only in GF(2^8)");
+public:
+ //! Main blob for storing data.
+ using TBlobType = typename TCodecTraits::TBlobType;
+ //! Main mutable blob for decoding data.
+ using TMutableBlobType = typename TCodecTraits::TMutableBlobType;
+
+ TReedSolomonIsa() {
+ EncodeGFTables_.resize(DataPartCount * ParityPartCount * 32, 0);
+ GeneratorMatrix_.resize((DataPartCount + ParityPartCount) * DataPartCount, 0);
+
+ gf_gen_rs_matrix(
+ GeneratorMatrix_.data(),
+ DataPartCount + ParityPartCount,
+ DataPartCount);
+
+ ec_init_tables(
+ DataPartCount,
+ ParityPartCount,
+ &GeneratorMatrix_.data()[DataPartCount * DataPartCount],
+ EncodeGFTables_.data());
+ }
+
+ virtual std::vector<TBlobType> Encode(const std::vector<TBlobType>& blocks) const override {
+ return ISAErasureEncode<DataPartCount, ParityPartCount, TCodecTraits, TBlobType, TMutableBlobType>(EncodeGFTables_, blocks);
+ }
+
+ virtual std::vector<TBlobType> Decode(
+ const std::vector<TBlobType>& blocks,
+ const TPartIndexList& erasedIndices) const override
+ {
+ if (erasedIndices.empty()) {
+ return std::vector<TBlobType>();
+ }
+
+ return ISAErasureDecode<DataPartCount, ParityPartCount, TCodecTraits, TBlobType, TMutableBlobType>(
+ blocks,
+ erasedIndices,
+ TConstArrayRef<TPartIndexList>(),
+ GeneratorMatrix_);
+ }
+
+ virtual ~TReedSolomonIsa() = default;
+
+private:
+ std::vector<unsigned char> GeneratorMatrix_;
+ std::vector<unsigned char> EncodeGFTables_;
+};
+
+} // namespace NErasure
diff --git a/library/cpp/erasure/ya.make b/library/cpp/erasure/ya.make
new file mode 100644
index 0000000000..bde816b4d8
--- /dev/null
+++ b/library/cpp/erasure/ya.make
@@ -0,0 +1,35 @@
+LIBRARY()
+
+SRCS(
+ public.cpp
+ codec.cpp
+ helpers.cpp
+
+ isa_erasure.cpp
+
+ reed_solomon.cpp
+ reed_solomon_isa.cpp
+
+ lrc.cpp
+ lrc_isa.cpp
+)
+
+PEERDIR(
+ contrib/libs/isa-l/erasure_code
+ library/cpp/sse
+ library/cpp/yt/assert
+)
+
+IF (NOT OPENSOURCE)
+ SRCS(
+ jerasure.cpp
+ reed_solomon_jerasure.cpp
+ lrc_jerasure.cpp
+ )
+
+ PEERDIR(contrib/libs/jerasure)
+ENDIF()
+
+GENERATE_ENUM_SERIALIZATION(public.h)
+
+END()
diff --git a/library/cpp/testing/CMakeLists.txt b/library/cpp/testing/CMakeLists.txt
index 70c5d112f1..6e9e24fec8 100644
--- a/library/cpp/testing/CMakeLists.txt
+++ b/library/cpp/testing/CMakeLists.txt
@@ -14,5 +14,6 @@ add_subdirectory(gtest)
add_subdirectory(gtest_extensions)
add_subdirectory(gtest_main)
add_subdirectory(hook)
+add_subdirectory(mock_server)
add_subdirectory(unittest)
add_subdirectory(unittest_main)
diff --git a/library/cpp/testing/gtest/friend.h b/library/cpp/testing/gtest/friend.h
new file mode 100644
index 0000000000..551a218be0
--- /dev/null
+++ b/library/cpp/testing/gtest/friend.h
@@ -0,0 +1,5 @@
+#pragma once
+
+// Using absolute path to gtest headers in order to allow using friend.h without PEERDIRing gtest.
+#include <contrib/restricted/googletest/googletest/include/gtest/gtest_prod.h>
+
diff --git a/library/cpp/testing/mock_server/CMakeLists.darwin-x86_64.txt b/library/cpp/testing/mock_server/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..a5d1538c15
--- /dev/null
+++ b/library/cpp/testing/mock_server/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,19 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(cpp-testing-mock_server)
+target_link_libraries(cpp-testing-mock_server PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ cpp-http-misc
+ cpp-http-server
+)
+target_sources(cpp-testing-mock_server PRIVATE
+ ${CMAKE_SOURCE_DIR}/library/cpp/testing/mock_server/server.cpp
+)
diff --git a/library/cpp/testing/mock_server/CMakeLists.linux-aarch64.txt b/library/cpp/testing/mock_server/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..c630a22d23
--- /dev/null
+++ b/library/cpp/testing/mock_server/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,20 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(cpp-testing-mock_server)
+target_link_libraries(cpp-testing-mock_server PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-http-misc
+ cpp-http-server
+)
+target_sources(cpp-testing-mock_server PRIVATE
+ ${CMAKE_SOURCE_DIR}/library/cpp/testing/mock_server/server.cpp
+)
diff --git a/library/cpp/testing/mock_server/CMakeLists.linux-x86_64.txt b/library/cpp/testing/mock_server/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..c630a22d23
--- /dev/null
+++ b/library/cpp/testing/mock_server/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,20 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(cpp-testing-mock_server)
+target_link_libraries(cpp-testing-mock_server PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-http-misc
+ cpp-http-server
+)
+target_sources(cpp-testing-mock_server PRIVATE
+ ${CMAKE_SOURCE_DIR}/library/cpp/testing/mock_server/server.cpp
+)
diff --git a/library/cpp/testing/mock_server/CMakeLists.txt b/library/cpp/testing/mock_server/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/library/cpp/testing/mock_server/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/library/cpp/testing/mock_server/CMakeLists.windows-x86_64.txt b/library/cpp/testing/mock_server/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..a5d1538c15
--- /dev/null
+++ b/library/cpp/testing/mock_server/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,19 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(cpp-testing-mock_server)
+target_link_libraries(cpp-testing-mock_server PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ cpp-http-misc
+ cpp-http-server
+)
+target_sources(cpp-testing-mock_server PRIVATE
+ ${CMAKE_SOURCE_DIR}/library/cpp/testing/mock_server/server.cpp
+)
diff --git a/library/cpp/testing/mock_server/ut/server_ut.cpp b/library/cpp/testing/mock_server/ut/server_ut.cpp
new file mode 100644
index 0000000000..74b3dcec05
--- /dev/null
+++ b/library/cpp/testing/mock_server/ut/server_ut.cpp
@@ -0,0 +1,40 @@
+#include <library/cpp/testing/mock_server/server.h>
+
+#include <library/cpp/http/simple/http_client.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(Server) {
+ int i;
+
+ Y_UNIT_TEST(pong) {
+ TPortManager pm;
+ ui16 port = pm.GetPort(80);
+ NMock::TMockServer server(port, []() { return new NMock::TPong; });
+
+ TKeepAliveHttpClient cl("localhost", port);
+ UNIT_ASSERT_VALUES_EQUAL(200, cl.DoGet("/ping"));
+ UNIT_ASSERT_VALUES_EQUAL(404, cl.DoGet("/kek"));
+ }
+
+ Y_UNIT_TEST(custom) {
+ class TCustomReplier: public TRequestReplier {
+ public:
+ bool DoReply(const TReplyParams& params) override {
+ THttpResponse resp(HttpCodes::HTTP_OK);
+ resp.SetContent("everithing is ok");
+ resp.OutTo(params.Output);
+
+ return true;
+ }
+ };
+
+ TPortManager pm;
+ ui16 port = pm.GetPort(80);
+ NMock::TMockServer server(port, []() { return new TCustomReplier; });
+
+ TKeepAliveHttpClient cl("localhost", port);
+ TStringStream out;
+ UNIT_ASSERT_VALUES_EQUAL(200, cl.DoGet("/foo", &out));
+ UNIT_ASSERT_VALUES_EQUAL("everithing is ok", out.Str());
+ }
+}
diff --git a/library/cpp/testing/mock_server/ut/ya.make b/library/cpp/testing/mock_server/ut/ya.make
new file mode 100644
index 0000000000..f4ee34458f
--- /dev/null
+++ b/library/cpp/testing/mock_server/ut/ya.make
@@ -0,0 +1,11 @@
+UNITTEST_FOR(library/cpp/testing/mock_server)
+
+PEERDIR(
+ library/cpp/http/simple
+)
+
+SRCS(
+ server_ut.cpp
+)
+
+END()
diff --git a/ydb/library/yql/providers/ya.make b/ydb/library/yql/providers/ya.make
index 5c17a8919d..5366e2c7d8 100644
--- a/ydb/library/yql/providers/ya.make
+++ b/ydb/library/yql/providers/ya.make
@@ -8,6 +8,7 @@ RECURSE(
s3
solomon
ydb
+ yt
function
generic
)
diff --git a/ydb/library/yql/providers/yt/CMakeLists.txt b/ydb/library/yql/providers/yt/CMakeLists.txt
index e38ff3d7da..8e464fc08e 100644
--- a/ydb/library/yql/providers/yt/CMakeLists.txt
+++ b/ydb/library/yql/providers/yt/CMakeLists.txt
@@ -13,5 +13,6 @@ add_subdirectory(expr_nodes)
add_subdirectory(gateway)
add_subdirectory(job)
add_subdirectory(lib)
+add_subdirectory(mkql_dq)
add_subdirectory(opt)
add_subdirectory(provider)
diff --git a/ydb/library/yql/providers/yt/codec/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/codec/CMakeLists.darwin-x86_64.txt
index a2627c4a3e..eae0a3b160 100644
--- a/ydb/library/yql/providers/yt/codec/CMakeLists.darwin-x86_64.txt
+++ b/ydb/library/yql/providers/yt/codec/CMakeLists.darwin-x86_64.txt
@@ -7,6 +7,7 @@
add_subdirectory(codegen)
+add_subdirectory(ut)
add_library(providers-yt-codec)
target_compile_options(providers-yt-codec PRIVATE
diff --git a/ydb/library/yql/providers/yt/codec/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/codec/CMakeLists.linux-aarch64.txt
index a0a504ebb8..e9793a476e 100644
--- a/ydb/library/yql/providers/yt/codec/CMakeLists.linux-aarch64.txt
+++ b/ydb/library/yql/providers/yt/codec/CMakeLists.linux-aarch64.txt
@@ -7,6 +7,7 @@
add_subdirectory(codegen)
+add_subdirectory(ut)
add_library(providers-yt-codec)
target_compile_options(providers-yt-codec PRIVATE
diff --git a/ydb/library/yql/providers/yt/codec/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/codec/CMakeLists.linux-x86_64.txt
index a0a504ebb8..e9793a476e 100644
--- a/ydb/library/yql/providers/yt/codec/CMakeLists.linux-x86_64.txt
+++ b/ydb/library/yql/providers/yt/codec/CMakeLists.linux-x86_64.txt
@@ -7,6 +7,7 @@
add_subdirectory(codegen)
+add_subdirectory(ut)
add_library(providers-yt-codec)
target_compile_options(providers-yt-codec PRIVATE
diff --git a/ydb/library/yql/providers/yt/codec/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/codec/CMakeLists.windows-x86_64.txt
index a2627c4a3e..eae0a3b160 100644
--- a/ydb/library/yql/providers/yt/codec/CMakeLists.windows-x86_64.txt
+++ b/ydb/library/yql/providers/yt/codec/CMakeLists.windows-x86_64.txt
@@ -7,6 +7,7 @@
add_subdirectory(codegen)
+add_subdirectory(ut)
add_library(providers-yt-codec)
target_compile_options(providers-yt-codec PRIVATE
diff --git a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.darwin-x86_64.txt
index a96cbb28b3..c3ae973d20 100644
--- a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.darwin-x86_64.txt
+++ b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.darwin-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_rescompiler_bin
TOOL_rescompiler_dependency
diff --git a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-aarch64.txt
index 171d66fff4..2e39f45b72 100644
--- a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-aarch64.txt
+++ b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-aarch64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_rescompiler_bin
TOOL_rescompiler_dependency
diff --git a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-x86_64.txt
index 171d66fff4..2e39f45b72 100644
--- a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-x86_64.txt
+++ b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.linux-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_rescompiler_bin
TOOL_rescompiler_dependency
diff --git a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.windows-x86_64.txt
index a96cbb28b3..c3ae973d20 100644
--- a/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.windows-x86_64.txt
+++ b/ydb/library/yql/providers/yt/codec/codegen/CMakeLists.windows-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_rescompiler_bin
TOOL_rescompiler_dependency
diff --git a/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..e9fafaccff
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,75 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-codegen-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-codegen-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ yt-codec-codegen
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-yt-codec
+)
+target_link_options(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ -Wl,-platform_version,macos,11.0,11.0
+ -fPIC
+ -fPIC
+ -framework
+ CoreFoundation
+)
+target_sources(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen/ut/yt_codec_cg_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-codegen-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-codec-codegen-ut)
diff --git a/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..940a32c07b
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,78 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-codegen-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-codegen-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-testing-unittest_main
+ yt-codec-codegen
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-yt-codec
+)
+target_link_options(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+)
+target_sources(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen/ut/yt_codec_cg_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-codegen-ut
+ cpp-malloc-jemalloc
+)
+vcs_info(ydb-library-yql-providers-yt-codec-codegen-ut)
diff --git a/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..4455b22435
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,80 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-codegen-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-codegen-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ yt-codec-codegen
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-yt-codec
+)
+target_link_options(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+)
+target_sources(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen/ut/yt_codec_cg_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-codegen-ut
+ cpp-malloc-tcmalloc
+ libs-tcmalloc-no_percpu_cache
+)
+vcs_info(ydb-library-yql-providers-yt-codec-codegen-ut)
diff --git a/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.txt b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..0189d49889
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/codegen/ut/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,68 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-codegen-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-codegen-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ yt-codec-codegen
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-yt-codec
+)
+target_sources(ydb-library-yql-providers-yt-codec-codegen-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/codegen/ut/yt_codec_cg_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-codegen-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-codegen-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-codec-codegen-ut)
diff --git a/ydb/library/yql/providers/yt/codec/ut/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..d60b5ec3fb
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,78 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-codec
+ cpp-yson-node
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-common-codec
+ providers-common-mkql
+ yt-lib-yson_helpers
+)
+target_link_options(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ -Wl,-platform_version,macos,11.0,11.0
+ -fPIC
+ -fPIC
+ -framework
+ CoreFoundation
+)
+target_sources(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/yt_codec_io_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-codec-ut)
diff --git a/ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..d00ff286f9
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,81 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-testing-unittest_main
+ providers-yt-codec
+ cpp-yson-node
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-common-codec
+ providers-common-mkql
+ yt-lib-yson_helpers
+)
+target_link_options(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+)
+target_sources(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/yt_codec_io_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-ut
+ cpp-malloc-jemalloc
+)
+vcs_info(ydb-library-yql-providers-yt-codec-ut)
diff --git a/ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..1004710d7f
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,83 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-codec
+ cpp-yson-node
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-common-codec
+ providers-common-mkql
+ yt-lib-yson_helpers
+)
+target_link_options(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+)
+target_sources(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/yt_codec_io_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-ut
+ cpp-malloc-tcmalloc
+ libs-tcmalloc-no_percpu_cache
+)
+vcs_info(ydb-library-yql-providers-yt-codec-ut)
diff --git a/ydb/library/yql/providers/yt/codec/ut/CMakeLists.txt b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/codec/ut/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..f5ea5a23c5
--- /dev/null
+++ b/ydb/library/yql/providers/yt/codec/ut/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,71 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-codec-ut)
+target_compile_options(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec
+)
+target_link_libraries(ydb-library-yql-providers-yt-codec-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-codec
+ cpp-yson-node
+ minikql-computation-llvm
+ udf-service-exception_policy
+ library-yql-sql
+ yql-sql-pg_dummy
+ providers-common-codec
+ providers-common-mkql
+ yt-lib-yson_helpers
+)
+target_sources(ydb-library-yql-providers-yt-codec-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/codec/yt_codec_io_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-codec-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-codec-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-codec-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-codec-ut)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.darwin-x86_64.txt
index c4bc336489..b911e9771d 100644
--- a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.darwin-x86_64.txt
+++ b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.darwin-x86_64.txt
@@ -6,6 +6,8 @@
# original buildsystem will not be accepted.
+add_subdirectory(dq)
+add_subdirectory(ut)
add_library(providers-yt-comp_nodes)
target_compile_options(providers-yt-comp_nodes PRIVATE
diff --git a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-aarch64.txt
index a60a2a41b1..763f7bd6c1 100644
--- a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-aarch64.txt
+++ b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-aarch64.txt
@@ -6,6 +6,8 @@
# original buildsystem will not be accepted.
+add_subdirectory(dq)
+add_subdirectory(ut)
add_library(providers-yt-comp_nodes)
target_compile_options(providers-yt-comp_nodes PRIVATE
diff --git a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-x86_64.txt
index a60a2a41b1..763f7bd6c1 100644
--- a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-x86_64.txt
+++ b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.linux-x86_64.txt
@@ -6,6 +6,8 @@
# original buildsystem will not be accepted.
+add_subdirectory(dq)
+add_subdirectory(ut)
add_library(providers-yt-comp_nodes)
target_compile_options(providers-yt-comp_nodes PRIVATE
diff --git a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.windows-x86_64.txt
index c4bc336489..b911e9771d 100644
--- a/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.windows-x86_64.txt
+++ b/ydb/library/yql/providers/yt/comp_nodes/CMakeLists.windows-x86_64.txt
@@ -6,6 +6,8 @@
# original buildsystem will not be accepted.
+add_subdirectory(dq)
+add_subdirectory(ut)
add_library(providers-yt-comp_nodes)
target_compile_options(providers-yt-comp_nodes PRIVATE
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..b1d1973fea
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,30 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-comp_nodes-dq)
+target_compile_options(yt-comp_nodes-dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-comp_nodes-dq PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ minikql-computation-llvm
+ providers-yt-comp_nodes
+ providers-yt-codec
+ providers-common-codec
+ cpp-mapreduce-interface
+ cpp-mapreduce-common
+ cpp-yson-node
+ yt-yt-core
+)
+target_sources(yt-comp_nodes-dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp
+)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..2564b94e36
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,34 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-comp_nodes-dq)
+target_compile_options(yt-comp_nodes-dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-comp_nodes-dq PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ minikql-computation-llvm
+ providers-yt-comp_nodes
+ providers-yt-codec
+ providers-common-codec
+ cpp-mapreduce-interface
+ cpp-mapreduce-common
+ cpp-yson-node
+ yt-yt-core
+ yt-yt-client
+ yt-client-arrow
+)
+target_sources(yt-comp_nodes-dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp
+)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..2564b94e36
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,34 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-comp_nodes-dq)
+target_compile_options(yt-comp_nodes-dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-comp_nodes-dq PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ minikql-computation-llvm
+ providers-yt-comp_nodes
+ providers-yt-codec
+ providers-common-codec
+ cpp-mapreduce-interface
+ cpp-mapreduce-common
+ cpp-yson-node
+ yt-yt-core
+ yt-yt-client
+ yt-client-arrow
+)
+target_sources(yt-comp_nodes-dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp
+)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.txt b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..b1d1973fea
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,30 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-comp_nodes-dq)
+target_compile_options(yt-comp_nodes-dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-comp_nodes-dq PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ minikql-computation-llvm
+ providers-yt-comp_nodes
+ providers-yt-codec
+ providers-common-codec
+ cpp-mapreduce-interface
+ cpp-mapreduce-common
+ cpp-yson-node
+ yt-yt-core
+)
+target_sources(yt-comp_nodes-dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp
+)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp
new file mode 100644
index 0000000000..c02a693f69
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.cpp
@@ -0,0 +1,24 @@
+#include "dq_yt_factory.h"
+#include "dq_yt_reader.h"
+#include "dq_yt_writer.h"
+
+namespace NYql {
+
+using namespace NKikimr::NMiniKQL;
+
+TComputationNodeFactory GetDqYtFactory(NKikimr::NMiniKQL::IStatsRegistry* jobStats) {
+ return [=] (TCallable& callable, const TComputationNodeFactoryContext& ctx) -> IComputationNode* {
+ TStringBuf name = callable.GetType()->GetName();
+ if (name == "DqYtRead") {
+ return NDqs::WrapDqYtRead(callable, jobStats, ctx);
+ }
+
+ if (name == "YtDqRowsWideWrite") {
+ return NDqs::WrapYtDqRowsWideWrite(callable, ctx);
+ }
+
+ return nullptr;
+ };
+}
+
+} // NYql
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.h b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.h
new file mode 100644
index 0000000000..7f7a95da80
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_factory.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <ydb/library/yql/minikql/computation/mkql_computation_node.h>
+#include <ydb/library/yql/minikql/mkql_stats_registry.h>
+
+namespace NYql {
+
+NKikimr::NMiniKQL::TComputationNodeFactory GetDqYtFactory(NKikimr::NMiniKQL::IStatsRegistry* jobStats = nullptr);
+
+}
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp
new file mode 100644
index 0000000000..8461b8e495
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.cpp
@@ -0,0 +1,120 @@
+#include "dq_yt_reader.h"
+
+#include "dq_yt_reader_impl.h"
+
+#include "dq_yt_rpc_reader.h"
+
+namespace NYql::NDqs {
+
+using namespace NKikimr::NMiniKQL;
+
+class TDqYtReadWrapperHttp : public TDqYtReadWrapperBase<TDqYtReadWrapperHttp, TFileInputState> {
+public:
+using TInputType = NYT::TRawTableReaderPtr;
+ TDqYtReadWrapperHttp(const TComputationNodeFactoryContext& ctx, const TString& clusterName,
+ const TString& token, const NYT::TNode& inputSpec, const NYT::TNode& samplingSpec,
+ const TVector<ui32>& inputGroups,
+ TType* itemType, const TVector<TString>& tableNames, TVector<std::pair<NYT::TRichYPath, NYT::TFormat>>&& tables, NKikimr::NMiniKQL::IStatsRegistry* jobStats, size_t inflight, size_t timeout)
+ : TDqYtReadWrapperBase<TDqYtReadWrapperHttp, TFileInputState>(ctx, clusterName, token, inputSpec, samplingSpec, inputGroups, itemType, tableNames, std::move(tables), jobStats, inflight, timeout) {}
+
+ void MakeState(TComputationContext& ctx, NUdf::TUnboxedValue& state) const {
+ TVector<NYT::TRawTableReaderPtr> rawReaders;
+
+ NYT::TCreateClientOptions createOpts;
+ if (Token) {
+ createOpts.Token(Token);
+ }
+
+ auto client = NYT::CreateClient(ClusterName, createOpts);
+ NYT::TTableReaderOptions readerOptions;
+ if (!SamplingSpec.IsUndefined()) {
+ readerOptions.Config(SamplingSpec);
+ }
+
+ for (auto [richYPath, format]: Tables) {
+ NYT::TRawTableReaderPtr reader;
+ const int lastAttempt = NYT::TConfig::Get()->ReadRetryCount - 1;
+ for (int attempt = 0; attempt <= lastAttempt; ++attempt) {
+ try {
+ if (richYPath.TransactionId_) {
+ auto transaction = client->AttachTransaction(richYPath.TransactionId_.GetRef());
+ richYPath.TransactionId_.Clear();
+ reader = transaction->CreateRawReader(richYPath, format, readerOptions.CreateTransaction(false));
+ } else {
+ reader = client->CreateRawReader(richYPath, format, readerOptions.CreateTransaction(true));
+ }
+ break;
+ } catch (const NYT::TErrorResponse& e) {
+ Cerr << "Error creating reader for " << richYPath.Path_ << ": " << e.what();
+ // Already retried inside CreateRawReader
+ throw;
+ } catch (const yexception& e) {
+ Cerr << "Error creating reader for " << richYPath.Path_ << ": " << e.what();
+ if (attempt == lastAttempt) {
+ throw;
+ }
+ NYT::NDetail::TWaitProxy::Get()->Sleep(NYT::TConfig::Get()->RetryInterval);
+ }
+ }
+ rawReaders.push_back(reader);
+ }
+
+ state = ctx.HolderFactory.Create<TDqYtReadWrapperBase<TDqYtReadWrapperHttp, TFileInputState>::TState>(Specs, ctx.HolderFactory, std::move(rawReaders), 4, 4_MB);
+ }
+};
+
+IComputationNode* WrapDqYtRead(TCallable& callable, NKikimr::NMiniKQL::IStatsRegistry* jobStats, const TComputationNodeFactoryContext& ctx) {
+ MKQL_ENSURE(callable.GetInputsCount() == 8 || callable.GetInputsCount() == 9, "Expected 8 or 9 arguments.");
+
+ TString clusterName(AS_VALUE(TDataLiteral, callable.GetInput(0))->AsValue().AsStringRef());
+ TString tokenName(AS_VALUE(TDataLiteral, callable.GetInput(1))->AsValue().AsStringRef());
+ TString inputSpec(AS_VALUE(TDataLiteral, callable.GetInput(2))->AsValue().AsStringRef());
+ TString samplingSpec(AS_VALUE(TDataLiteral, callable.GetInput(3))->AsValue().AsStringRef());
+ TString token;
+ if (auto sec = ctx.SecureParamsProvider) {
+ NUdf::TStringRef val;
+ if (sec->GetSecureParam(tokenName, val)) {
+ token = val;
+ }
+ }
+
+ TVector<ui32> inputGroups;
+ TVector<TString> tableNames;
+ TVector<std::pair<NYT::TRichYPath, NYT::TFormat>> tables; // richpath, skiff
+ TListLiteral* groupList = AS_VALUE(TListLiteral, callable.GetInput(4));
+ for (ui32 grp = 0; grp < groupList->GetItemsCount(); ++grp) {
+ TListLiteral* tableList = AS_VALUE(TListLiteral, groupList->GetItems()[grp]);
+ for (ui32 i = 0; i < tableList->GetItemsCount(); ++i) {
+ TTupleLiteral* tableTuple = AS_VALUE(TTupleLiteral, tableList->GetItems()[i]);
+ YQL_ENSURE(tableTuple->GetValuesCount() == 3);
+ tableNames.emplace_back(AS_VALUE(TDataLiteral, tableTuple->GetValue(0))->AsValue().AsStringRef());
+ inputGroups.push_back(grp);
+
+ NYT::TRichYPath richYPath;
+ NYT::Deserialize(richYPath, NYT::NodeFromYsonString(TString(AS_VALUE(TDataLiteral, tableTuple->GetValue(1))->AsValue().AsStringRef())));
+ tables.emplace_back(richYPath, NYT::TFormat(NYT::NodeFromYsonString(AS_VALUE(TDataLiteral, tableTuple->GetValue(2))->AsValue().AsStringRef())));
+ }
+ }
+ if (1 == groupList->GetItemsCount()) {
+ inputGroups.clear();
+ }
+ size_t timeout(AS_VALUE(TDataLiteral, callable.GetInput(7))->AsValue().Get<size_t>());
+#ifdef __linux__
+ size_t inflight(AS_VALUE(TDataLiteral, callable.GetInput(6))->AsValue().Get<size_t>());
+ if (inflight) {
+ return new TDqYtReadWrapperBase<TDqYtReadWrapperRPC, TParallelFileInputState>(ctx, clusterName, token,
+ NYT::NodeFromYsonString(inputSpec), samplingSpec ? NYT::NodeFromYsonString(samplingSpec) : NYT::TNode(),
+ inputGroups, static_cast<TType*>(callable.GetInput(5).GetNode()), tableNames, std::move(tables), jobStats, inflight, timeout);
+ } else {
+ return new TDqYtReadWrapperBase<TDqYtReadWrapperHttp, TFileInputState>(ctx, clusterName, token,
+ NYT::NodeFromYsonString(inputSpec), samplingSpec ? NYT::NodeFromYsonString(samplingSpec) : NYT::TNode(),
+ inputGroups, static_cast<TType*>(callable.GetInput(5).GetNode()), tableNames, std::move(tables), jobStats, inflight, timeout);
+ }
+#else
+ return new TDqYtReadWrapperBase<TDqYtReadWrapperHttp, TFileInputState>(ctx, clusterName, token,
+ NYT::NodeFromYsonString(inputSpec), samplingSpec ? NYT::NodeFromYsonString(samplingSpec) : NYT::TNode(),
+ inputGroups, static_cast<TType*>(callable.GetInput(5).GetNode()), tableNames, std::move(tables), jobStats, 0, timeout);
+#endif
+}
+
+} // NYql
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.h b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.h
new file mode 100644
index 0000000000..2dd5c2deef
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <ydb/library/yql/minikql/computation/mkql_computation_node.h>
+#include <ydb/library/yql/minikql/mkql_stats_registry.h>
+#include <ydb/library/yql/minikql/mkql_node.h>
+
+namespace NYql::NDqs {
+
+NKikimr::NMiniKQL::IComputationNode* WrapDqYtRead(NKikimr::NMiniKQL::TCallable& callable, NKikimr::NMiniKQL::IStatsRegistry* jobStats, const NKikimr::NMiniKQL::TComputationNodeFactoryContext& ctx);
+
+} // NYql
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader_impl.h b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader_impl.h
new file mode 100644
index 0000000000..607c953786
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_reader_impl.h
@@ -0,0 +1,205 @@
+#pragma once
+
+#include "dq_yt_reader.h"
+
+#include <ydb/library/yql/providers/yt/comp_nodes/yql_mkql_file_input_state.h>
+#include <ydb/library/yql/providers/yt/codec/yt_codec.h>
+#include <ydb/library/yql/providers/common/codec/yql_codec.h>
+#include <ydb/library/yql/minikql/mkql_node_cast.h>
+#include <ydb/library/yql/minikql/computation/mkql_computation_node_codegen.h>
+
+#include <yt/cpp/mapreduce/interface/common.h>
+#include <yt/cpp/mapreduce/interface/errors.h>
+#include <yt/cpp/mapreduce/interface/client.h>
+#include <yt/cpp/mapreduce/interface/serialize.h>
+#include <yt/cpp/mapreduce/interface/config.h>
+#include <yt/cpp/mapreduce/common/wait_proxy.h>
+
+#include <library/cpp/yson/node/node.h>
+#include <library/cpp/yson/node/node_io.h>
+
+#include <util/generic/size_literals.h>
+#include <util/stream/output.h>
+
+namespace NYql::NDqs {
+
+using namespace NKikimr::NMiniKQL;
+
+template<typename T, typename IS>
+class TDqYtReadWrapperBase : public TStatefulWideFlowCodegeneratorNode<TDqYtReadWrapperBase<T, IS>> {
+using TBaseComputation = TStatefulWideFlowCodegeneratorNode<TDqYtReadWrapperBase<T, IS>>;
+public:
+ TDqYtReadWrapperBase(const TComputationNodeFactoryContext& ctx, const TString& clusterName,
+ const TString& token, const NYT::TNode& inputSpec, const NYT::TNode& samplingSpec,
+ const TVector<ui32>& inputGroups,
+ TType* itemType, const TVector<TString>& tableNames, TVector<std::pair<NYT::TRichYPath, NYT::TFormat>>&& tables, NKikimr::NMiniKQL::IStatsRegistry* jobStats, size_t inflight,
+ size_t timeout) : TBaseComputation(ctx.Mutables, this, EValueRepresentation::Boxed, EValueRepresentation::Boxed)
+ , Width(AS_TYPE(TStructType, itemType)->GetMembersCount())
+ , CodecCtx(ctx.Env, ctx.FunctionRegistry, &ctx.HolderFactory)
+ , ClusterName(clusterName)
+ , Token(token)
+ , SamplingSpec(samplingSpec)
+ , Tables(std::move(tables))
+ , Inflight(inflight)
+ , Timeout(timeout)
+ {
+ Specs.SetUseSkiff("", TMkqlIOSpecs::ESystemField::RowIndex | TMkqlIOSpecs::ESystemField::RangeIndex);
+ Specs.Init(CodecCtx, inputSpec, inputGroups, tableNames, itemType, {}, {}, jobStats);
+ }
+
+ class TState: public TComputationValue<TState>, public IS {
+ public:
+
+ template<typename... Args>
+ TState(TMemoryUsageInfo* memInfo, Args&&... args)
+ : TComputationValue<TState>(memInfo)
+ , IS(std::forward<Args>(args)...)
+ {
+ IS::SetNextBlockCallback([this]() { Yield_ = true; });
+ }
+
+ virtual ~TState() = default;
+
+ NUdf::TUnboxedValuePod FetchRecord() {
+ if (!AtStart_) {
+ IS::Next();
+ }
+ AtStart_ = false;
+
+ if (!IS::IsValid()) {
+ IS::Finish();
+ return NUdf::TUnboxedValuePod::MakeFinish();
+ }
+
+ if (Yield_) {
+ Yield_ = false;
+ AtStart_ = true;
+ return NUdf::TUnboxedValuePod::MakeYield();
+ }
+
+ return IS::GetCurrent().Release();
+ }
+
+ private:
+ bool AtStart_ = true;
+ bool Yield_ = false;
+ };
+
+ void MakeState(TComputationContext& ctx, NUdf::TUnboxedValue& state) const {
+ static_cast<const T*>(this)->MakeState(ctx, state);
+ }
+
+ EFetchResult DoCalculate(NUdf::TUnboxedValue& state, TComputationContext& ctx, NUdf::TUnboxedValue*const* output) const {
+ if (!state.HasValue()) {
+ MakeState(ctx, state);
+ }
+
+ if (const auto value = static_cast<TState&>(*state.AsBoxed()).FetchRecord(); value.IsFinish())
+ return EFetchResult::Finish;
+ else if (value.IsYield())
+ return EFetchResult::Yield;
+ else {
+ const auto elements = value.GetElements();
+ for (ui32 i = 0U; i < Width; ++i)
+ if (const auto out = *output++)
+ *out = elements[i];
+
+ }
+
+ return EFetchResult::One;
+ }
+
+#ifndef MKQL_DISABLE_CODEGEN
+ ICodegeneratorInlineWideNode::TGenerateResult DoGenGetValues(const TCodegenContext& ctx, Value* statePtr, BasicBlock*& block) const {
+ auto& context = ctx.Codegen.GetContext();
+
+ const auto valueType = Type::getInt128Ty(context);
+ const auto arrayType = ArrayType::get(valueType, Width);
+ const auto pointerType = PointerType::getUnqual(arrayType);
+ const auto structPtrType = PointerType::getUnqual(StructType::get(context));
+ const auto statusType = Type::getInt32Ty(context);
+
+ const auto stateType = StructType::get(context, {
+ structPtrType, // vtbl
+ Type::getInt32Ty(context), // ref
+ Type::getInt16Ty(context), // abi
+ Type::getInt16Ty(context), // reserved
+ structPtrType // meminfo
+ });
+
+ const auto statePtrType = PointerType::getUnqual(stateType);
+
+ const auto placeholder = new AllocaInst(pointerType, 0U, "paceholder", &ctx.Func->getEntryBlock().back());
+
+ const auto make = BasicBlock::Create(context, "make", ctx.Func);
+ const auto main = BasicBlock::Create(context, "main", ctx.Func);
+ const auto good = BasicBlock::Create(context, "good", ctx.Func);
+ const auto done = BasicBlock::Create(context, "done", ctx.Func);
+
+ BranchInst::Create(main, make, HasValue(statePtr, block), block);
+ block = make;
+
+ const auto self = CastInst::Create(Instruction::IntToPtr, ConstantInt::get(Type::getInt64Ty(context), uintptr_t(static_cast<const TDqYtReadWrapperBase<T, IS>*>(this))), structPtrType, "self", block);
+ const auto makeFunc = ConstantInt::get(Type::getInt64Ty(context), GetMethodPtr(&TDqYtReadWrapperBase<T, IS>::MakeState));
+ const auto makeType = FunctionType::get(Type::getVoidTy(context), {self->getType(), ctx.Ctx->getType(), statePtr->getType()}, false);
+ const auto makeFuncPtr = CastInst::Create(Instruction::IntToPtr, makeFunc, PointerType::getUnqual(makeType), "function", block);
+ CallInst::Create(makeType, makeFuncPtr, {self, ctx.Ctx, statePtr}, "", block);
+ BranchInst::Create(main, block);
+
+ block = main;
+
+ const auto state = new LoadInst(valueType, statePtr, "state", block);
+ const auto half = CastInst::Create(Instruction::Trunc, state, Type::getInt64Ty(context), "half", block);
+ const auto stateArg = CastInst::Create(Instruction::IntToPtr, half, statePtrType, "state_arg", block);
+
+ const auto func = ConstantInt::get(Type::getInt64Ty(context), GetMethodPtr(&TState::FetchRecord));
+ const auto funcType = FunctionType::get(valueType, { statePtrType }, false);
+ const auto funcPtr = CastInst::Create(Instruction::IntToPtr, func, PointerType::getUnqual(funcType), "fetch_func", block);
+ const auto fetch = CallInst::Create(funcType, funcPtr, { stateArg }, "fetch", block);
+
+ const auto result = PHINode::Create(statusType, 2U, "result", done);
+ const auto special = SelectInst::Create(IsYield(fetch, block), ConstantInt::get(statusType, static_cast<i32>(EFetchResult::Yield)), ConstantInt::get(statusType, static_cast<i32>(EFetchResult::Finish)), "special", block);
+ result->addIncoming(special, block);
+
+ BranchInst::Create(done, good, IsSpecial(fetch, block), block);
+
+ block = good;
+
+ const auto elements = CallBoxedValueVirtualMethod<NUdf::TBoxedValueAccessor::EMethod::GetElements>(pointerType, fetch, ctx.Codegen, block);
+ new StoreInst(elements, placeholder, block);
+
+ result->addIncoming(ConstantInt::get(statusType, static_cast<i32>(EFetchResult::One)), block);
+
+ BranchInst::Create(done, block);
+
+ block = done;
+
+ ICodegeneratorInlineWideNode::TGettersList getters;
+ getters.reserve(Width);
+ for (ui32 i = 0U; i < Width; ++i) {
+ getters.emplace_back([i, placeholder, pointerType, arrayType, valueType](const TCodegenContext& ctx, BasicBlock*& block) {
+ const auto indexType = Type::getInt32Ty(ctx.Codegen.GetContext());
+ const auto pointer = new LoadInst(pointerType, placeholder, (TString("pointer_") += ToString(i)).c_str(), block);
+ const auto ptr = GetElementPtrInst::CreateInBounds(arrayType, pointer, {ConstantInt::get(indexType, 0), ConstantInt::get(indexType, i)}, (TString("ptr_") += ToString(i)).c_str(), block);
+ const auto load = new LoadInst(valueType, ptr, (TString("load_") += ToString(i)).c_str(), block);
+ return load;
+ });
+ }
+
+ return {result, std::move(getters)};
+ }
+#endif
+ void RegisterDependencies() const final {}
+
+ const ui32 Width;
+ NCommon::TCodecContext CodecCtx;
+ TMkqlIOSpecs Specs;
+
+ TString ClusterName;
+ TString Token;
+ NYT::TNode SamplingSpec;
+ TVector<std::pair<NYT::TRichYPath, NYT::TFormat>> Tables;
+ size_t Inflight;
+ size_t Timeout;
+};
+}
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.cpp b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.cpp
new file mode 100644
index 0000000000..5fb09535f2
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.cpp
@@ -0,0 +1,347 @@
+#include "dq_yt_rpc_reader.h"
+
+#include "yt/cpp/mapreduce/common/helpers.h"
+
+#include <yt/yt/library/auth/auth.h>
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/client/api/rpc_proxy/client_impl.h>
+#include <yt/yt/client/api/rpc_proxy/config.h>
+#include <yt/yt/client/api/rpc_proxy/connection.h>
+#include <yt/yt/client/api/rpc_proxy/row_stream.h>
+
+namespace NYql::NDqs {
+
+using namespace NKikimr::NMiniKQL;
+
+namespace {
+TStatKey RPCReaderAwaitingStallTime("Job_RPCReaderAwaitingStallTime", true);
+NYT::NYPath::TRichYPath ConvertYPathFromOld(const NYT::TRichYPath& richYPath) {
+ NYT::NYPath::TRichYPath tableYPath(richYPath.Path_);
+ const auto& rngs = richYPath.GetRanges();
+ if (rngs) {
+ TVector<NYT::NChunkClient::TReadRange> ranges;
+ for (const auto& rng: *rngs) {
+ auto& range = ranges.emplace_back();
+ if (rng.LowerLimit_.Offset_) {
+ range.LowerLimit().SetOffset(*rng.LowerLimit_.Offset_);
+ }
+
+ if (rng.LowerLimit_.TabletIndex_) {
+ range.LowerLimit().SetTabletIndex(*rng.LowerLimit_.TabletIndex_);
+ }
+
+ if (*rng.LowerLimit_.RowIndex_) {
+ range.LowerLimit().SetRowIndex(*rng.LowerLimit_.RowIndex_);
+ }
+
+ if (rng.UpperLimit_.Offset_) {
+ range.UpperLimit().SetOffset(*rng.UpperLimit_.Offset_);
+ }
+
+ if (rng.UpperLimit_.TabletIndex_) {
+ range.UpperLimit().SetTabletIndex(*rng.UpperLimit_.TabletIndex_);
+ }
+
+ if (*rng.UpperLimit_.RowIndex_) {
+ range.UpperLimit().SetRowIndex(*rng.UpperLimit_.RowIndex_);
+ }
+ }
+ tableYPath.SetRanges(std::move(ranges));
+ }
+
+ if (richYPath.Columns_) {
+ tableYPath.SetColumns(richYPath.Columns_->Parts_);
+ }
+
+ return tableYPath;
+}
+
+class TFakeRPCReader : public NYT::TRawTableReader {
+public:
+ TFakeRPCReader(NYT::TSharedRef&& payload) : Payload_(std::move(payload)), PayloadStream_(Payload_.Begin(), Payload_.Size()) {}
+
+ bool Retry(const TMaybe<ui32>& rangeIndex, const TMaybe<ui64>& rowIndex) override {
+ Y_UNUSED(rangeIndex);
+ Y_UNUSED(rowIndex);
+ return false;
+ }
+
+ void ResetRetries() override {
+
+ }
+
+ bool HasRangeIndices() const override {
+ return true;
+ };
+
+ size_t DoRead(void* buf, size_t len) override {
+ if (!PayloadStream_.Exhausted()) {
+ return PayloadStream_.Read(buf, len);
+ }
+ return 0;
+ };
+
+ virtual ~TFakeRPCReader() override {
+ }
+private:
+ NYT::TSharedRef Payload_;
+ TMemoryInput PayloadStream_;
+};
+}
+#ifdef RPC_PRINT_TIME
+int cnt = 0;
+auto beg = std::chrono::steady_clock::now();
+std::mutex mtx;
+void print_add(int x) {
+ std::lock_guard l(mtx);
+ Cerr << (cnt += x) << " (" << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - beg).count() << "ms gone from process start)\n";
+}
+#endif
+
+struct TSettingsHolder {
+ NYT::NApi::IConnectionPtr Connection;
+ NYT::TIntrusivePtr<NYT::NApi::NRpcProxy::TClient> Client;
+};
+
+TParallelFileInputState::TParallelFileInputState(const TMkqlIOSpecs& spec,
+ const NKikimr::NMiniKQL::THolderFactory& holderFactory,
+ TVector<NYT::NConcurrency::IAsyncZeroCopyInputStreamPtr>&& rawInputs,
+ size_t blockSize,
+ size_t inflight,
+ std::unique_ptr<TSettingsHolder>&& settings)
+ : InnerState_(new TInnerState (rawInputs.size()))
+ , StateByReader_(rawInputs.size())
+ , Spec_(&spec)
+ , HolderFactory_(holderFactory)
+ , RawInputs_(std::move(rawInputs))
+ , BlockSize_(blockSize)
+ , Inflight_(inflight)
+ , Settings_(std::move(settings))
+ , TimerAwaiting_(RPCReaderAwaitingStallTime, 100)
+{
+#ifdef RPC_PRINT_TIME
+ print_add(1);
+#endif
+ YQL_ENSURE(Spec_->Inputs.size() == RawInputs_.size());
+ MkqlReader_.SetSpecs(*Spec_, HolderFactory_);
+ Valid_ = NextValue();
+}
+
+size_t TParallelFileInputState::GetTableIndex() const {
+ return CurrentInput_;
+}
+
+size_t TParallelFileInputState::GetRecordIndex() const {
+ Y_FAIL("Not implemented");
+ return CurrentRecord_; // returns 1-based index
+}
+
+void TParallelFileInputState::SetNextBlockCallback(std::function<void()> cb) {
+ MkqlReader_.SetNextBlockCallback(cb);
+ OnNextBlockCallback_ = std::move(cb);
+}
+
+bool TParallelFileInputState::IsValid() const {
+ return Valid_;
+}
+
+NUdf::TUnboxedValue TParallelFileInputState::GetCurrent() {
+ return CurrentValue_;
+}
+
+void TParallelFileInputState::Next() {
+ Valid_ = NextValue();
+}
+
+void TParallelFileInputState::Finish() {
+ TimerAwaiting_.Report(Spec_->JobStats_);
+ MkqlReader_.Finish();
+}
+
+bool TParallelFileInputState::RunNext() {
+ while (true) {
+ size_t InputIdx = 0;
+ {
+ std::lock_guard lock(InnerState_->Lock);
+ if (InnerState_->CurrentInputIdx == RawInputs_.size() && InnerState_->CurrentInflight == 0 && InnerState_->Results.empty() && !MkqlReader_.IsValid()) {
+ return false;
+ }
+
+ if (InnerState_->CurrentInflight >= Inflight_ || InnerState_->Results.size() >= Inflight_) {
+ break;
+ }
+
+ while (InnerState_->CurrentInputIdx < InnerState_->IsInputDone.size() && InnerState_->IsInputDone[InnerState_->CurrentInputIdx]) {
+ ++InnerState_->CurrentInputIdx;
+ }
+
+ if (InnerState_->CurrentInputIdx == InnerState_->IsInputDone.size()) {
+ break;
+ }
+ InputIdx = InnerState_->CurrentInputIdx;
+ ++InnerState_->CurrentInputIdx;
+ ++InnerState_->CurrentInflight;
+ }
+
+ RawInputs_[InputIdx]->Read().SubscribeUnique(BIND([state = InnerState_, inputIdx = InputIdx
+#ifdef RPC_PRINT_TIME
+ , startAt = std::chrono::steady_clock::now()
+#endif
+ ](NYT::TErrorOr<NYT::TSharedRef>&& res_){
+#ifdef RPC_PRINT_TIME
+ auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - startAt).count();
+ if (elapsed > 1'000'000) {
+ Cerr << (TStringBuilder() "Warn: request took: " << elapsed << " mcs)\n");
+ }
+#endif
+ if (!res_.IsOK()) {
+ std::lock_guard lock(state->Lock);
+ state->Error = std::move(res_);
+ --state->CurrentInflight;
+ state->WaitPromise.TrySet();
+ return;
+ }
+ auto block = std::move(res_.Value());
+ NYT::NApi::NRpcProxy::NProto::TRowsetDescriptor descriptor;
+ NYT::NApi::NRpcProxy::NProto::TRowsetStatistics statistics;
+
+ auto CurrentPayload_ = std::move(NYT::NApi::NRpcProxy::DeserializeRowStreamBlockEnvelope(block, &descriptor, &statistics));
+ std::lock_guard lock(state->Lock);
+ state->CurrentInputIdx = std::min(state->CurrentInputIdx, inputIdx);
+ --state->CurrentInflight;
+ // skip no-skiff data
+ if (descriptor.rowset_format() != NYT::NApi::NRpcProxy::NProto::RF_FORMAT) {
+ state->WaitPromise.TrySet();
+ return;
+ }
+
+ if (CurrentPayload_.Empty()) {
+ state->IsInputDone[inputIdx] = 1;
+ state->WaitPromise.TrySet();
+ return;
+ }
+ state->Results.emplace(std::move(TResult{inputIdx, std::move(CurrentPayload_)}));
+ state->WaitPromise.TrySet();
+ }));
+ }
+ return true;
+}
+
+bool TParallelFileInputState::NextValue() {
+ for (;;) {
+ if (!RunNext()) {
+ Y_ENSURE(InnerState_->Results.empty());
+#ifdef RPC_PRINT_TIME
+ print_add(-1);
+#endif
+ return false;
+ }
+ if (MkqlReader_.IsValid()) {
+ CurrentValue_ = std::move(MkqlReader_.GetRow());
+ if (!Spec_->InputGroups.empty()) {
+ CurrentValue_ = HolderFactory_.CreateVariantHolder(CurrentValue_.Release(), Spec_->InputGroups.at(CurrentInput_));
+ }
+ MkqlReader_.Next();
+ return true;
+ }
+ if (MkqlReader_.GetRowIndexUnchecked()) {
+ StateByReader_[CurrentInput_].CurrentRow = *MkqlReader_.GetRowIndexUnchecked() - 1;
+ }
+ StateByReader_[CurrentInput_].CurrentRange = MkqlReader_.GetRangeIndexUnchecked();
+ bool needWait = false;
+ {
+ std::lock_guard lock(InnerState_->Lock);
+ needWait = InnerState_->Results.empty();
+ }
+ if (needWait) {
+ auto guard = Guard(TimerAwaiting_);
+ YQL_ENSURE(NYT::NConcurrency::WaitFor(InnerState_->WaitPromise.ToFuture()).IsOK());
+ }
+ TResult result;
+ {
+ std::lock_guard lock(InnerState_->Lock);
+ if (!InnerState_->Error.IsOK()) {
+ InnerState_->Error.ThrowOnError();
+ }
+ if (InnerState_->Results.empty()) {
+ continue;
+ }
+ result = std::move(InnerState_->Results.front());
+ InnerState_->Results.pop();
+ InnerState_->WaitPromise = NYT::NewPromise<void>();
+ }
+ CurrentInput_ = result.Input_;
+ CurrentReader_ = MakeIntrusive<TFakeRPCReader>(std::move(result.Value_));
+ MkqlReader_.SetReader(*CurrentReader_, 1, BlockSize_, ui32(CurrentInput_), true, StateByReader_[CurrentInput_].CurrentRow, StateByReader_[CurrentInput_].CurrentRange);
+ MkqlReader_.Next();
+ }
+}
+
+void TDqYtReadWrapperRPC::MakeState(TComputationContext& ctx, NUdf::TUnboxedValue& state) const {
+ auto connectionConfig = NYT::New<NYT::NApi::NRpcProxy::TConnectionConfig>();
+ connectionConfig->ClusterUrl = ClusterName;
+ connectionConfig->DefaultTotalStreamingTimeout = TDuration::MilliSeconds(Timeout);
+ auto connection = CreateConnection(connectionConfig);
+ auto clientOptions = NYT::NApi::TClientOptions();
+
+ if (Token) {
+ clientOptions.Token = Token;
+ }
+
+ auto client = DynamicPointerCast<NYT::NApi::NRpcProxy::TClient>(connection->CreateClient(clientOptions));
+ Y_VERIFY(client);
+ auto apiServiceProxy = client->CreateApiServiceProxy();
+
+ TVector<NYT::TFuture<NYT::NConcurrency::IAsyncZeroCopyInputStreamPtr>> waitFor;
+
+ for (auto [richYPath, format]: Tables) {
+ auto request = apiServiceProxy.ReadTable();
+ client->InitStreamingRequest(*request);
+ request->ClientAttachmentsStreamingParameters().ReadTimeout = TDuration::MilliSeconds(Timeout);
+
+ TString ppath;
+ auto tableYPath = ConvertYPathFromOld(richYPath);
+
+ NYT::NYPath::ToProto(&ppath, tableYPath);
+ request->set_path(ppath);
+ request->set_desired_rowset_format(NYT::NApi::NRpcProxy::NProto::ERowsetFormat::RF_FORMAT);
+
+ request->set_enable_table_index(true);
+ request->set_enable_range_index(true);
+ request->set_enable_row_index(true);
+ request->set_unordered(Inflight > 1);
+
+ // https://a.yandex-team.ru/arcadia/yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.proto?rev=r11519304#L2338
+ if (!SamplingSpec.IsUndefined()) {
+ TStringStream ss;
+ SamplingSpec.Save(&ss);
+ request->set_config(ss.Str());
+ }
+
+ if (richYPath.TransactionId_) {
+ ui32* dw = richYPath.TransactionId_->dw;
+ // P. S. No proper way to convert it
+ request->mutable_transactional_options()->mutable_transaction_id()->set_first((ui64)dw[3] | (ui64(dw[2]) << 32));
+ request->mutable_transactional_options()->mutable_transaction_id()->set_second((ui64)dw[1] | (ui64(dw[0]) << 32));
+ }
+
+ // Get skiff format yson string
+ TStringStream fmt;
+ format.Config.Save(&fmt);
+ request->set_format(fmt.Str());
+
+ waitFor.emplace_back(std::move(CreateRpcClientInputStream(std::move(request)).ApplyUnique(BIND([](NYT::NConcurrency::IAsyncZeroCopyInputStreamPtr&& stream) {
+ // first packet contains meta, skip it
+ return stream->Read().ApplyUnique(BIND([stream = std::move(stream)](NYT::TSharedRef&&) {
+ return std::move(stream);
+ }));
+ }))));
+ }
+
+ TVector<NYT::NConcurrency::IAsyncZeroCopyInputStreamPtr> rawInputs;
+ NYT::NConcurrency::WaitFor(NYT::AllSucceeded(waitFor)).ValueOrThrow().swap(rawInputs);
+ state = ctx.HolderFactory.Create<TDqYtReadWrapperBase<TDqYtReadWrapperRPC, TParallelFileInputState>::TState>(Specs, ctx.HolderFactory, std::move(rawInputs), 4_MB, Inflight, std::make_unique<TSettingsHolder>(TSettingsHolder{std::move(connection), std::move(client)}));
+}
+}
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.h b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.h
new file mode 100644
index 0000000000..75d5a2d166
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_rpc_reader.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "dq_yt_reader_impl.h"
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYql::NDqs {
+
+using namespace NKikimr::NMiniKQL;
+
+struct TSettingsHolder;
+
+class TParallelFileInputState: public IInputState {
+struct TResult {
+ size_t Input_ = 0;
+ NYT::TSharedRef Value_;
+};
+struct TReaderState {
+ TMaybe<ui64> CurrentRow;
+ TMaybe<ui32> CurrentRange;
+};
+public:
+ TParallelFileInputState(const TMkqlIOSpecs& spec,
+ const NKikimr::NMiniKQL::THolderFactory& holderFactory,
+ TVector<NYT::NConcurrency::IAsyncZeroCopyInputStreamPtr>&& rawInputs,
+ size_t blockSize,
+ size_t inflight,
+ std::unique_ptr<TSettingsHolder>&& client);
+
+ size_t GetTableIndex() const;
+
+ size_t GetRecordIndex() const;
+
+ void SetNextBlockCallback(std::function<void()> cb);
+protected:
+ virtual bool IsValid() const override;
+
+ virtual NUdf::TUnboxedValue GetCurrent() override;
+
+ virtual void Next() override;
+
+ void Finish();
+
+ bool RunNext();
+
+ bool NextValue();
+
+private:
+ // Used to pass struct in lambdas. std::shared_ptr copying is thread-safe
+ struct TInnerState {
+ TInnerState(size_t inputsCount) : IsInputDone(inputsCount) {};
+ std::mutex Lock;
+ std::queue<TResult> Results;
+ std::vector<bool> IsInputDone;
+ NYT::TError Error{};
+ size_t CurrentInputIdx = 0;
+ size_t CurrentInflight = 0;
+ NYT::TPromise<void> WaitPromise = NYT::NewPromise<void>();
+ };
+ std::shared_ptr<TInnerState> InnerState_;
+ std::vector<TReaderState> StateByReader_;
+ NYT::TRawTableReaderPtr CurrentReader_ = nullptr;
+ bool Eof_ = false;
+ const TMkqlIOSpecs* Spec_;
+ const NKikimr::NMiniKQL::THolderFactory& HolderFactory_;
+ TVector<NYT::NConcurrency::IAsyncZeroCopyInputStreamPtr> RawInputs_;
+ const size_t BlockSize_;
+ TMkqlReaderImpl MkqlReader_;
+ size_t CurrentInput_ = 0;
+ size_t CurrentRecord_ = 1;
+ size_t Inflight_ = 1;
+ bool Valid_ = true;
+ std::unique_ptr<TSettingsHolder> Settings_;
+ NUdf::TUnboxedValue CurrentValue_;
+ std::function<void()> OnNextBlockCallback_;
+ NKikimr::NMiniKQL::TSamplingStatTimer TimerAwaiting_;
+
+};
+
+
+class TDqYtReadWrapperRPC : public TDqYtReadWrapperBase<TDqYtReadWrapperRPC, TParallelFileInputState> {
+public:
+using TInputType = NYT::NConcurrency::IAsyncZeroCopyInputStreamPtr;
+ TDqYtReadWrapperRPC(const TComputationNodeFactoryContext& ctx, const TString& clusterName,
+ const TString& token, const NYT::TNode& inputSpec, const NYT::TNode& samplingSpec,
+ const TVector<ui32>& inputGroups,
+ TType* itemType, const TVector<TString>& tableNames, TVector<std::pair<NYT::TRichYPath, NYT::TFormat>>&& tables, NKikimr::NMiniKQL::IStatsRegistry* jobStats, size_t inflight, size_t timeout)
+ : TDqYtReadWrapperBase<TDqYtReadWrapperRPC, TParallelFileInputState>(ctx, clusterName, token, inputSpec, samplingSpec, inputGroups, itemType, tableNames, std::move(tables), jobStats, inflight, timeout) {}
+
+ void MakeState(TComputationContext& ctx, NUdf::TUnboxedValue& state) const;
+};
+}
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp
new file mode 100644
index 0000000000..a53f7a3537
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.cpp
@@ -0,0 +1,282 @@
+#include "dq_yt_writer.h"
+
+#include <library/cpp/yson/node/node_io.h>
+#include <util/generic/size_literals.h>
+#include <yt/cpp/mapreduce/interface/client.h>
+#include <yt/cpp/mapreduce/interface/common.h>
+#include <ydb/library/yql/providers/yt/codec/yt_codec_io.h>
+#include <ydb/library/yql/minikql/computation/mkql_computation_node_codegen.h>
+#include <ydb/library/yql/minikql/mkql_node_cast.h>
+#include <ydb/library/yql/providers/yt/common/yql_names.h>
+
+#include <utility>
+
+namespace NYql::NDqs {
+namespace {
+
+using namespace NKikimr::NMiniKQL;
+using namespace NYT;
+
+class TYtDqWideWriteWrapper final : public TStatefulFlowCodegeneratorNode<TYtDqWideWriteWrapper> {
+ using TBaseComputation = TStatefulFlowCodegeneratorNode<TYtDqWideWriteWrapper>;
+
+ class TWriterState : public TComputationValue<TWriterState> {
+ public:
+ TWriterState(
+ TMemoryUsageInfo* memInfo,
+ IClientPtr&& client,
+ ITransactionPtr&& transaction,
+ THolder<TMkqlIOSpecs>&& specs,
+ TRawTableWriterPtr&& outStream,
+ THolder<TMkqlWriterImpl>&& writer
+ )
+ : TComputationValue<TWriterState>(memInfo)
+ , Client(std::move(client)), Transaction(std::move(transaction))
+ , Specs(std::move(specs)), OutStream(std::move(outStream)), Writer(std::move(writer))
+ {}
+
+ ~TWriterState() override {
+ try {
+ Finish();
+ } catch (...) {
+ }
+ }
+
+ void AddRow(const NUdf::TUnboxedValuePod* row) const {
+ Writer->AddFlatRow(row);
+ }
+
+ void Finish() {
+ if (!Finished) {
+ Writer->Finish();
+ OutStream->Finish();
+ }
+ Finished = true;
+ }
+ private:
+ bool Finished = false;
+ const IClientPtr Client;
+ const ITransactionPtr Transaction;
+ const THolder<TMkqlIOSpecs> Specs;
+ const TRawTableWriterPtr OutStream;
+ const THolder<TMkqlWriterImpl> Writer;
+ };
+
+public:
+ TYtDqWideWriteWrapper(TComputationMutables& mutables,
+ IComputationWideFlowNode* flow,
+ std::vector<EValueRepresentation>&& representations,
+ const std::string_view& clusterName,
+ const std::string_view& token,
+ const TRichYPath& path,
+ const NYT::TNode& outSpec,
+ const NYT::TNode& writerOptions,
+ THolder<NCommon::TCodecContext>&& codecCtx
+ )
+ : TBaseComputation(mutables, flow, EValueRepresentation::Embedded, EValueRepresentation::Boxed)
+ , Flow(flow)
+ , Representations(std::move(representations))
+ , ClusterName(clusterName)
+ , Token(token)
+ , Path(path)
+ , OutSpec(outSpec)
+ , WriterOptions(writerOptions)
+ , CodecCtx(std::move(codecCtx))
+ , Values(Representations.size()), Fields(GetPointers(Values))
+ {}
+
+ NUdf::TUnboxedValuePod DoCalculate(NUdf::TUnboxedValue& state, TComputationContext& ctx) const {
+ if (state.IsFinish()) {
+ return NUdf::TUnboxedValuePod::MakeFinish();
+ } else if (state.IsInvalid())
+ MakeState(ctx, state);
+
+ switch (const auto ptr = static_cast<TWriterState*>(state.AsBoxed().Get()); Flow->FetchValues(ctx, Fields.data())) {
+ case EFetchResult::One:
+ ptr->AddRow(Values.data());
+ return NUdf::TUnboxedValuePod::Void();
+ case EFetchResult::Yield:
+ return NUdf::TUnboxedValuePod::MakeYield();
+ case EFetchResult::Finish:
+ ptr->Finish();
+ state = NUdf::TUnboxedValuePod::MakeFinish();
+ return NUdf::TUnboxedValuePod::MakeFinish();
+ }
+ }
+#ifndef MKQL_DISABLE_CODEGEN
+ Value* DoGenerateGetValue(const TCodegenContext& ctx, Value* statePtr, BasicBlock*& block) const {
+ auto& context = ctx.Codegen.GetContext();
+
+ const auto valueType = Type::getInt128Ty(context);
+ const auto indexType = Type::getInt32Ty(context);
+ const auto structPtrType = PointerType::getUnqual(StructType::get(context));
+ const auto arrayType = ArrayType::get(valueType, Representations.size());
+ const auto values = new AllocaInst(arrayType, 0U, "values", &ctx.Func->getEntryBlock().back());
+
+ const auto init = BasicBlock::Create(context, "init", ctx.Func);
+ const auto next = BasicBlock::Create(context, "next", ctx.Func);
+ const auto work = BasicBlock::Create(context, "work", ctx.Func);
+ const auto done = BasicBlock::Create(context, "done", ctx.Func);
+ const auto exit = BasicBlock::Create(context, "exit", ctx.Func);
+
+ const auto output = PHINode::Create(valueType, 4U, "output", exit);
+ output->addIncoming(GetFinish(context), block);
+
+ const auto check = new LoadInst(valueType, statePtr, "check", block);
+ const auto choise = SwitchInst::Create(check, next, 2U, block);
+ choise->addCase(GetInvalid(context), init);
+ choise->addCase(GetFinish(context), exit);
+
+ block = init;
+
+ const auto ptrType = PointerType::getUnqual(StructType::get(context));
+ const auto self = CastInst::Create(Instruction::IntToPtr, ConstantInt::get(Type::getInt64Ty(context), uintptr_t(this)), ptrType, "self", block);
+ const auto makeFunc = ConstantInt::get(Type::getInt64Ty(context), GetMethodPtr(&TYtDqWideWriteWrapper::MakeState));
+ const auto makeType = FunctionType::get(Type::getVoidTy(context), {self->getType(), ctx.Ctx->getType(), statePtr->getType()}, false);
+ const auto makeFuncPtr = CastInst::Create(Instruction::IntToPtr, makeFunc, PointerType::getUnqual(makeType), "function", block);
+ CallInst::Create(makeType, makeFuncPtr, {self, ctx.Ctx, statePtr}, "", block);
+
+ BranchInst::Create(next, block);
+
+ block = next;
+
+ const auto result = GetNodeValues(Flow, ctx, block);
+
+ output->addIncoming(GetYield(context), block);
+
+ const auto way = SwitchInst::Create(result.first, work, 2U, block);
+ way->addCase(ConstantInt::get(indexType, static_cast<i32>(EFetchResult::Yield)), exit);
+ way->addCase(ConstantInt::get(indexType, static_cast<i32>(EFetchResult::Finish)), done);
+
+ {
+ block = work;
+
+ TSmallVec<Value*> fields;
+ fields.reserve(Representations.size());
+ for (ui32 i = 0U; i < Representations.size(); ++i) {
+ const auto pointer = GetElementPtrInst::CreateInBounds(arrayType, values, {ConstantInt::get(indexType, 0), ConstantInt::get(indexType, i)}, (TString("ptr_") += ToString(i)).c_str(), block);
+ fields.emplace_back(result.second[i](ctx, block));
+ new StoreInst(fields.back(), pointer, block);
+ }
+
+ const auto state = new LoadInst(valueType, statePtr, "state", block);
+ const auto half = CastInst::Create(Instruction::Trunc, state, Type::getInt64Ty(context), "half", block);
+ const auto stateArg = CastInst::Create(Instruction::IntToPtr, half, structPtrType, "state_arg", block);
+
+ const auto addFunc = ConstantInt::get(Type::getInt64Ty(context), GetMethodPtr(&TWriterState::AddRow));
+ const auto addType = FunctionType::get(Type::getVoidTy(context), {stateArg->getType(), values->getType()}, false);
+ const auto addPtr = CastInst::Create(Instruction::IntToPtr, addFunc, PointerType::getUnqual(addType), "write", block);
+ CallInst::Create(addType, addPtr, {stateArg, values}, "", block);
+
+ for (ui32 i = 0U; i < Representations.size(); ++i) {
+ ValueCleanup(Representations[i], fields[i], ctx, block);
+ }
+
+ output->addIncoming(GetFalse(context), block);
+ BranchInst::Create(exit, block);
+ }
+
+ {
+ block = done;
+
+ const auto state = new LoadInst(valueType, statePtr, "state", block);
+ const auto half = CastInst::Create(Instruction::Trunc, state, Type::getInt64Ty(context), "half", block);
+ const auto stateArg = CastInst::Create(Instruction::IntToPtr, half, structPtrType, "state_arg", block);
+
+ const auto finishFunc = ConstantInt::get(Type::getInt64Ty(context), GetMethodPtr(&TWriterState::Finish));
+ const auto finishType = FunctionType::get(Type::getVoidTy(context), {stateArg->getType()}, false);
+ const auto finishPtr = CastInst::Create(Instruction::IntToPtr, finishFunc, PointerType::getUnqual(finishType), "finish", block);
+ CallInst::Create(finishType, finishPtr, {stateArg}, "", block);
+
+ UnRefBoxed(statePtr, ctx, block);
+ new StoreInst(GetFinish(context), statePtr, block);
+
+ output->addIncoming(GetFinish(context), block);
+ BranchInst::Create(exit, block);
+ }
+
+ block = exit;
+ return output;
+ }
+#endif
+private:
+ void MakeState(TComputationContext& ctx, NUdf::TUnboxedValue& state) const {
+ TString token;
+ if (NUdf::TStringRef tokenRef(Token); ctx.Builder->GetSecureParam(tokenRef, tokenRef)) {
+ token = tokenRef;
+ }
+
+ NYT::TCreateClientOptions createOpts;
+ if (token) {
+ createOpts.Token(token);
+ }
+
+ auto client = NYT::CreateClient(ClusterName, createOpts);
+ auto transaction = client->AttachTransaction(Path.TransactionId_.GetRef());
+ auto specs = MakeHolder<TMkqlIOSpecs>();
+ specs->SetUseSkiff("");
+ specs->Init(*CodecCtx, OutSpec);
+ auto path = Path;
+ path.TransactionId_.Clear();
+ NYT::TTableWriterOptions options;
+ options.Config(WriterOptions);
+ auto outStream = transaction->CreateRawWriter(path, specs->MakeOutputFormat(), options);
+
+ auto writer = MakeHolder<TMkqlWriterImpl>(outStream, 4_MB);
+ writer->SetSpecs(*specs);
+
+ state = ctx.HolderFactory.Create<TWriterState>(std::move(client), std::move(transaction), std::move(specs), std::move(outStream), std::move(writer));
+ }
+
+ void RegisterDependencies() const final {
+ FlowDependsOn(Flow);
+ }
+
+ static std::vector<NUdf::TUnboxedValue*> GetPointers(std::vector<NUdf::TUnboxedValue>& array) {
+ std::vector<NUdf::TUnboxedValue*> pointers;
+ pointers.reserve(array.size());
+ std::transform(array.begin(), array.end(), std::back_inserter(pointers), [](NUdf::TUnboxedValue& v) { return std::addressof(v); });
+ return pointers;
+ }
+
+ IComputationWideFlowNode* const Flow;
+ const std::vector<EValueRepresentation> Representations;
+
+ const TString ClusterName;
+ const TString Token;
+ const TRichYPath Path;
+ const NYT::TNode OutSpec;
+ const NYT::TNode WriterOptions;
+ const THolder<NCommon::TCodecContext> CodecCtx;
+
+ std::vector<NUdf::TUnboxedValue> Values;
+ const std::vector<NUdf::TUnboxedValue*> Fields;
+};
+
+}
+
+IComputationNode* WrapYtDqRowsWideWrite(TCallable& callable, const TComputationNodeFactoryContext& ctx) {
+ YQL_ENSURE(callable.GetInputsCount() == 6, "Expected six args.");
+
+ const auto& clusterName = AS_VALUE(TDataLiteral, callable.GetInput(1))->AsValue().AsStringRef();
+ const auto& token = AS_VALUE(TDataLiteral, callable.GetInput(2))->AsValue().AsStringRef();
+
+ TRichYPath richYPath;
+ Deserialize(richYPath, NYT::NodeFromYsonString(TString(AS_VALUE(TDataLiteral, callable.GetInput(3))->AsValue().AsStringRef())));
+
+ const NYT::TNode outSpec(NYT::NodeFromYsonString((AS_VALUE(TDataLiteral, callable.GetInput(4))->AsValue().AsStringRef())));
+ const NYT::TNode writerOptions(NYT::NodeFromYsonString((AS_VALUE(TDataLiteral, callable.GetInput(5))->AsValue().AsStringRef())));
+ const auto node = LocateNode(ctx.NodeLocator, callable, 0);
+
+ std::vector<EValueRepresentation> representations;
+ auto wideComponents = GetWideComponents(AS_TYPE(TFlowType, callable.GetInput(0).GetStaticType()));
+ representations.reserve(wideComponents.size());
+ for (ui32 i = 0U; i < wideComponents.size(); ++i) {
+ representations.emplace_back(GetValueRepresentation(wideComponents[i]));
+ }
+
+ auto codecCtx = MakeHolder<NCommon::TCodecContext>(ctx.Env, ctx.FunctionRegistry, &ctx.HolderFactory);
+ return new TYtDqWideWriteWrapper(ctx.Mutables, static_cast<IComputationWideFlowNode*>(node), std::move(representations), clusterName, token, richYPath, outSpec, writerOptions, std::move(codecCtx));
+}
+
+} // NYql
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.h b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.h
new file mode 100644
index 0000000000..de14269bef
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/dq_yt_writer.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <ydb/library/yql/minikql/computation/mkql_computation_node.h>
+#include <ydb/library/yql/minikql/mkql_node.h>
+
+namespace NYql::NDqs {
+
+NKikimr::NMiniKQL::IComputationNode* WrapYtDqRowsWideWrite(NKikimr::NMiniKQL::TCallable& callable, const NKikimr::NMiniKQL::TComputationNodeFactoryContext& ctx);
+
+} // NYql
diff --git a/ydb/library/yql/providers/yt/comp_nodes/dq/ya.make b/ydb/library/yql/providers/yt/comp_nodes/dq/ya.make
new file mode 100644
index 0000000000..9f94d73e3d
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/dq/ya.make
@@ -0,0 +1,35 @@
+LIBRARY()
+
+PEERDIR(
+ ydb/library/yql/minikql/computation/llvm
+ ydb/library/yql/providers/yt/comp_nodes
+ ydb/library/yql/providers/yt/codec
+ ydb/library/yql/providers/common/codec
+ yt/cpp/mapreduce/interface
+ yt/cpp/mapreduce/common
+ library/cpp/yson/node
+ yt/yt/core
+)
+
+IF(LINUX)
+ PEERDIR(
+ yt/yt/client
+ yt/yt/client/arrow
+ )
+
+ SRCS(
+ dq_yt_rpc_reader.cpp
+ )
+ENDIF()
+
+SRCS(
+ dq_yt_reader.cpp
+ dq_yt_factory.cpp
+ dq_yt_writer.cpp
+)
+
+
+YQL_LAST_ABI_VERSION()
+
+
+END()
diff --git a/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..6f89ba02b8
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,82 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-comp_nodes-ut)
+target_compile_options(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes
+)
+target_link_libraries(ydb-library-yql-providers-yt-comp_nodes-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-comp_nodes
+ library-cpp-random_provider
+ library-cpp-time_provider
+ minikql-comp_nodes-llvm
+ udf-service-exception_policy
+ yql-sql-pg_dummy
+)
+target_link_options(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ -Wl,-platform_version,macos,11.0,11.0
+ -fPIC
+ -fPIC
+ -framework
+ CoreFoundation
+)
+target_sources(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/ut/yql_mkql_output_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 10
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ LABELS
+ MEDIUM
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ TIMEOUT
+ 600
+)
+target_allocator(ydb-library-yql-providers-yt-comp_nodes-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-comp_nodes-ut)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..81e6d39e76
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,85 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-comp_nodes-ut)
+target_compile_options(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes
+)
+target_link_libraries(ydb-library-yql-providers-yt-comp_nodes-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-testing-unittest_main
+ providers-yt-comp_nodes
+ library-cpp-random_provider
+ library-cpp-time_provider
+ minikql-comp_nodes-llvm
+ udf-service-exception_policy
+ yql-sql-pg_dummy
+)
+target_link_options(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+)
+target_sources(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/ut/yql_mkql_output_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 10
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ LABELS
+ MEDIUM
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ TIMEOUT
+ 600
+)
+target_allocator(ydb-library-yql-providers-yt-comp_nodes-ut
+ cpp-malloc-jemalloc
+)
+vcs_info(ydb-library-yql-providers-yt-comp_nodes-ut)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..d8ee7ec596
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,87 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-comp_nodes-ut)
+target_compile_options(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes
+)
+target_link_libraries(ydb-library-yql-providers-yt-comp_nodes-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-comp_nodes
+ library-cpp-random_provider
+ library-cpp-time_provider
+ minikql-comp_nodes-llvm
+ udf-service-exception_policy
+ yql-sql-pg_dummy
+)
+target_link_options(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+)
+target_sources(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/ut/yql_mkql_output_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 10
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ LABELS
+ MEDIUM
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ TIMEOUT
+ 600
+)
+target_allocator(ydb-library-yql-providers-yt-comp_nodes-ut
+ cpp-malloc-tcmalloc
+ libs-tcmalloc-no_percpu_cache
+)
+vcs_info(ydb-library-yql-providers-yt-comp_nodes-ut)
diff --git a/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.txt b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..38487de31a
--- /dev/null
+++ b/ydb/library/yql/providers/yt/comp_nodes/ut/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,75 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-comp_nodes-ut)
+target_compile_options(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes
+)
+target_link_libraries(ydb-library-yql-providers-yt-comp_nodes-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-comp_nodes
+ library-cpp-random_provider
+ library-cpp-time_provider
+ minikql-comp_nodes-llvm
+ udf-service-exception_policy
+ yql-sql-pg_dummy
+)
+target_sources(ydb-library-yql-providers-yt-comp_nodes-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/comp_nodes/ut/yql_mkql_output_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 10
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ LABELS
+ MEDIUM
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-comp_nodes-ut
+ PROPERTY
+ TIMEOUT
+ 600
+)
+target_allocator(ydb-library-yql-providers-yt-comp_nodes-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-comp_nodes-ut)
diff --git a/ydb/library/yql/providers/yt/gateway/CMakeLists.txt b/ydb/library/yql/providers/yt/gateway/CMakeLists.txt
index 39d0a63866..cdd2098ca3 100644
--- a/ydb/library/yql/providers/yt/gateway/CMakeLists.txt
+++ b/ydb/library/yql/providers/yt/gateway/CMakeLists.txt
@@ -9,3 +9,4 @@
add_subdirectory(file)
add_subdirectory(lib)
add_subdirectory(native)
+add_subdirectory(profile)
diff --git a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.darwin-x86_64.txt
index 92bdf4cee5..f8b5242d56 100644
--- a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.darwin-x86_64.txt
+++ b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.darwin-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
add_library(yt-gateway-native)
target_compile_options(yt-gateway-native PRIVATE
diff --git a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-aarch64.txt
index f377da4581..06abdf6b88 100644
--- a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-aarch64.txt
+++ b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-aarch64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
add_library(yt-gateway-native)
target_compile_options(yt-gateway-native PRIVATE
diff --git a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-x86_64.txt
index f377da4581..06abdf6b88 100644
--- a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-x86_64.txt
+++ b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.linux-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
add_library(yt-gateway-native)
target_compile_options(yt-gateway-native PRIVATE
diff --git a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.windows-x86_64.txt
index 92bdf4cee5..f8b5242d56 100644
--- a/ydb/library/yql/providers/yt/gateway/native/CMakeLists.windows-x86_64.txt
+++ b/ydb/library/yql/providers/yt/gateway/native/CMakeLists.windows-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
add_library(yt-gateway-native)
target_compile_options(yt-gateway-native PRIVATE
diff --git a/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..b00ec7398c
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,61 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-gateway-native-ut)
+target_compile_options(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(ydb-library-yql-providers-yt-gateway-native-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-gtest
+ cpp-testing-gtest_main
+ yt-gateway-native
+ yt-gateway-file
+ yql-core-ut_common
+ cpp-testing-mock_server
+ cpp-testing-common
+ udf-service-terminate_policy
+ yql-sql-pg
+)
+target_link_options(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ -Wl,-platform_version,macos,11.0,11.0
+ -fPIC
+ -fPIC
+ -framework
+ CoreFoundation
+)
+target_sources(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/native/ut/yql_yt_native_folders_ut.cpp
+)
+add_test(
+ NAME
+ ydb-library-yql-providers-yt-gateway-native-ut
+ COMMAND
+ ydb-library-yql-providers-yt-gateway-native-ut
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-gateway-native-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-gateway-native-ut)
diff --git a/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..b209ede94d
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,65 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-gateway-native-ut)
+target_compile_options(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(ydb-library-yql-providers-yt-gateway-native-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-testing-gtest
+ cpp-testing-gtest_main
+ yt-gateway-native
+ yt-gateway-file
+ yql-core-ut_common
+ cpp-testing-mock_server
+ cpp-testing-common
+ udf-service-terminate_policy
+ yql-sql-pg
+)
+target_link_options(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+ -lutil
+)
+target_sources(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/native/ut/yql_yt_native_folders_ut.cpp
+)
+add_test(
+ NAME
+ ydb-library-yql-providers-yt-gateway-native-ut
+ COMMAND
+ ydb-library-yql-providers-yt-gateway-native-ut
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-gateway-native-ut
+ cpp-malloc-jemalloc
+)
+vcs_info(ydb-library-yql-providers-yt-gateway-native-ut)
diff --git a/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..94d201649c
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,67 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-gateway-native-ut)
+target_compile_options(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(ydb-library-yql-providers-yt-gateway-native-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-gtest
+ cpp-testing-gtest_main
+ yt-gateway-native
+ yt-gateway-file
+ yql-core-ut_common
+ cpp-testing-mock_server
+ cpp-testing-common
+ udf-service-terminate_policy
+ yql-sql-pg
+)
+target_link_options(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+ -lutil
+)
+target_sources(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/native/ut/yql_yt_native_folders_ut.cpp
+)
+add_test(
+ NAME
+ ydb-library-yql-providers-yt-gateway-native-ut
+ COMMAND
+ ydb-library-yql-providers-yt-gateway-native-ut
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-gateway-native-ut
+ cpp-malloc-tcmalloc
+ libs-tcmalloc-no_percpu_cache
+)
+vcs_info(ydb-library-yql-providers-yt-gateway-native-ut)
diff --git a/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.txt b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..b6ba65dd2a
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/native/ut/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,54 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-gateway-native-ut)
+target_compile_options(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(ydb-library-yql-providers-yt-gateway-native-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-gtest
+ cpp-testing-gtest_main
+ yt-gateway-native
+ yt-gateway-file
+ yql-core-ut_common
+ cpp-testing-mock_server
+ cpp-testing-common
+ udf-service-terminate_policy
+ yql-sql-pg
+)
+target_sources(ydb-library-yql-providers-yt-gateway-native-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/native/ut/yql_yt_native_folders_ut.cpp
+)
+add_test(
+ NAME
+ ydb-library-yql-providers-yt-gateway-native-ut
+ COMMAND
+ ydb-library-yql-providers-yt-gateway-native-ut
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_property(
+ TEST
+ ydb-library-yql-providers-yt-gateway-native-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-gateway-native-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-gateway-native-ut)
diff --git a/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..ec6d710928
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-gateway-profile)
+target_compile_options(yt-gateway-profile PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-gateway-profile PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ yql-utils-log
+ providers-yt-provider
+)
+target_sources(yt-gateway-profile PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp
+)
diff --git a/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..343e6f19b7
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,23 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-gateway-profile)
+target_compile_options(yt-gateway-profile PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-gateway-profile PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yql-utils-log
+ providers-yt-provider
+)
+target_sources(yt-gateway-profile PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp
+)
diff --git a/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..343e6f19b7
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,23 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-gateway-profile)
+target_compile_options(yt-gateway-profile PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-gateway-profile PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yql-utils-log
+ providers-yt-provider
+)
+target_sources(yt-gateway-profile PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp
+)
diff --git a/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.txt b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..ec6d710928
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-gateway-profile)
+target_compile_options(yt-gateway-profile PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(yt-gateway-profile PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ yql-utils-log
+ providers-yt-provider
+)
+target_sources(yt-gateway-profile PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp
+)
diff --git a/ydb/library/yql/providers/yt/gateway/profile/ya.make b/ydb/library/yql/providers/yt/gateway/profile/ya.make
new file mode 100644
index 0000000000..3132b176fa
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/ya.make
@@ -0,0 +1,14 @@
+LIBRARY()
+
+SRCS(
+ yql_yt_profiling.cpp
+)
+
+PEERDIR(
+ ydb/library/yql/utils/log
+ ydb/library/yql/providers/yt/provider
+)
+
+YQL_LAST_ABI_VERSION()
+
+END()
diff --git a/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp b/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp
new file mode 100644
index 0000000000..07b3bc31b1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.cpp
@@ -0,0 +1,184 @@
+#include "yql_yt_profiling.h"
+
+#include <ydb/library/yql/utils/log/profile.h>
+
+#include <util/generic/ptr.h>
+
+using namespace NThreading;
+
+namespace NYql {
+namespace {
+
+class TProfilingYtGateway final: public IYtGateway {
+public:
+ TProfilingYtGateway(IYtGateway::TPtr&& slave)
+ : Slave_(std::move(slave))
+ {
+ }
+
+ void OpenSession(TOpenSessionOptions&& options) final {
+ YQL_PROFILE_FUNC(TRACE);
+ Slave_->OpenSession(std::move(options));
+ }
+
+ void CloseSession(TCloseSessionOptions&& options) final {
+ YQL_PROFILE_FUNC(TRACE);
+ Slave_->CloseSession(std::move(options));
+ }
+
+ void CleanupSession(TCleanupSessionOptions&& options) final {
+ YQL_PROFILE_FUNC(TRACE);
+ Slave_->CleanupSession(std::move(options));
+ }
+
+ TFuture<TFinalizeResult> Finalize(TFinalizeOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->Finalize(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TCanonizePathsResult> CanonizePaths(TCanonizePathsOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->CanonizePaths(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TTableInfoResult> GetTableInfo(TGetTableInfoOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->GetTableInfo(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TTableRangeResult> GetTableRange(TTableRangeOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->GetTableRange(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TFolderResult> GetFolder(TFolderOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->GetFolder(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TBatchFolderResult> ResolveLinks(TResolveOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->ResolveLinks(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TBatchFolderResult> GetFolders(TBatchFolderOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->GetFolders(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TResOrPullResult> ResOrPull(const TExprNode::TPtr& node, TExprContext& ctx, TResOrPullOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->ResOrPull(node, ctx, std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TRunResult> Run(const TExprNode::TPtr& node, TExprContext& ctx, TRunOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->Run(node, ctx, std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TRunResult> Prepare(const TExprNode::TPtr& node, TExprContext& ctx, TPrepareOptions&& options) const final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->Prepare(node, ctx, std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TCalcResult> Calc(const TExprNode::TListType& nodes, TExprContext& ctx, TCalcOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->Calc(nodes, ctx, std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TPublishResult> Publish(const TExprNode::TPtr& node, TExprContext& ctx, TPublishOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->Publish(node, ctx, std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TCommitResult> Commit(TCommitOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->Commit(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TDropTrackablesResult> DropTrackables(TDropTrackablesOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->DropTrackables(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TFuture<TPathStatResult> PathStat(TPathStatOptions&& options) final {
+ auto profileScope = YQL_PROFILE_FUNC_VAL(TRACE);
+ auto future = Slave_->PathStat(std::move(options));
+ return YQL_PROFILE_BIND_VAL(future, profileScope);
+ }
+
+ TPathStatResult TryPathStat(TPathStatOptions&& options) final {
+ YQL_PROFILE_FUNC(TRACE);
+ return Slave_->TryPathStat(std::move(options));
+ }
+
+ bool TryParseYtUrl(const TString& url, TString* cluster, TString* path) const final {
+ YQL_PROFILE_FUNC(TRACE);
+ return Slave_->TryParseYtUrl(url, cluster, path);
+ }
+
+ TString GetDefaultClusterName() const final {
+ YQL_PROFILE_FUNC(TRACE);
+ return Slave_->GetDefaultClusterName();
+ }
+
+ TString GetClusterServer(const TString& cluster) const final {
+ return Slave_->GetClusterServer(cluster);
+ }
+
+ NYT::TRichYPath GetRealTable(const TString& sessionId, const TString& cluster, const TString& table, ui32 epoch, const TString& tmpFolder) const final {
+ return Slave_->GetRealTable(sessionId, cluster, table, epoch, tmpFolder);
+ }
+
+ NYT::TRichYPath GetWriteTable(const TString& sessionId, const TString& cluster, const TString& table, const TString& tmpFolder) const final {
+ return Slave_->GetWriteTable(sessionId, cluster, table, tmpFolder);
+ }
+
+ NThreading::TFuture<TRunResult> GetTableStat(const TExprNode::TPtr& node, TExprContext& ctx, TPrepareOptions&& options) final {
+ return Slave_->GetTableStat(node, ctx, std::move(options));
+ }
+
+ TFullResultTableResult PrepareFullResultTable(TFullResultTableOptions&& options) final {
+ YQL_PROFILE_FUNC(TRACE);
+ return Slave_->PrepareFullResultTable(std::move(options));
+ }
+
+ void SetStatUploader(IStatUploader::TPtr statUploader) final {
+ YQL_PROFILE_FUNC(TRACE);
+ Slave_->SetStatUploader(statUploader);
+ }
+
+ void RegisterMkqlCompiler(NCommon::TMkqlCallableCompilerBase& compiler) final {
+ Y_UNUSED(compiler);
+ }
+
+ TGetTablePartitionsResult GetTablePartitions(TGetTablePartitionsOptions&& options) override {
+ YQL_PROFILE_FUNC(TRACE);
+ return Slave_->GetTablePartitions(std::move(options));
+ }
+
+private:
+ IYtGateway::TPtr Slave_;
+};
+
+} // namespace
+
+IYtGateway::TPtr CreateYtProfilingGateway(IYtGateway::TPtr slave) {
+ return MakeIntrusive<TProfilingYtGateway>(std::move(slave));
+}
+
+} // namspace NYql
diff --git a/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.h b/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.h
new file mode 100644
index 0000000000..7058295499
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/profile/yql_yt_profiling.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <ydb/library/yql/providers/yt/provider/yql_yt_gateway.h>
+
+namespace NYql {
+
+IYtGateway::TPtr CreateYtProfilingGateway(IYtGateway::TPtr slave);
+
+} // namspace NYql
diff --git a/ydb/library/yql/providers/yt/gateway/ya.make b/ydb/library/yql/providers/yt/gateway/ya.make
new file mode 100644
index 0000000000..053aebfabe
--- /dev/null
+++ b/ydb/library/yql/providers/yt/gateway/ya.make
@@ -0,0 +1,6 @@
+RECURSE(
+ file
+ lib
+ native
+ profile
+)
diff --git a/ydb/library/yql/providers/yt/lib/ya.make b/ydb/library/yql/providers/yt/lib/ya.make
new file mode 100644
index 0000000000..9fb3a47e1c
--- /dev/null
+++ b/ydb/library/yql/providers/yt/lib/ya.make
@@ -0,0 +1,19 @@
+RECURSE(
+ expr_traits
+ graph_reorder
+ hash
+ infer_schema
+ init_yt_api
+ key_filter
+ lambda_builder
+ log
+ mkql_helpers
+ res_pull
+ row_spec
+ schema
+ skiff
+ url_mapper
+ yson_helpers
+ yt_url_lister
+ yt_download
+)
diff --git a/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..2ffa214eb3
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,26 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(providers-yt-mkql_dq)
+target_compile_options(providers-yt-mkql_dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(providers-yt-mkql_dq PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yson-node
+ cpp-mapreduce-common
+ cpp-mapreduce-interface
+ library-yql-minikql
+ yql-public-udf
+ library-yql-utils
+)
+target_sources(providers-yt-mkql_dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp
+)
diff --git a/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..d8c0d3c84f
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,27 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(providers-yt-mkql_dq)
+target_compile_options(providers-yt-mkql_dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(providers-yt-mkql_dq PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yson-node
+ cpp-mapreduce-common
+ cpp-mapreduce-interface
+ library-yql-minikql
+ yql-public-udf
+ library-yql-utils
+)
+target_sources(providers-yt-mkql_dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp
+)
diff --git a/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..d8c0d3c84f
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,27 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(providers-yt-mkql_dq)
+target_compile_options(providers-yt-mkql_dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(providers-yt-mkql_dq PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yson-node
+ cpp-mapreduce-common
+ cpp-mapreduce-interface
+ library-yql-minikql
+ yql-public-udf
+ library-yql-utils
+)
+target_sources(providers-yt-mkql_dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp
+)
diff --git a/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.txt b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..2ffa214eb3
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,26 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(providers-yt-mkql_dq)
+target_compile_options(providers-yt-mkql_dq PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_link_libraries(providers-yt-mkql_dq PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yson-node
+ cpp-mapreduce-common
+ cpp-mapreduce-interface
+ library-yql-minikql
+ yql-public-udf
+ library-yql-utils
+)
+target_sources(providers-yt-mkql_dq PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp
+)
diff --git a/ydb/library/yql/providers/yt/mkql_dq/ya.make b/ydb/library/yql/providers/yt/mkql_dq/ya.make
new file mode 100644
index 0000000000..97c92a7b6f
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/ya.make
@@ -0,0 +1,18 @@
+LIBRARY()
+
+SRCS(
+ yql_yt_dq_transform.cpp
+)
+
+PEERDIR(
+ library/cpp/yson/node
+ yt/cpp/mapreduce/common
+ yt/cpp/mapreduce/interface
+ ydb/library/yql/minikql
+ ydb/library/yql/public/udf
+ ydb/library/yql/utils
+)
+
+YQL_LAST_ABI_VERSION()
+
+END()
diff --git a/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp b/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp
new file mode 100644
index 0000000000..41d652354c
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.cpp
@@ -0,0 +1,131 @@
+#include "yql_yt_dq_transform.h"
+
+#include <ydb/library/yql/minikql/mkql_program_builder.h>
+#include <ydb/library/yql/minikql/mkql_node_cast.h>
+#include <ydb/library/yql/minikql/mkql_function_registry.h>
+#include <ydb/library/yql/minikql/mkql_node_printer.h>
+#include <ydb/library/yql/minikql/defs.h>
+#include <ydb/library/yql/utils/yql_panic.h>
+#include <ydb/library/yql/utils/log/log.h>
+
+#include <yt/cpp/mapreduce/interface/serialize.h>
+#include <yt/cpp/mapreduce/common/helpers.h>
+
+#include <library/cpp/yson/node/serialize.h>
+#include <library/cpp/yson/node/node_io.h>
+
+#include <util/generic/vector.h>
+#include <util/generic/guid.h>
+
+
+namespace NYql {
+
+using namespace NKikimr;
+
+class TYtDqTaskTransform {
+public:
+ TYtDqTaskTransform(THashMap<TString, TString> taskParams, const NMiniKQL::IFunctionRegistry& functionRegistry)
+ : TaskParams(std::move(taskParams))
+ , FunctionRegistry(functionRegistry)
+ {
+ }
+
+ NMiniKQL::TCallableVisitFunc operator()(NMiniKQL::TInternName name) {
+ if (TaskParams.contains("yt") && name == "DqYtRead") {
+ return [this](NMiniKQL::TCallable& callable, const NMiniKQL::TTypeEnvironment& env) {
+ using namespace NMiniKQL;
+
+ TProgramBuilder pgmBuilder(env, FunctionRegistry);
+
+ YQL_ENSURE(callable.GetInputsCount() == 8 || callable.GetInputsCount() == 9, "Expected 8 or 9 arguments.");
+
+ TCallableBuilder callableBuilder(env, callable.GetType()->GetName(), callable.GetType()->GetReturnType(), false);
+ callableBuilder.Add(callable.GetInput(0));
+ callableBuilder.Add(callable.GetInput(1));
+ callableBuilder.Add(callable.GetInput(2));
+ callableBuilder.Add(callable.GetInput(3));
+
+ if (callable.GetInputsCount() == 8U)
+ callableBuilder.Add(callable.GetInput(4));
+ else {
+ auto params = NYT::NodeFromYsonString(TaskParams.Value("yt", TString())).AsMap();
+
+ TVector<TRuntimeNode> newGrpList;
+ TListLiteral* groupList = AS_VALUE(TListLiteral, callable.GetInput(4));
+ for (ui32 grp = 0; grp < groupList->GetItemsCount(); ++grp) {
+ TListLiteral* tableList = AS_VALUE(TListLiteral, groupList->GetItems()[grp]);
+ TVector<TRuntimeNode> newTableList;
+ for (ui32 i = 0; i < tableList->GetItemsCount(); ++i) {
+ TString paramsKey = TStringBuilder() << grp << "/" << i;
+
+ TTupleLiteral* tableTuple = AS_VALUE(TTupleLiteral, tableList->GetItems()[i]);
+ YQL_ENSURE(tableTuple->GetValuesCount() == 3);
+
+ NYT::TRichYPath richYPath;
+ NYT::Deserialize(richYPath, NYT::NodeFromYsonString(TString(AS_VALUE(TDataLiteral, tableTuple->GetValue(1))->AsValue().AsStringRef())));
+
+ if (params.contains(paramsKey)) {
+ NYT::TRichYPath ranges;
+ NYT::Deserialize(ranges, params[paramsKey]);
+ richYPath.MutableRanges() = ranges.GetRanges();
+ } else {
+ richYPath.MutableRanges().ConstructInPlace();
+ }
+
+ newTableList.push_back(pgmBuilder.NewTuple({
+ tableTuple->GetValue(0),
+ pgmBuilder.NewDataLiteral<NUdf::EDataSlot::String>(NYT::NodeToYsonString(NYT::PathToNode(richYPath))),
+ tableTuple->GetValue(2),
+ }));
+ }
+ newGrpList.push_back(pgmBuilder.NewList(newTableList.front().GetStaticType(), newTableList));
+ }
+ callableBuilder.Add(pgmBuilder.NewList(newGrpList.front().GetStaticType(), newGrpList));
+ }
+ callableBuilder.Add(callable.GetInput(5));
+ callableBuilder.Add(callable.GetInput(6));
+ callableBuilder.Add(callable.GetInput(7));
+ return TRuntimeNode(callableBuilder.Build(), false);
+ };
+ }
+ if (name == "YtDqRowsWideWrite") {
+ YQL_ENSURE(TaskParams.contains("yt.write.tx"), "Expected nested transaction");
+ return [this](NMiniKQL::TCallable& callable, const NMiniKQL::TTypeEnvironment& env) -> NMiniKQL::TRuntimeNode {
+ using namespace NMiniKQL;
+
+ TProgramBuilder pgmBuilder(env, FunctionRegistry);
+
+ YQL_ENSURE(callable.GetInputsCount() == 6, "Expected six arguments.");
+
+ TCallableBuilder callableBuilder(env, callable.GetType()->GetName(), callable.GetType()->GetReturnType(), false);
+ callableBuilder.Add(callable.GetInput(0));
+ callableBuilder.Add(callable.GetInput(1));
+ callableBuilder.Add(callable.GetInput(2));
+
+ NYT::TRichYPath richYPath;
+ NYT::Deserialize(richYPath, NYT::NodeFromYsonString(TString(AS_VALUE(TDataLiteral, callable.GetInput(3))->AsValue().AsStringRef())));
+ richYPath.TransactionId(GetGuid(TaskParams.Value("yt.write.tx", TString())));
+ callableBuilder.Add(pgmBuilder.NewDataLiteral<NUdf::EDataSlot::String>(NYT::NodeToYsonString(NYT::PathToNode(richYPath))));
+
+ callableBuilder.Add(callable.GetInput(4));
+ callableBuilder.Add(callable.GetInput(5));
+
+ return TRuntimeNode(callableBuilder.Build(), false);
+ };
+ }
+
+ return NMiniKQL::TCallableVisitFunc();
+ }
+
+private:
+ THashMap<TString, TString> TaskParams;
+ const NMiniKQL::IFunctionRegistry& FunctionRegistry;
+};
+
+TTaskTransformFactory CreateYtDqTaskTransformFactory() {
+ return [] (const THashMap<TString, TString>& taskParams, const NKikimr::NMiniKQL::IFunctionRegistry* funcRegistry) -> NKikimr::NMiniKQL::TCallableVisitFuncProvider {
+ return TYtDqTaskTransform(taskParams, *funcRegistry);
+ };
+}
+
+} // NYql
diff --git a/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.h b/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.h
new file mode 100644
index 0000000000..3c5dfc0493
--- /dev/null
+++ b/ydb/library/yql/providers/yt/mkql_dq/yql_yt_dq_transform.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <ydb/library/yql/dq/integration/transform/yql_dq_task_transform.h>
+
+namespace NYql {
+
+TTaskTransformFactory CreateYtDqTaskTransformFactory();
+
+}
diff --git a/ydb/library/yql/providers/yt/provider/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/provider/CMakeLists.darwin-x86_64.txt
index 011d8774a5..dd6cc43535 100644
--- a/ydb/library/yql/providers/yt/provider/CMakeLists.darwin-x86_64.txt
+++ b/ydb/library/yql/providers/yt/provider/CMakeLists.darwin-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_enum_parser_bin
TOOL_enum_parser_dependency
diff --git a/ydb/library/yql/providers/yt/provider/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/provider/CMakeLists.linux-aarch64.txt
index 5fed4af2d0..6d095ca3b0 100644
--- a/ydb/library/yql/providers/yt/provider/CMakeLists.linux-aarch64.txt
+++ b/ydb/library/yql/providers/yt/provider/CMakeLists.linux-aarch64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_enum_parser_bin
TOOL_enum_parser_dependency
diff --git a/ydb/library/yql/providers/yt/provider/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/provider/CMakeLists.linux-x86_64.txt
index 5fed4af2d0..6d095ca3b0 100644
--- a/ydb/library/yql/providers/yt/provider/CMakeLists.linux-x86_64.txt
+++ b/ydb/library/yql/providers/yt/provider/CMakeLists.linux-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_enum_parser_bin
TOOL_enum_parser_dependency
diff --git a/ydb/library/yql/providers/yt/provider/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/provider/CMakeLists.windows-x86_64.txt
index 011d8774a5..dd6cc43535 100644
--- a/ydb/library/yql/providers/yt/provider/CMakeLists.windows-x86_64.txt
+++ b/ydb/library/yql/providers/yt/provider/CMakeLists.windows-x86_64.txt
@@ -6,6 +6,7 @@
# original buildsystem will not be accepted.
+add_subdirectory(ut)
get_built_tool_path(
TOOL_enum_parser_bin
TOOL_enum_parser_dependency
diff --git a/ydb/library/yql/providers/yt/provider/ut/CMakeLists.darwin-x86_64.txt b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..dfa077e6df
--- /dev/null
+++ b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,89 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-provider-ut)
+target_compile_options(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider
+)
+target_link_libraries(ydb-library-yql-providers-yt-provider-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-provider
+ yt-lib-schema
+ yt-gateway-file
+ yql-core-ut_common
+ library-yql-ast
+ udf-service-terminate_policy
+ yql-core-services
+ library-yql-core
+ providers-common-gateway
+ providers-common-provider
+ providers-common-config
+ yql-providers-config
+ providers-dq-common
+ providers-dq-provider
+ providers-result-provider
+ yql-sql-v1
+ minikql-invoke_builtins-llvm
+ yql-sql-pg
+)
+target_link_options(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ -Wl,-platform_version,macos,11.0,11.0
+ -fPIC
+ -fPIC
+ -framework
+ CoreFoundation
+)
+target_sources(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_dq_integration_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_epoch_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_cbo_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-provider-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-provider-ut)
diff --git a/ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-aarch64.txt b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..1389d843ed
--- /dev/null
+++ b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,93 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-provider-ut)
+target_compile_options(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider
+)
+target_link_libraries(ydb-library-yql-providers-yt-provider-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-testing-unittest_main
+ providers-yt-provider
+ yt-lib-schema
+ yt-gateway-file
+ yql-core-ut_common
+ library-yql-ast
+ udf-service-terminate_policy
+ yql-core-services
+ library-yql-core
+ providers-common-gateway
+ providers-common-provider
+ providers-common-config
+ yql-providers-config
+ providers-dq-common
+ providers-dq-provider
+ providers-result-provider
+ yql-sql-v1
+ minikql-invoke_builtins-llvm
+ yql-sql-pg
+)
+target_link_options(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+ -lutil
+)
+target_sources(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_dq_integration_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_epoch_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_cbo_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-provider-ut
+ cpp-malloc-jemalloc
+)
+vcs_info(ydb-library-yql-providers-yt-provider-ut)
diff --git a/ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-x86_64.txt b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..4f29f9c957
--- /dev/null
+++ b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,95 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-provider-ut)
+target_compile_options(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider
+)
+target_link_libraries(ydb-library-yql-providers-yt-provider-ut PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-provider
+ yt-lib-schema
+ yt-gateway-file
+ yql-core-ut_common
+ library-yql-ast
+ udf-service-terminate_policy
+ yql-core-services
+ library-yql-core
+ providers-common-gateway
+ providers-common-provider
+ providers-common-config
+ yql-providers-config
+ providers-dq-common
+ providers-dq-provider
+ providers-result-provider
+ yql-sql-v1
+ minikql-invoke_builtins-llvm
+ yql-sql-pg
+)
+target_link_options(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ -ldl
+ -lrt
+ -Wl,--no-as-needed
+ -fPIC
+ -fPIC
+ -lpthread
+ -lrt
+ -ldl
+ -lutil
+)
+target_sources(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_dq_integration_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_epoch_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_cbo_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-provider-ut
+ cpp-malloc-tcmalloc
+ libs-tcmalloc-no_percpu_cache
+)
+vcs_info(ydb-library-yql-providers-yt-provider-ut)
diff --git a/ydb/library/yql/providers/yt/provider/ut/CMakeLists.txt b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.txt
new file mode 100644
index 0000000000..f8b31df0c1
--- /dev/null
+++ b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/ydb/library/yql/providers/yt/provider/ut/CMakeLists.windows-x86_64.txt b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..8bc613ff3e
--- /dev/null
+++ b/ydb/library/yql/providers/yt/provider/ut/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,82 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_executable(ydb-library-yql-providers-yt-provider-ut)
+target_compile_options(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ -DUSE_CURRENT_UDF_ABI_VERSION
+)
+target_include_directories(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider
+)
+target_link_libraries(ydb-library-yql-providers-yt-provider-ut PUBLIC
+ contrib-libs-cxxsupp
+ yutil
+ library-cpp-cpuid_check
+ cpp-testing-unittest_main
+ providers-yt-provider
+ yt-lib-schema
+ yt-gateway-file
+ yql-core-ut_common
+ library-yql-ast
+ udf-service-terminate_policy
+ yql-core-services
+ library-yql-core
+ providers-common-gateway
+ providers-common-provider
+ providers-common-config
+ yql-providers-config
+ providers-dq-common
+ providers-dq-provider
+ providers-result-provider
+ yql-sql-v1
+ minikql-invoke_builtins-llvm
+ yql-sql-pg
+)
+target_sources(ydb-library-yql-providers-yt-provider-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_dq_integration_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_epoch_ut.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/library/yql/providers/yt/provider/ut/yql_yt_cbo_ut.cpp
+)
+set_property(
+ TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ SPLIT_FACTOR
+ 1
+)
+add_yunittest(
+ NAME
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_TARGET
+ ydb-library-yql-providers-yt-provider-ut
+ TEST_ARG
+ --print-before-suite
+ --print-before-test
+ --fork-tests
+ --print-times
+ --show-fails
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ LABELS
+ SMALL
+)
+set_yunittest_property(
+ TEST
+ ydb-library-yql-providers-yt-provider-ut
+ PROPERTY
+ PROCESSORS
+ 1
+)
+target_allocator(ydb-library-yql-providers-yt-provider-ut
+ system_allocator
+)
+vcs_info(ydb-library-yql-providers-yt-provider-ut)
diff --git a/ydb/library/yql/providers/yt/ya.make b/ydb/library/yql/providers/yt/ya.make
new file mode 100644
index 0000000000..a43252062f
--- /dev/null
+++ b/ydb/library/yql/providers/yt/ya.make
@@ -0,0 +1,17 @@
+RECURSE(
+ codec
+ codec/codegen/ut
+ codec/ut
+ common
+ comp_nodes
+ comp_nodes/ut
+ comp_nodes/dq
+ expr_nodes
+ gateway
+ job
+ lib
+ mkql_dq
+ opt
+ provider
+ provider/ut
+)
diff --git a/yt/yt/CMakeLists.darwin-x86_64.txt b/yt/yt/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..b0414f0741
--- /dev/null
+++ b/yt/yt/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,11 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(build)
+add_subdirectory(core)
+add_subdirectory(library)
diff --git a/yt/yt/CMakeLists.linux-aarch64.txt b/yt/yt/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..91d55b509b
--- /dev/null
+++ b/yt/yt/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,12 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(build)
+add_subdirectory(client)
+add_subdirectory(core)
+add_subdirectory(library)
diff --git a/yt/yt/CMakeLists.linux-x86_64.txt b/yt/yt/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..91d55b509b
--- /dev/null
+++ b/yt/yt/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,12 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(build)
+add_subdirectory(client)
+add_subdirectory(core)
+add_subdirectory(library)
diff --git a/yt/yt/CMakeLists.txt b/yt/yt/CMakeLists.txt
index b0414f0741..f8b31df0c1 100644
--- a/yt/yt/CMakeLists.txt
+++ b/yt/yt/CMakeLists.txt
@@ -6,6 +6,12 @@
# original buildsystem will not be accepted.
-add_subdirectory(build)
-add_subdirectory(core)
-add_subdirectory(library)
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/CMakeLists.windows-x86_64.txt b/yt/yt/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..b0414f0741
--- /dev/null
+++ b/yt/yt/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,11 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(build)
+add_subdirectory(core)
+add_subdirectory(library)
diff --git a/yt/yt/client/CMakeLists.linux-aarch64.txt b/yt/yt/client/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..839714005e
--- /dev/null
+++ b/yt/yt/client/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,206 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(arrow)
+add_subdirectory(query_tracker_client)
+set(
+ YT_RPC_MODIFY_ROWS_STRONG_LOCKS_VERSION
+ 2
+)
+set(
+ YT_RPC_PROXY_CLIENT_PROTOCOL_VERSION_MINOR
+ 1
+)
+set(
+ YT_RPC_PROXY_PROTOCOL_VERSION_MAJOR
+ 1
+)
+set(
+ YT_RPC_PROXY_SERVER_PROTOCOL_VERSION_MINOR
+ 2
+)
+
+add_library(yt-yt-client)
+target_compile_options(yt-yt-client PRIVATE
+ -Wdeprecated-this-capture
+)
+target_include_directories(yt-yt-client PUBLIC
+ ${CMAKE_BINARY_DIR}/yt
+ ${CMAKE_BINARY_DIR}/yt/yt/client/_/api/rpc_proxy
+)
+target_link_libraries(yt-yt-client PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-client-query_tracker_client
+ yt-yt-core
+ yt-core-http
+ yt-library-auth
+ yt-library-decimal
+ yt-library-re2
+ yt-library-erasure
+ yt-library-numeric
+ yt-library-quantile_digest
+ yt_proto-yt-client
+ library-cpp-json
+ contrib-libs-pfr
+)
+target_sources(yt-yt-client PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/client_common.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/client_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/delegating_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/etc_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/journal_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/operation_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/security_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/table_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/query_tracker_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/internal_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/operation_archive_schema.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rowset.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/skynet.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/transaction.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/persistent_queue.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/sticky_transaction_pool.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/address_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/client_impl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/client_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/connection.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/connection_impl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/file_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/file_writer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/journal_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/journal_writer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/table_mount_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/table_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/table_writer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/transaction.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/transaction_impl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/row_stream.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/wire_row_stream.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/election/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/hive/timestamp_map.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/hydra/version.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/replication_card.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/replication_card_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/replication_card_serialization.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/chunk_replica.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/data_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/read_limit.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/ready_event_reader_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/boolean_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/floating_point_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/helper.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/integer_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/null_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/string_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/journal_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/journal_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/cypress_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/node_tracker_client/node_directory.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/node_tracker_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/node_tracker_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/object_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/object_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/scheduler/operation_id_or_alias.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/scheduler/operation_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/acl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/access_control.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/adapters.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/table_output.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/blob_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/check_schema_compatibility.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/chunk_stripe_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/column_rename_descriptor.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/column_sort_schema.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/comparator.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/key.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/key_bound.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/key_bound_compressor.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/pipe.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/versioned_row.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/unversioned_row.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/unversioned_value.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/versioned_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/row_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/row_batch.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/row_buffer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/schema.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/schema_serialization_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/serialize.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/logical_type.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/name_table.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/wire_protocol.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/columnar_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/value_consumer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/table_consumer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/schemaless_row_reorderer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/unordered_schemaful_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/validate_logical_type.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/composite_compare.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/columnar.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/record_codegen_cpp.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/record_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/table_mount_cache_detail.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/table_mount_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/common.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/consumer_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/partition_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/queue_rowset.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/ypath/rich.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/ypath/parser_detail.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/batching_timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/noop_timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/remote_timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/timestamp_provider_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/io_tags.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/method_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/workload.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/job_tracker_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/job_tracker_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/query_client/query_builder.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/query_client/query_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/check_yson_token.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/check_type_compatibility.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/infinite_entity.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/yson_format_conversion.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/uuid_text.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/time_text.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/zookeeper/packet.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/zookeeper/protocol.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/zookeeper/requests.cpp
+)
+configure_file(
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/protocol_version_variables.h.in
+ ${CMAKE_BINARY_DIR}/yt/yt/client/_/api/rpc_proxy/protocol_version_variables.h
+)
diff --git a/yt/yt/client/CMakeLists.linux-x86_64.txt b/yt/yt/client/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..839714005e
--- /dev/null
+++ b/yt/yt/client/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,206 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(arrow)
+add_subdirectory(query_tracker_client)
+set(
+ YT_RPC_MODIFY_ROWS_STRONG_LOCKS_VERSION
+ 2
+)
+set(
+ YT_RPC_PROXY_CLIENT_PROTOCOL_VERSION_MINOR
+ 1
+)
+set(
+ YT_RPC_PROXY_PROTOCOL_VERSION_MAJOR
+ 1
+)
+set(
+ YT_RPC_PROXY_SERVER_PROTOCOL_VERSION_MINOR
+ 2
+)
+
+add_library(yt-yt-client)
+target_compile_options(yt-yt-client PRIVATE
+ -Wdeprecated-this-capture
+)
+target_include_directories(yt-yt-client PUBLIC
+ ${CMAKE_BINARY_DIR}/yt
+ ${CMAKE_BINARY_DIR}/yt/yt/client/_/api/rpc_proxy
+)
+target_link_libraries(yt-yt-client PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-client-query_tracker_client
+ yt-yt-core
+ yt-core-http
+ yt-library-auth
+ yt-library-decimal
+ yt-library-re2
+ yt-library-erasure
+ yt-library-numeric
+ yt-library-quantile_digest
+ yt_proto-yt-client
+ library-cpp-json
+ contrib-libs-pfr
+)
+target_sources(yt-yt-client PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/client_common.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/client_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/delegating_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/etc_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/journal_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/operation_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/security_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/table_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/query_tracker_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/internal_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/operation_archive_schema.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rowset.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/skynet.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/transaction.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/persistent_queue.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/sticky_transaction_pool.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/address_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/client_impl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/client_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/connection.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/connection_impl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/file_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/file_writer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/journal_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/journal_writer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/table_mount_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/table_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/table_writer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/transaction.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/transaction_impl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/row_stream.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/wire_row_stream.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/election/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/hive/timestamp_map.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/hydra/version.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/replication_card.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/replication_card_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chaos_client/replication_card_serialization.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/chunk_replica.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/data_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/read_limit.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/chunk_client/ready_event_reader_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/boolean_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/floating_point_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/helper.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/integer_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/null_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/converters/string_converter.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/journal_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/journal_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/cypress_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/node_tracker_client/node_directory.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/node_tracker_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/node_tracker_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/object_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/object_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/scheduler/operation_id_or_alias.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/scheduler/operation_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/acl.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/access_control.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/security_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/adapters.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/table_output.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/blob_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/check_schema_compatibility.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/chunk_stripe_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/column_rename_descriptor.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/column_sort_schema.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/comparator.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/key.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/key_bound.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/key_bound_compressor.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/pipe.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/versioned_row.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/unversioned_row.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/unversioned_value.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/versioned_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/row_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/row_batch.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/row_buffer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/schema.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/schema_serialization_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/serialize.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/logical_type.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/name_table.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/wire_protocol.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/columnar_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/value_consumer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/table_consumer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/schemaless_row_reorderer.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/unordered_schemaful_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/validate_logical_type.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/composite_compare.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/columnar.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/record_codegen_cpp.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/table_client/record_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/table_mount_cache_detail.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/table_mount_cache.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/tablet_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/common.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/consumer_client.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/partition_reader.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/queue_client/queue_rowset.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/ypath/rich.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/ypath/parser_detail.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/batching_timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/noop_timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/remote_timestamp_provider.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/transaction_client/timestamp_provider_base.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/io_tags.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/method_helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/misc/workload.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/job_tracker_client/public.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/job_tracker_client/helpers.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/query_client/query_builder.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/query_client/query_statistics.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/check_yson_token.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/check_type_compatibility.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/infinite_entity.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/yson_format_conversion.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/uuid_text.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/complex_types/time_text.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/zookeeper/packet.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/zookeeper/protocol.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/zookeeper/requests.cpp
+)
+configure_file(
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/api/rpc_proxy/protocol_version_variables.h.in
+ ${CMAKE_BINARY_DIR}/yt/yt/client/_/api/rpc_proxy/protocol_version_variables.h
+)
diff --git a/yt/yt/client/CMakeLists.txt b/yt/yt/client/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/client/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/client/api/accounting_client.h b/yt/yt/client/api/accounting_client.h
new file mode 100644
index 0000000000..80113dc8c8
--- /dev/null
+++ b/yt/yt/client/api/accounting_client.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "client_common.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTransferAccountResourcesOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+struct TTransferPoolResourcesOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IAccountingClient
+{
+ virtual TFuture<void> TransferAccountResources(
+ const TString& srcAccount,
+ const TString& dstAccount,
+ NYTree::INodePtr resourceDelta,
+ const TTransferAccountResourcesOptions& options = {}) = 0;
+
+ virtual TFuture<void> TransferPoolResources(
+ const TString& srcPool,
+ const TString& dstPool,
+ const TString& poolTree,
+ NYTree::INodePtr resourceDelta,
+ const TTransferPoolResourcesOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/admin_client.h b/yt/yt/client/api/admin_client.h
new file mode 100644
index 0000000000..176f88370c
--- /dev/null
+++ b/yt/yt/client/api/admin_client.h
@@ -0,0 +1,282 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/client/job_tracker_client/public.h>
+
+#include <yt/yt/client/chaos_client/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBuildSnapshotOptions
+ : public TTimeoutOptions
+{
+ //! Refers either to masters or to tablet cells.
+ //! If null then the primary one is assumed.
+ NHydra::TCellId CellId;
+ bool SetReadOnly = false;
+ bool WaitForSnapshotCompletion = true;
+};
+
+struct TBuildMasterSnapshotsOptions
+ : public TTimeoutOptions
+{
+ bool SetReadOnly = false;
+ bool WaitForSnapshotCompletion = true;
+ bool Retry = true;
+};
+
+struct TSwitchLeaderOptions
+ : public TTimeoutOptions
+{ };
+
+struct TResetStateHashOptions
+ : public TTimeoutOptions
+{
+ //! If not set, random number is used.
+ std::optional<ui64> NewStateHash;
+};
+
+struct TGCCollectOptions
+ : public TTimeoutOptions
+{
+ //! Refers to master cell.
+ //! If null then the primary one is assumed.
+ NHydra::TCellId CellId;
+};
+
+struct TKillProcessOptions
+ : public TTimeoutOptions
+{
+ int ExitCode = 42;
+};
+
+struct TWriteCoreDumpOptions
+ : public TTimeoutOptions
+{ };
+
+struct TWriteLogBarrierOptions
+ : public TTimeoutOptions
+{
+ TString Category;
+};
+
+struct TWriteOperationControllerCoreDumpOptions
+ : public TTimeoutOptions
+{ };
+
+struct THealExecNodeOptions
+ : public TTimeoutOptions
+{
+ std::vector<TString> Locations;
+ std::vector<TString> AlertTypesToReset;
+ bool ForceReset = false;
+};
+
+struct TSuspendCoordinatorOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+struct TResumeCoordinatorOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+struct TMigrateReplicationCardsOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{
+ NObjectClient::TCellId DestinationCellId;
+ std::vector<NChaosClient::TReplicationCardId> ReplicationCardIds;
+};
+
+struct TSuspendChaosCellsOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+struct TResumeChaosCellsOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+struct TSuspendTabletCellsOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+struct TResumeTabletCellsOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{ };
+
+struct TDisableChunkLocationsOptions
+ : public TTimeoutOptions
+{ };
+
+struct TDisableChunkLocationsResult
+{
+ std::vector<TGuid> LocationUuids;
+};
+
+struct TDestroyChunkLocationsOptions
+ : public TTimeoutOptions
+{ };
+
+struct TDestroyChunkLocationsResult
+{
+ std::vector<TGuid> LocationUuids;
+};
+
+struct TResurrectChunkLocationsOptions
+ : public TTimeoutOptions
+{ };
+
+struct TResurrectChunkLocationsResult
+{
+ std::vector<TGuid> LocationUuids;
+};
+
+using TCellIdToSnapshotIdMap = THashMap<NHydra::TCellId, int>;
+
+struct TAddMaintenanceOptions
+ : public TTimeoutOptions
+{ };
+
+struct TMaintenanceFilter
+{
+ struct TByUser
+ {
+ struct TAll
+ { };
+
+ struct TMine
+ { };
+ };
+
+ // Empty means no filtering by id.
+ std::vector<TMaintenanceId> Ids;
+ std::optional<EMaintenanceType> Type;
+ std::variant<TByUser::TAll, TByUser::TMine, TString> User;
+};
+
+struct TRemoveMaintenanceOptions
+ : public TTimeoutOptions
+{ };
+
+struct TRequestRebootOptions
+ : public TTimeoutOptions
+{ };
+
+struct TRequestRebootResult
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IAdminClient
+{
+ virtual TFuture<int> BuildSnapshot(
+ const TBuildSnapshotOptions& options = {}) = 0;
+
+ virtual TFuture<TCellIdToSnapshotIdMap> BuildMasterSnapshots(
+ const TBuildMasterSnapshotsOptions& options = {}) = 0;
+
+ virtual TFuture<void> SwitchLeader(
+ NHydra::TCellId cellId,
+ const TString& newLeaderAddress,
+ const TSwitchLeaderOptions& options = {}) = 0;
+
+ virtual TFuture<void> ResetStateHash(
+ NHydra::TCellId cellId,
+ const TResetStateHashOptions& options = {}) = 0;
+
+ virtual TFuture<void> GCCollect(
+ const TGCCollectOptions& options = {}) = 0;
+
+ virtual TFuture<void> KillProcess(
+ const TString& address,
+ const TKillProcessOptions& options = {}) = 0;
+
+ virtual TFuture<TString> WriteCoreDump(
+ const TString& address,
+ const TWriteCoreDumpOptions& options = {}) = 0;
+
+ virtual TFuture<TGuid> WriteLogBarrier(
+ const TString& address,
+ const TWriteLogBarrierOptions& options) = 0;
+
+ virtual TFuture<TString> WriteOperationControllerCoreDump(
+ NJobTrackerClient::TOperationId operationId,
+ const TWriteOperationControllerCoreDumpOptions& options = {}) = 0;
+
+ virtual TFuture<void> HealExecNode(
+ const TString& address,
+ const THealExecNodeOptions& options = {}) = 0;
+
+ virtual TFuture<void> SuspendCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TSuspendCoordinatorOptions& options = {}) = 0;
+
+ virtual TFuture<void> ResumeCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TResumeCoordinatorOptions& options = {}) = 0;
+
+ virtual TFuture<void> MigrateReplicationCards(
+ NObjectClient::TCellId chaosCellId,
+ const TMigrateReplicationCardsOptions& options = {}) = 0;
+
+ virtual TFuture<void> SuspendChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendChaosCellsOptions& options = {}) = 0;
+
+ virtual TFuture<void> ResumeChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeChaosCellsOptions& options = {}) = 0;
+
+ virtual TFuture<void> SuspendTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendTabletCellsOptions& options = {}) = 0;
+
+ virtual TFuture<void> ResumeTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeTabletCellsOptions& options = {}) = 0;
+
+ virtual TFuture<TMaintenanceId> AddMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ EMaintenanceType type,
+ const TString& comment,
+ const TAddMaintenanceOptions& options = {}) = 0;
+
+ virtual TFuture<TMaintenanceCounts> RemoveMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ const TMaintenanceFilter& filter,
+ const TRemoveMaintenanceOptions& options = {}) = 0;
+
+ virtual TFuture<TDisableChunkLocationsResult> DisableChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDisableChunkLocationsOptions& options = {}) = 0;
+
+ virtual TFuture<TDestroyChunkLocationsResult> DestroyChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDestroyChunkLocationsOptions& options = {}) = 0;
+
+ virtual TFuture<TResurrectChunkLocationsResult> ResurrectChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TResurrectChunkLocationsOptions& options = {}) = 0;
+
+ virtual TFuture<TRequestRebootResult> RequestReboot(
+ const TString& nodeAddress,
+ const TRequestRebootOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/client.cpp b/yt/yt/client/api/client.cpp
new file mode 100644
index 0000000000..be231d8ebf
--- /dev/null
+++ b/yt/yt/client/api/client.cpp
@@ -0,0 +1,65 @@
+#include "client.h"
+#include "transaction.h"
+#include "private.h"
+
+#include <yt/yt/client/job_tracker_client/helpers.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NApi {
+
+using namespace NConcurrency;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto& Logger = ApiLogger;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: After the cluster name is actually set, the value never changes. Thus, it is safe to return TStringBuf.
+std::optional<TStringBuf> TClusterAwareClientBase::GetClusterName(bool fetchIfNull)
+{
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ if (ClusterName_) {
+ return ClusterName_;
+ }
+ }
+
+ auto clusterName = GetConnection()->GetClusterName();
+ if (fetchIfNull && !clusterName) {
+ clusterName = FetchClusterNameFromMasterCache();
+ }
+
+ if (!clusterName) {
+ return {};
+ }
+
+ auto guard = WriterGuard(SpinLock_);
+ if (!ClusterName_) {
+ ClusterName_ = clusterName;
+ }
+
+ return ClusterName_;
+}
+
+std::optional<TString> TClusterAwareClientBase::FetchClusterNameFromMasterCache()
+{
+ TGetNodeOptions options;
+ options.ReadFrom = EMasterChannelKind::MasterCache;
+ auto clusterNameYsonOrError = WaitFor(GetNode(ClusterNamePath, options));
+ if (!clusterNameYsonOrError.IsOK()) {
+ YT_LOG_WARNING(clusterNameYsonOrError, "Could not fetch cluster name from from master cache (Path: %v)",
+ ClusterNamePath);
+ return {};
+ }
+ return ConvertTo<TString>(clusterNameYsonOrError.Value());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/client.h b/yt/yt/client/api/client.h
new file mode 100644
index 0000000000..3248ea089a
--- /dev/null
+++ b/yt/yt/client/api/client.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include "connection.h"
+#include "accounting_client.h"
+#include "admin_client.h"
+#include "cypress_client.h"
+#include "etc_client.h"
+#include "file_client.h"
+#include "journal_client.h"
+#include "operation_client.h"
+#include "security_client.h"
+#include "transaction_client.h"
+#include "table_client.h"
+#include "queue_client.h"
+#include "query_tracker_client.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Provides a basic set of functions that can be invoked
+//! both standalone and inside transaction.
+/*
+ * This interface contains methods shared by IClient and ITransaction.
+ *
+ * Thread affinity: single
+ */
+struct IClientBase
+ : public virtual TRefCounted
+ , public ITransactionClientBase
+ , public ITableClientBase
+ , public ICypressClientBase
+ , public IFileClientBase
+ , public IJournalClientBase
+ , public IQueueClientBase
+ , public IEtcClientBase
+{
+ virtual IConnectionPtr GetConnection() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IClientBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A central entry point for all interactions with the YT cluster.
+/*!
+ * In contrast to IConnection, each IClient represents an authenticated entity.
+ * The needed username is passed to #IConnection::CreateClient via options.
+ * Note that YT API has no built-in authentication mechanisms so it must be wrapped
+ * with appropriate logic.
+ *
+ * Most methods accept |TransactionId| as a part of their options.
+ * A similar effect can be achieved by issuing requests via ITransaction.
+ */
+struct IClient
+ : public virtual IClientBase
+ , public ITransactionClient
+ , public ITableClient
+ , public IQueueClient
+ , public IJournalClient
+ , public IFileClient
+ , public ISecurityClient
+ , public IAccountingClient
+ , public IOperationClient
+ , public IAdminClient
+ , public IQueryTrackerClient
+ , public IEtcClient
+{
+ //! Terminates all channels.
+ //! Aborts all pending uncommitted transactions.
+ virtual void Terminate() = 0;
+
+ virtual const NTabletClient::ITableMountCachePtr& GetTableMountCache() = 0;
+ virtual const NChaosClient::IReplicationCardCachePtr& GetReplicationCardCache() = 0;
+ virtual const NTransactionClient::ITimestampProviderPtr& GetTimestampProvider() = 0;
+
+ virtual std::optional<TStringBuf> GetClusterName(bool fetchIfNull = true) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IClient)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TClusterAwareClientBase
+ : public virtual IClient
+{
+public:
+ //! Returns and caches the cluster name corresponding to this client.
+ //! If available, the name is taken from the client's connection configuration.
+ //! Otherwise, if fetchIfNull is set to true, the first call to this method
+ //! will fetch the cluster name from Cypress via master caches.
+ //!
+ //! NB: Descendants of this class should be able to perform GetNode calls,
+ //! so this cannot be used directly in tablet transactions.
+ //! Use the transaction's parent client instead.
+ std::optional<TStringBuf> GetClusterName(bool fetchIfNull) override;
+
+private:
+ YT_DECLARE_SPIN_LOCK(NThreading::TReaderWriterSpinLock, SpinLock_);
+ std::optional<TString> ClusterName_;
+
+ std::optional<TString> FetchClusterNameFromMasterCache();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/client_cache.cpp b/yt/yt/client/api/client_cache.cpp
new file mode 100644
index 0000000000..2af50c294b
--- /dev/null
+++ b/yt/yt/client/api/client_cache.cpp
@@ -0,0 +1,45 @@
+#include "client_cache.h"
+#include "connection.h"
+
+namespace NYT::NApi {
+
+using namespace NRpc;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCachedClient::TCachedClient(
+ const TAuthenticationIdentity& identity,
+ IClientPtr client)
+ : TSyncCacheValueBase(identity)
+ , Client_(std::move(client))
+{ }
+
+const IClientPtr& TCachedClient::GetClient()
+{
+ return Client_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TClientCache::TClientCache(
+ TSlruCacheConfigPtr config,
+ IConnectionPtr connection)
+ : TSyncSlruCacheBase<TAuthenticationIdentity, TCachedClient>(std::move(config))
+ , Connection_(std::move(connection))
+{ }
+
+IClientPtr TClientCache::Get(
+ const TAuthenticationIdentity& identity,
+ const TClientOptions& options)
+{
+ auto cachedClient = Find(identity);
+ if (!cachedClient) {
+ cachedClient = New<TCachedClient>(identity, Connection_->CreateClient(options));
+ TryInsert(cachedClient, &cachedClient);
+ }
+ return cachedClient->GetClient();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/client_cache.h b/yt/yt/client/api/client_cache.h
new file mode 100644
index 0000000000..bc7d455b6a
--- /dev/null
+++ b/yt/yt/client/api/client_cache.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "private.h"
+
+#include <yt/yt/core/misc/sync_cache.h>
+
+#include <yt/yt/core/rpc/authentication_identity.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCachedClient
+ : public TSyncCacheValueBase<NRpc::TAuthenticationIdentity, TCachedClient>
+{
+public:
+ TCachedClient(
+ const NRpc::TAuthenticationIdentity& identity,
+ IClientPtr client);
+
+ const IClientPtr& GetClient();
+
+private:
+ const IClientPtr Client_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! An SLRU-cache based class for keeping a cache of clients for different users.
+/*
+ * For NApi::NNative::IClient equivalent see ytlib/api/native/client_cache.h.
+ *
+ * Cache is completely thread-safe.
+ */
+class TClientCache
+ : public TSyncSlruCacheBase<NRpc::TAuthenticationIdentity, TCachedClient>
+{
+public:
+ TClientCache(
+ TSlruCacheConfigPtr config,
+ IConnectionPtr connection);
+
+ IClientPtr Get(
+ const NRpc::TAuthenticationIdentity& identity,
+ const TClientOptions& options);
+
+private:
+ // TODO(max42): shouldn't this be TWeakPtr?
+ const IConnectionPtr Connection_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TClientCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/client_common.cpp b/yt/yt/client/api/client_common.cpp
new file mode 100644
index 0000000000..ce15cfb9f7
--- /dev/null
+++ b/yt/yt/client/api/client_common.cpp
@@ -0,0 +1,133 @@
+#include "client_common.h"
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT::NApi {
+
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+EWorkloadCategory FromUserWorkloadCategory(EUserWorkloadCategory category)
+{
+ switch (category) {
+ case EUserWorkloadCategory::Realtime:
+ return EWorkloadCategory::UserRealtime;
+ case EUserWorkloadCategory::Interactive:
+ return EWorkloadCategory::UserInteractive;
+ case EUserWorkloadCategory::Batch:
+ return EWorkloadCategory::UserBatch;
+ default:
+ YT_ABORT();
+ }
+}
+
+} // namespace
+
+TUserWorkloadDescriptor::operator TWorkloadDescriptor() const
+{
+ TWorkloadDescriptor result;
+ result.Category = FromUserWorkloadCategory(Category);
+ result.Band = Band;
+ return result;
+}
+
+struct TSerializableUserWorkloadDescriptor
+ : public TYsonStructLite
+ , public TUserWorkloadDescriptor
+{
+ REGISTER_YSON_STRUCT_LITE(TSerializableUserWorkloadDescriptor);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.BaseClassParameter("category", &TThis::Category);
+ registrar.BaseClassParameter("band", &TThis::Band)
+ .Optional();
+ }
+
+public:
+ static TThis Wrap(const TUserWorkloadDescriptor& source)
+ {
+ TThis result = Create();
+ result.Band = source.Band;
+ result.Category = source.Category;
+ return result;
+ }
+
+ TUserWorkloadDescriptor Unwrap()
+ {
+ TUserWorkloadDescriptor result;
+ result.Band = Band;
+ result.Category = Category;
+ return result;
+ }
+};
+
+void Serialize(const TUserWorkloadDescriptor& workloadDescriptor, NYson::IYsonConsumer* consumer)
+{
+ NYTree::Serialize(TSerializableUserWorkloadDescriptor::Wrap(workloadDescriptor), consumer);
+}
+
+void Deserialize(TUserWorkloadDescriptor& workloadDescriptor, INodePtr node)
+{
+ auto serializableWorkloadDescriptor = TSerializableUserWorkloadDescriptor::Create();
+ NYTree::Deserialize(serializableWorkloadDescriptor, node);
+ workloadDescriptor = serializableWorkloadDescriptor.Unwrap();
+}
+
+void Deserialize(TUserWorkloadDescriptor& workloadDescriptor, NYson::TYsonPullParserCursor* cursor)
+{
+ auto serializableWorkloadDescriptor = TSerializableUserWorkloadDescriptor::Create();
+ NYTree::Deserialize(serializableWorkloadDescriptor, cursor);
+ workloadDescriptor = serializableWorkloadDescriptor.Unwrap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NRpc::TMutationId TMutatingOptions::GetOrGenerateMutationId() const
+{
+ if (Retry && !MutationId) {
+ THROW_ERROR_EXCEPTION("Cannot execute retry without mutation id");
+ }
+ return MutationId ? MutationId : NRpc::GenerateMutationId();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TSerializableMasterReadOptions::Register(TRegistrar registrar)
+{
+ registrar.BaseClassParameter("read_from", &TThis::ReadFrom)
+ .Default(TMasterReadOptions{}.ReadFrom);
+
+ registrar.BaseClassParameter("disable_per_user_cache", &TThis::DisablePerUserCache)
+ .Default(TMasterReadOptions{}.DisablePerUserCache);
+
+ registrar.BaseClassParameter("expire_after_successful_update_time", &TThis::ExpireAfterSuccessfulUpdateTime)
+ .Default(TMasterReadOptions{}.ExpireAfterSuccessfulUpdateTime);
+
+ registrar.BaseClassParameter("expire_after_failed_update_time", &TThis::ExpireAfterFailedUpdateTime)
+ .Default(TMasterReadOptions{}.ExpireAfterFailedUpdateTime);
+
+ registrar.BaseClassParameter("cache_sticky_group_size", &TThis::CacheStickyGroupSize)
+ .Default(TMasterReadOptions{}.CacheStickyGroupSize);
+
+ registrar.BaseClassParameter("enable_client_cache_stickiness", &TThis::EnableClientCacheStickiness)
+ .Default(TMasterReadOptions{}.EnableClientCacheStickiness);
+
+ registrar.BaseClassParameter("success_staleness_bound", &TThis::SuccessStalenessBound)
+ .Default(TMasterReadOptions{}.SuccessStalenessBound);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TPrerequisiteRevisionConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("path", &TThis::Path);
+ registrar.Parameter("revision", &TThis::Revision);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/client_common.h b/yt/yt/client/api/client_common.h
new file mode 100644
index 0000000000..730fc63ce3
--- /dev/null
+++ b/yt/yt/client/api/client_common.h
@@ -0,0 +1,204 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/hydra/public.h>
+
+#include <yt/yt/client/misc/workload.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TMutatingOptions
+{
+ NRpc::TMutationId MutationId;
+ bool Retry = false;
+
+ NRpc::TMutationId GetOrGenerateMutationId() const;
+};
+
+struct TTimeoutOptions
+{
+ std::optional<TDuration> Timeout;
+};
+
+struct TMultiplexingBandOptions
+{
+ NRpc::EMultiplexingBand MultiplexingBand = NRpc::EMultiplexingBand::Default;
+};
+
+struct TSuppressableAccessTrackingOptions
+{
+ bool SuppressAccessTracking = false;
+ bool SuppressModificationTracking = false;
+ bool SuppressExpirationTimeoutRenewal = false;
+};
+
+struct TUserWorkloadDescriptor
+{
+ EUserWorkloadCategory Category = EUserWorkloadCategory::Interactive;
+ int Band = 0;
+
+ operator TWorkloadDescriptor() const;
+};
+
+void Serialize(const TUserWorkloadDescriptor& workloadDescriptor, NYson::IYsonConsumer* consumer);
+void Deserialize(TUserWorkloadDescriptor& workloadDescriptor, NYTree::INodePtr node);
+void Deserialize(TUserWorkloadDescriptor& workloadDescriptor, NYson::TYsonPullParserCursor* cursor);
+
+struct TTransactionalOptions
+{
+ //! Ignored when queried via transaction.
+ NObjectClient::TTransactionId TransactionId;
+ bool Ping = false;
+ bool PingAncestors = false;
+ //! For internal use only.
+ //! Setting it to |true| may result in loss of consistency.
+ bool SuppressTransactionCoordinatorSync = false;
+ //! For internal use only.
+ //! Setting it to |true| may result in loss of consistency .
+ bool SuppressUpstreamSync = false;
+};
+
+struct TMasterReadOptions
+{
+ EMasterChannelKind ReadFrom = EMasterChannelKind::Follower;
+ bool DisablePerUserCache = false;
+ TDuration ExpireAfterSuccessfulUpdateTime = TDuration::Seconds(15);
+ TDuration ExpireAfterFailedUpdateTime = TDuration::Seconds(15);
+ std::optional<int> CacheStickyGroupSize;
+ bool EnableClientCacheStickiness = false;
+
+ // When staleness bound is non-zero, master cache is allowed to
+ // return successful expired response, with staleness not exceeding the bound.
+ // This allows non-blocking master cache responses, with async on-demand updates.
+ TDuration SuccessStalenessBound;
+};
+
+struct TSerializableMasterReadOptions
+ : public TMasterReadOptions
+ , public NYTree::TYsonStruct
+{
+ REGISTER_YSON_STRUCT(TSerializableMasterReadOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TSerializableMasterReadOptions)
+
+struct TPrerequisiteRevisionConfig
+ : public NYTree::TYsonStruct
+{
+ NYTree::TYPath Path;
+ NHydra::TRevision Revision;
+
+ REGISTER_YSON_STRUCT(TPrerequisiteRevisionConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TPrerequisiteRevisionConfig)
+
+struct TPrerequisiteOptions
+{
+ std::vector<NTransactionClient::TTransactionId> PrerequisiteTransactionIds;
+ std::vector<TPrerequisiteRevisionConfigPtr> PrerequisiteRevisions;
+};
+
+struct TSyncReplicaCacheOptions
+{
+ std::optional<TDuration> CachedSyncReplicasTimeout;
+};
+
+struct TTabletReadOptionsBase
+ : public TSyncReplicaCacheOptions
+{
+ NHydra::EPeerKind ReadFrom = NHydra::EPeerKind::Leader;
+ std::optional<TDuration> RpcHedgingDelay;
+
+ //! Ignored when queried via transaction.
+ NTransactionClient::TTimestamp Timestamp = NTransactionClient::SyncLastCommittedTimestamp;
+
+ NTransactionClient::TTimestamp RetentionTimestamp = NTransactionClient::NullTimestamp;
+
+ EReplicaConsistency ReplicaConsistency = EReplicaConsistency::None;
+};
+
+struct TTabletReadOptions
+ : public TTimeoutOptions
+ , public TTabletReadOptionsBase
+{ };
+
+struct TSelectRowsOptionsBase
+ : public TTabletReadOptions
+ , public TSuppressableAccessTrackingOptions
+{
+ //! Limits range expanding.
+ ui64 RangeExpansionLimit = 200000;
+ //! Limits maximum parallel subqueries.
+ int MaxSubqueries = std::numeric_limits<int>::max();
+ //! Path in Cypress with UDFs.
+ std::optional<TString> UdfRegistryPath;
+ //! If |true| then logging is more verbose.
+ bool VerboseLogging = false;
+ // COMPAT(lukyan)
+ //! Use fixed and rewritten range inference.
+ bool NewRangeInference = false;
+};
+
+struct TSelectRowsOptions
+ : public TSelectRowsOptionsBase
+{
+ //! If null then connection defaults are used.
+ std::optional<i64> InputRowLimit;
+ //! If null then connection defaults are used.
+ std::optional<i64> OutputRowLimit;
+ //! Allow queries without any condition on key columns.
+ bool AllowFullScan = true;
+ //! Allow queries with join condition which implies foreign query with IN operator.
+ bool AllowJoinWithoutIndex = false;
+ //! Execution pool.
+ std::optional<TString> ExecutionPool;
+ //! If |true| then incomplete result would lead to a failure.
+ bool FailOnIncompleteResult = true;
+ //! Enables generated code caching.
+ bool EnableCodeCache = true;
+ //! Used to prioritize requests.
+ TUserWorkloadDescriptor WorkloadDescriptor;
+ //! Memory limit per execution node.
+ size_t MemoryLimitPerNode = std::numeric_limits<size_t>::max();
+ //! Info on detailed profiling.
+ TDetailedProfilingInfoPtr DetailedProfilingInfo;
+ //! YSON map with placeholder values for parameterized queries.
+ NYson::TYsonString PlaceholderValues;
+ //! Expected schemas for tables in a query (used for replica fallback in replicated tables).
+ using TExpectedTableSchemas = THashMap<NYPath::TYPath, NTableClient::TTableSchemaPtr>;
+ TExpectedTableSchemas ExpectedTableSchemas;
+};
+
+struct TFallbackReplicaOptions
+{
+ NTableClient::TTableSchemaPtr FallbackTableSchema;
+ NTabletClient::TTableReplicaId FallbackReplicaId;
+};
+
+struct TCreateObjectOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{
+ bool IgnoreExisting = false;
+ bool Sync = true;
+ NYTree::IAttributeDictionaryPtr Attributes;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/config.cpp b/yt/yt/client/api/config.cpp
new file mode 100644
index 0000000000..d8cdabecd3
--- /dev/null
+++ b/yt/yt/client/api/config.cpp
@@ -0,0 +1,190 @@
+#include "config.h"
+
+#include <yt/yt/core/misc/backoff_strategy_config.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TTableMountCacheConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("on_error_retry_count", &TThis::OnErrorRetryCount)
+ .GreaterThanOrEqual(0)
+ .Default(5);
+ registrar.Parameter("on_error_retry_slack_period", &TThis::OnErrorSlackPeriod)
+ .GreaterThan(TDuration::Zero())
+ .Default(TDuration::Seconds(1));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TConnectionConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("connection_type", &TThis::ConnectionType)
+ .Default(EConnectionType::Native);
+ registrar.Parameter("cluster_name", &TThis::ClusterName)
+ .Default();
+ registrar.Parameter("table_mount_cache", &TThis::TableMountCache)
+ .DefaultNew();
+ registrar.Parameter("replication_card_cache", &TThis::ReplicationCardCache)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TConnectionDynamicConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("table_mount_cache", &TThis::TableMountCache)
+ .DefaultNew();
+ registrar.Parameter("tablet_write_backoff", &TThis::TabletWriteBackoff)
+ .DefaultNew();
+
+ registrar.Preprocessor([] (TThis* config) {
+ // TODO(gritukan): Enable tablet retries by default one day.
+ config->TabletWriteBackoff = New<TSerializableExponentialBackoffOptions>();
+ config->TabletWriteBackoff->RetryCount = 1;
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TPersistentQueuePollerConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_prefetch_row_count", &TThis::MaxPrefetchRowCount)
+ .GreaterThan(0)
+ .Default(1024);
+ registrar.Parameter("max_prefetch_data_weight", &TThis::MaxPrefetchDataWeight)
+ .GreaterThan(0)
+ .Default((i64) 16 * 1024 * 1024);
+ registrar.Parameter("max_rows_per_fetch", &TThis::MaxRowsPerFetch)
+ .GreaterThan(0)
+ .Default(512);
+ registrar.Parameter("max_rows_per_poll", &TThis::MaxRowsPerPoll)
+ .GreaterThan(0)
+ .Default(1);
+ registrar.Parameter("max_fetched_untrimmed_row_count", &TThis::MaxFetchedUntrimmedRowCount)
+ .GreaterThan(0)
+ .Default(40000);
+ registrar.Parameter("untrimmed_data_rows_low", &TThis::UntrimmedDataRowsLow)
+ .Default(0);
+ registrar.Parameter("untrimmed_data_rows_high", &TThis::UntrimmedDataRowsHigh)
+ .Default(std::numeric_limits<i64>::max());
+ registrar.Parameter("data_poll_period", &TThis::DataPollPeriod)
+ .Default(TDuration::Seconds(1));
+ registrar.Parameter("state_trim_period", &TThis::StateTrimPeriod)
+ .Default(TDuration::Seconds(15));
+ registrar.Parameter("backoff_time", &TThis::BackoffTime)
+ .Default(TDuration::Seconds(5));
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->UntrimmedDataRowsLow > config->UntrimmedDataRowsHigh) {
+ THROW_ERROR_EXCEPTION("\"untrimmed_data_rows_low\" must not exceed \"untrimmed_data_rows_high\"");
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TJournalChunkWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_batch_row_count", &TThis::MaxBatchRowCount)
+ .Default(256);
+ registrar.Parameter("max_batch_data_size", &TThis::MaxBatchDataSize)
+ .Default(16_MB);
+ registrar.Parameter("max_batch_delay", &TThis::MaxBatchDelay)
+ .Default(TDuration::MilliSeconds(5));
+
+ registrar.Parameter("max_flush_row_count", &TThis::MaxFlushRowCount)
+ .Default(100'000);
+ registrar.Parameter("max_flush_data_size", &TThis::MaxFlushDataSize)
+ .Default(100_MB);
+
+ registrar.Parameter("prefer_local_host", &TThis::PreferLocalHost)
+ .Default(true);
+
+ registrar.Parameter("node_rpc_timeout", &TThis::NodeRpcTimeout)
+ .Default(TDuration::Seconds(15));
+ registrar.Parameter("node_ping_period", &TThis::NodePingPeriod)
+ .Default(TDuration::Seconds(15));
+ registrar.Parameter("node_ban_timeout", &TThis::NodeBanTimeout)
+ .Default(TDuration::Seconds(60));
+
+ registrar.Parameter("node_channel", &TThis::NodeChannel)
+ .DefaultNew();
+
+ registrar.Parameter("replica_failure_probability", &TThis::ReplicaFailureProbability)
+ .Default(0.0)
+ .InRange(0.0, 1.0);
+ registrar.Parameter("replica_row_limits", &TThis::ReplicaRowLimits)
+ .Default();
+ registrar.Parameter("replica_fake_timeout_delay", &TThis::ReplicaFakeTimeoutDelay)
+ .Default();
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->MaxBatchRowCount > config->MaxFlushRowCount) {
+ THROW_ERROR_EXCEPTION("\"max_batch_row_count\" cannot be greater than \"max_flush_row_count\"")
+ << TErrorAttribute("max_batch_row_count", config->MaxBatchRowCount)
+ << TErrorAttribute("max_flush_row_count", config->MaxFlushRowCount);
+ }
+ if (config->MaxBatchDataSize > config->MaxFlushDataSize) {
+ THROW_ERROR_EXCEPTION("\"max_batch_data_size\" cannot be greater than \"max_flush_data_size\"")
+ << TErrorAttribute("max_batch_data_size", config->MaxBatchDataSize)
+ << TErrorAttribute("max_flush_data_size", config->MaxFlushDataSize);
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TJournalWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_chunk_row_count", &TThis::MaxChunkRowCount)
+ .GreaterThan(0)
+ .Default(1'000'000);
+ registrar.Parameter("max_chunk_data_size", &TThis::MaxChunkDataSize)
+ .GreaterThan(0)
+ .Default(10_GB);
+ registrar.Parameter("max_chunk_session_duration", &TThis::MaxChunkSessionDuration)
+ .Default(TDuration::Hours(60));
+
+ registrar.Parameter("open_session_backoff_time", &TThis::OpenSessionBackoffTime)
+ .Default(TDuration::Seconds(10));
+
+ registrar.Parameter("prerequisite_transaction_probe_period", &TThis::PrerequisiteTransactionProbePeriod)
+ .Default(TDuration::Seconds(60));
+
+ registrar.Parameter("dont_close", &TThis::DontClose)
+ .Default(false);
+ registrar.Parameter("dont_seal", &TThis::DontSeal)
+ .Default(false);
+ registrar.Parameter("dont_preallocate", &TThis::DontPreallocate)
+ .Default(false);
+ registrar.Parameter("open_delay", &TThis::OpenDelay)
+ .Default();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TJournalChunkWriterOptions::Register(TRegistrar registrar)
+{
+ registrar.Parameter("replication_factor", &TThis::ReplicationFactor)
+ .Default(3);
+ registrar.Parameter("erasure_codec", &TThis::ErasureCodec)
+ .Default(NErasure::ECodec::None);
+
+ registrar.Parameter("read_quorum", &TThis::ReadQuorum)
+ .Default(2);
+ registrar.Parameter("write_quorum", &TThis::WriteQuorum)
+ .Default(2);
+
+ registrar.Parameter("replica_lag_limit", &TThis::ReplicaLagLimit)
+ .Default(NJournalClient::DefaultReplicaLagLimit);
+
+ registrar.Parameter("enable_multiplexing", &TThis::EnableMultiplexing)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/config.h b/yt/yt/client/api/config.h
new file mode 100644
index 0000000000..5bc5506299
--- /dev/null
+++ b/yt/yt/client/api/config.h
@@ -0,0 +1,256 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/journal_client/config.h>
+
+#include <yt/yt/client/tablet_client/config.h>
+
+#include <yt/yt/client/chaos_client/config.h>
+
+#include <yt/yt/client/chunk_client/config.h>
+
+#include <yt/yt/client/file_client/config.h>
+
+#include <yt/yt/library/erasure/public.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <yt/yt/core/misc/backoff_strategy_api.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EConnectionType,
+ (Native)
+ (Rpc)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableMountCacheConfig
+ : public NTabletClient::TTableMountCacheConfig
+{
+public:
+ int OnErrorRetryCount;
+ TDuration OnErrorSlackPeriod;
+
+ REGISTER_YSON_STRUCT(TTableMountCacheConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableMountCacheConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TConnectionConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ EConnectionType ConnectionType;
+ std::optional<TString> ClusterName;
+ TTableMountCacheConfigPtr TableMountCache;
+ NChaosClient::TReplicationCardCacheConfigPtr ReplicationCardCache;
+
+ REGISTER_YSON_STRUCT(TConnectionConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TConnectionConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TConnectionDynamicConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ NTabletClient::TTableMountCacheDynamicConfigPtr TableMountCache;
+
+ TSerializableExponentialBackoffOptionsPtr TabletWriteBackoff;
+
+ REGISTER_YSON_STRUCT(TConnectionDynamicConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TConnectionDynamicConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPersistentQueuePollerConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ //! Try to keep at most this many prefetched rows in memory. This limit is approximate.
+ i64 MaxPrefetchRowCount;
+
+ //! Try to keep at most this much prefetched data in memory. This limit is approximate.
+ i64 MaxPrefetchDataWeight;
+
+ //! The limit for the number of rows to be requested in a single background fetch request.
+ i64 MaxRowsPerFetch;
+
+ //! The limit for the number of rows to be returned by #TPersistentQueuePoller::Poll call.
+ i64 MaxRowsPerPoll;
+
+ //! The limit on maximum number of consumed but not yet trimmed row indexes. No new rows are fetched when the limit is reached.
+ i64 MaxFetchedUntrimmedRowCount;
+
+ //! When trimming data table, keep the number of consumed but untrimmed rows about this level.
+ i64 UntrimmedDataRowsLow;
+
+ //! When more than this many of consumed but untrimmed rows appear in data table, trim the front ones
+ //! in accordance to #UntrimmedDataRowsLow.
+ i64 UntrimmedDataRowsHigh;
+
+ //! How often the data table is to be polled.
+ TDuration DataPollPeriod;
+
+ //! How often the state table is to be trimmed.
+ TDuration StateTrimPeriod;
+
+ //! For how long to backoff when a state conflict is detected.
+ TDuration BackoffTime;
+
+ REGISTER_YSON_STRUCT(TPersistentQueuePollerConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TPersistentQueuePollerConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFileReaderConfig
+ : public virtual NChunkClient::TMultiChunkReaderConfig
+{
+ REGISTER_YSON_STRUCT(TFileReaderConfig);
+
+ static void Register(TRegistrar)
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TFileReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFileWriterConfig
+ : public NChunkClient::TMultiChunkWriterConfig
+ , public NFileClient::TFileChunkWriterConfig
+{
+ REGISTER_YSON_STRUCT(TFileWriterConfig);
+
+ static void Register(TRegistrar)
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TFileWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TJournalReaderConfig
+ : public NJournalClient::TChunkReaderConfig
+ , public TWorkloadConfig
+{
+ REGISTER_YSON_STRUCT(TJournalReaderConfig);
+
+ static void Register(TRegistrar)
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TJournalReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TJournalChunkWriterConfig
+ : public virtual TWorkloadConfig
+{
+public:
+ int MaxBatchRowCount;
+ i64 MaxBatchDataSize;
+ TDuration MaxBatchDelay;
+
+ int MaxFlushRowCount;
+ i64 MaxFlushDataSize;
+
+ bool PreferLocalHost;
+
+ TDuration NodeRpcTimeout;
+ TDuration NodePingPeriod;
+ TDuration NodeBanTimeout;
+
+ NRpc::TRetryingChannelConfigPtr NodeChannel;
+
+ // For testing purposes only.
+ double ReplicaFailureProbability;
+
+ //! After writing #ReplicaRowLimits[index] rows to replica #index
+ //! request will fail with timeout after #ReplicaFakeTimeoutDelay
+ //! but rows will be actually written.
+ std::optional<std::vector<int>> ReplicaRowLimits;
+ TDuration ReplicaFakeTimeoutDelay;
+
+ REGISTER_YSON_STRUCT(TJournalChunkWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TJournalChunkWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TJournalWriterConfig
+ : public TJournalChunkWriterConfig
+{
+public:
+ int MaxChunkRowCount;
+ i64 MaxChunkDataSize;
+ TDuration MaxChunkSessionDuration;
+
+ TDuration OpenSessionBackoffTime;
+
+ TDuration PrerequisiteTransactionProbePeriod;
+
+ // For testing purposes only.
+ bool DontClose;
+ bool DontSeal;
+ bool DontPreallocate;
+
+ std::optional<TDuration> OpenDelay;
+
+ REGISTER_YSON_STRUCT(TJournalWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TJournalWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TJournalChunkWriterOptions
+ : public NYTree::TYsonStruct
+{
+public:
+ int ReplicationFactor;
+ NErasure::ECodec ErasureCodec;
+
+ int ReadQuorum;
+ int WriteQuorum;
+
+ int ReplicaLagLimit;
+
+ bool EnableMultiplexing;
+
+ REGISTER_YSON_STRUCT(TJournalChunkWriterOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TJournalChunkWriterOptions)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/connection.h b/yt/yt/client/api/connection.h
new file mode 100644
index 0000000000..8c59c70319
--- /dev/null
+++ b/yt/yt/client/api/connection.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/chunk_client/public.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <yt/yt/client/security_client/public.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/client/hive/public.h>
+
+#include <yt/yt/core/actions/callback.h>
+
+#include <yt/yt/core/rpc/authentication_identity.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TConnectionOptions
+{
+ //! If non-null, suppresses creation of a per-connection thread pool.
+ IInvokerPtr ConnectionInvoker;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTransactionParticipantOptions
+{
+ TDuration RpcTimeout = TDuration::Seconds(5);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Represents an established connection with a YT cluster.
+/*
+ * IConnection instance caches most of the stuff needed for fast interaction
+ * with the cluster (e.g. connection channels, mount info etc).
+ *
+ * Thread affinity: any
+ */
+struct IConnection
+ : public virtual TRefCounted
+{
+ virtual TClusterTag GetClusterTag() const = 0;
+ virtual const TString& GetLoggingTag() const = 0;
+ virtual const TString& GetClusterId() const = 0;
+ virtual const std::optional<TString>& GetClusterName() const = 0;
+ virtual IInvokerPtr GetInvoker() = 0;
+
+ // TODO(gritukan): Fix alien transaction creation for RPC proxy connection
+ // and eliminate this method.
+ virtual bool IsSameCluster(const IConnectionPtr& other) const = 0;
+
+ virtual IClientPtr CreateClient(const TClientOptions& options = {}) = 0;
+ virtual NHiveClient::ITransactionParticipantPtr CreateTransactionParticipant(
+ NHiveClient::TCellId cellId,
+ const TTransactionParticipantOptions& options = TTransactionParticipantOptions()) = 0;
+
+ virtual void ClearMetadataCaches() = 0;
+ virtual void Terminate() = 0;
+
+ //! Returns a YSON-serialized connection config.
+ virtual NYson::TYsonString GetConfigYson() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IConnection)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/cypress_client.h b/yt/yt/client/api/cypress_client.h
new file mode 100644
index 0000000000..9b7c5b1435
--- /dev/null
+++ b/yt/yt/client/api/cypress_client.h
@@ -0,0 +1,253 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/core/ytree/attribute_filter.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TGetNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMasterReadOptions
+ , public TSuppressableAccessTrackingOptions
+ , public TPrerequisiteOptions
+{
+ // NB(eshcherbin): Used in profiling Orchid.
+ NYTree::IAttributeDictionaryPtr Options;
+ NYTree::TAttributeFilter Attributes;
+ std::optional<i64> MaxSize;
+};
+
+struct TSetNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+ , public TSuppressableAccessTrackingOptions
+ , public TPrerequisiteOptions
+{
+ bool Recursive = false;
+ bool Force = false;
+};
+
+struct TMultisetAttributesNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+ , public TSuppressableAccessTrackingOptions
+ , public TPrerequisiteOptions
+{ };
+
+struct TRemoveNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{
+ bool Recursive = true;
+ bool Force = false;
+};
+
+struct TListNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMasterReadOptions
+ , public TSuppressableAccessTrackingOptions
+ , public TPrerequisiteOptions
+{
+ NYTree::TAttributeFilter Attributes;
+ std::optional<i64> MaxSize;
+};
+
+struct TCreateNodeOptions
+ : public TCreateObjectOptions
+ , public TTransactionalOptions
+{
+ bool Recursive = false;
+ bool LockExisting = false;
+ bool Force = false;
+ bool IgnoreTypeMismatch = false;
+};
+
+struct TLockNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{
+ bool Waitable = false;
+ std::optional<TString> ChildKey;
+ std::optional<TString> AttributeKey;
+};
+
+struct TLockNodeResult
+{
+ NCypressClient::TLockId LockId;
+ NCypressClient::TNodeId NodeId;
+ NHydra::TRevision Revision = NHydra::NullRevision;
+};
+
+struct TUnlockNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{ };
+
+struct TCopyNodeOptionsBase
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{
+ bool Recursive = false;
+ bool Force = false;
+ bool PreserveAccount = false;
+ bool PreserveCreationTime = false;
+ bool PreserveModificationTime = false;
+ bool PreserveOwner = false;
+ bool PreserveExpirationTime = false;
+ bool PreserveExpirationTimeout = false;
+ bool PreserveAcl = false;
+ bool PessimisticQuotaCheck = true;
+};
+
+struct TCopyNodeOptions
+ : public TCopyNodeOptionsBase
+{
+ bool IgnoreExisting = false;
+ bool LockExisting = false;
+};
+
+struct TMoveNodeOptions
+ : public TCopyNodeOptionsBase
+{
+ TMoveNodeOptions()
+ {
+ // COMPAT(babenko): YT-11903, consider dropping this override
+ PreserveCreationTime = true;
+ }
+};
+
+struct TLinkNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{
+ //! Attributes of a newly created link node.
+ NYTree::IAttributeDictionaryPtr Attributes;
+ bool Recursive = false;
+ bool IgnoreExisting = false;
+ bool LockExisting = false;
+ bool Force = false;
+};
+
+struct TConcatenateNodesOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+{
+ NChunkClient::TFetcherConfigPtr ChunkMetaFetcherConfig;
+
+ bool UniqualizeChunks = false;
+};
+
+struct TNodeExistsOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+ , public TTransactionalOptions
+ , public TSuppressableAccessTrackingOptions
+ , public TPrerequisiteOptions
+{ };
+
+struct TExternalizeNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+{ };
+
+struct TInternalizeNodeOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ICypressClientBase
+{
+ virtual TFuture<NYson::TYsonString> GetNode(
+ const NYPath::TYPath& path,
+ const TGetNodeOptions& options = {}) = 0;
+
+ virtual TFuture<void> SetNode(
+ const NYPath::TYPath& path,
+ const NYson::TYsonString& value,
+ const TSetNodeOptions& options = {}) = 0;
+
+ virtual TFuture<void> MultisetAttributesNode(
+ const NYPath::TYPath& path,
+ const NYTree::IMapNodePtr& attributes,
+ const TMultisetAttributesNodeOptions& options = {}) = 0;
+
+ virtual TFuture<void> RemoveNode(
+ const NYPath::TYPath& path,
+ const TRemoveNodeOptions& options = {}) = 0;
+
+ virtual TFuture<NYson::TYsonString> ListNode(
+ const NYPath::TYPath& path,
+ const TListNodeOptions& options = {}) = 0;
+
+ virtual TFuture<NCypressClient::TNodeId> CreateNode(
+ const NYPath::TYPath& path,
+ NObjectClient::EObjectType type,
+ const TCreateNodeOptions& options = {}) = 0;
+
+ virtual TFuture<TLockNodeResult> LockNode(
+ const NYPath::TYPath& path,
+ NCypressClient::ELockMode mode,
+ const TLockNodeOptions& options = {}) = 0;
+
+ virtual TFuture<void> UnlockNode(
+ const NYPath::TYPath& path,
+ const TUnlockNodeOptions& options = {}) = 0;
+
+ virtual TFuture<NCypressClient::TNodeId> CopyNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TCopyNodeOptions& options = {}) = 0;
+
+ virtual TFuture<NCypressClient::TNodeId> MoveNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TMoveNodeOptions& options = {}) = 0;
+
+ virtual TFuture<NCypressClient::TNodeId> LinkNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TLinkNodeOptions& options = {}) = 0;
+
+ virtual TFuture<void> ConcatenateNodes(
+ const std::vector<NYPath::TRichYPath>& srcPaths,
+ const NYPath::TRichYPath& dstPath,
+ const TConcatenateNodesOptions& options = {}) = 0;
+
+ virtual TFuture<bool> NodeExists(
+ const NYPath::TYPath& path,
+ const TNodeExistsOptions& options = {}) = 0;
+
+ virtual TFuture<void> ExternalizeNode(
+ const NYPath::TYPath& path,
+ NObjectClient::TCellTag cellTag,
+ const TExternalizeNodeOptions& options = {}) = 0;
+
+ virtual TFuture<void> InternalizeNode(
+ const NYPath::TYPath& path,
+ const TInternalizeNodeOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/delegating_client.cpp b/yt/yt/client/api/delegating_client.cpp
new file mode 100644
index 0000000000..5757389e34
--- /dev/null
+++ b/yt/yt/client/api/delegating_client.cpp
@@ -0,0 +1,1000 @@
+#include "delegating_client.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDelegatingClient::TDelegatingClient(IClientPtr underlying)
+ : Underlying_(std::move(underlying))
+{ }
+
+IConnectionPtr TDelegatingClient::GetConnection()
+{
+ return Underlying_->GetConnection();
+}
+
+std::optional<TStringBuf> TDelegatingClient::GetClusterName(bool fetchIfNull)
+{
+ return Underlying_->GetClusterName(fetchIfNull);
+}
+
+TFuture<ITransactionPtr> TDelegatingClient::StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const TTransactionStartOptions& options)
+{
+ return Underlying_->StartTransaction(type, options);
+}
+
+TFuture<IUnversionedRowsetPtr> TDelegatingClient::LookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TLookupRowsOptions& options)
+{
+ return Underlying_->LookupRows(path, std::move(nameTable), keys, options);
+}
+
+TFuture<IVersionedRowsetPtr> TDelegatingClient::VersionedLookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TVersionedLookupRowsOptions& options)
+{
+ return Underlying_->VersionedLookupRows(path, std::move(nameTable), keys, options);
+}
+
+TFuture<std::vector<IUnversionedRowsetPtr>> TDelegatingClient::MultiLookup(
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options)
+{
+ return Underlying_->MultiLookup(subrequests, options);
+}
+
+TFuture<TSelectRowsResult> TDelegatingClient::SelectRows(
+ const TString& query,
+ const TSelectRowsOptions& options)
+{
+ return Underlying_->SelectRows(query, options);
+}
+
+TFuture<NYson::TYsonString> TDelegatingClient::ExplainQuery(
+ const TString& query,
+ const TExplainQueryOptions& options)
+{
+ return Underlying_->ExplainQuery(query, options);
+}
+
+TFuture<TPullRowsResult> TDelegatingClient::PullRows(
+ const NYPath::TYPath& path,
+ const TPullRowsOptions& options)
+{
+ return Underlying_->PullRows(path, options);
+}
+
+TFuture<ITableReaderPtr> TDelegatingClient::CreateTableReader(
+ const NYPath::TRichYPath& path,
+ const TTableReaderOptions& options)
+{
+ return Underlying_->CreateTableReader(path, options);
+}
+
+TFuture<ITableWriterPtr> TDelegatingClient::CreateTableWriter(
+ const NYPath::TRichYPath& path,
+ const TTableWriterOptions& options)
+{
+ return Underlying_->CreateTableWriter(path, options);
+}
+
+TFuture<NQueueClient::IQueueRowsetPtr> TDelegatingClient::PullQueue(
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullQueueOptions& options)
+{
+ return Underlying_->PullQueue(queuePath, offset, partitionIndex, rowBatchReadOptions, options);
+}
+
+TFuture<NQueueClient::IQueueRowsetPtr> TDelegatingClient::PullConsumer(
+ const NYPath::TRichYPath& consumerPath,
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullConsumerOptions& options)
+{
+ return Underlying_->PullConsumer(consumerPath, queuePath, offset, partitionIndex, rowBatchReadOptions, options);
+}
+
+TFuture<void> TDelegatingClient::RegisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ bool vital,
+ const TRegisterQueueConsumerOptions& options)
+{
+ return Underlying_->RegisterQueueConsumer(queuePath, consumerPath, vital, options);
+}
+
+TFuture<void> TDelegatingClient::UnregisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ const TUnregisterQueueConsumerOptions& options)
+{
+ return Underlying_->UnregisterQueueConsumer(queuePath, consumerPath, options);
+}
+
+TFuture<std::vector<TListQueueConsumerRegistrationsResult>> TDelegatingClient::ListQueueConsumerRegistrations(
+ const std::optional<NYPath::TRichYPath>& queuePath,
+ const std::optional<NYPath::TRichYPath>& consumerPath,
+ const TListQueueConsumerRegistrationsOptions& options)
+{
+ return Underlying_->ListQueueConsumerRegistrations(queuePath, consumerPath, options);
+}
+
+TFuture<NYson::TYsonString> TDelegatingClient::GetNode(
+ const NYPath::TYPath& path,
+ const TGetNodeOptions& options)
+{
+ return Underlying_->GetNode(path, options);
+}
+
+TFuture<void> TDelegatingClient::SetNode(
+ const NYPath::TYPath& path,
+ const NYson::TYsonString& value,
+ const TSetNodeOptions& options)
+{
+ return Underlying_->SetNode(path, value, options);
+}
+
+TFuture<void> TDelegatingClient::MultisetAttributesNode(
+ const NYPath::TYPath& path,
+ const NYTree::IMapNodePtr& attributes,
+ const TMultisetAttributesNodeOptions& options)
+{
+ return Underlying_->MultisetAttributesNode(path, attributes, options);
+}
+
+TFuture<void> TDelegatingClient::RemoveNode(
+ const NYPath::TYPath& path,
+ const TRemoveNodeOptions& options)
+{
+ return Underlying_->RemoveNode(path, options);
+}
+
+TFuture<NYson::TYsonString> TDelegatingClient::ListNode(
+ const NYPath::TYPath& path,
+ const TListNodeOptions& options)
+{
+ return Underlying_->ListNode(path, options);
+}
+
+TFuture<NCypressClient::TNodeId> TDelegatingClient::CreateNode(
+ const NYPath::TYPath& path,
+ NObjectClient::EObjectType type,
+ const TCreateNodeOptions& options)
+{
+ return Underlying_->CreateNode(path, type, options);
+}
+
+TFuture<TLockNodeResult> TDelegatingClient::LockNode(
+ const NYPath::TYPath& path,
+ NCypressClient::ELockMode mode,
+ const TLockNodeOptions& options)
+{
+ return Underlying_->LockNode(path, mode, options);
+}
+
+TFuture<void> TDelegatingClient::UnlockNode(
+ const NYPath::TYPath& path,
+ const TUnlockNodeOptions& options)
+{
+ return Underlying_->UnlockNode(path, options);
+}
+
+TFuture<NCypressClient::TNodeId> TDelegatingClient::CopyNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TCopyNodeOptions& options)
+{
+ return Underlying_->CopyNode(srcPath, dstPath, options);
+}
+
+TFuture<NCypressClient::TNodeId> TDelegatingClient::MoveNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TMoveNodeOptions& options)
+{
+ return Underlying_->MoveNode(srcPath, dstPath, options);
+}
+
+TFuture<NCypressClient::TNodeId> TDelegatingClient::LinkNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TLinkNodeOptions& options)
+{
+ return Underlying_->LinkNode(srcPath, dstPath, options);
+}
+
+TFuture<void> TDelegatingClient::ConcatenateNodes(
+ const std::vector<NYPath::TRichYPath>& srcPaths,
+ const NYPath::TRichYPath& dstPath,
+ const TConcatenateNodesOptions& options)
+{
+ return Underlying_->ConcatenateNodes(srcPaths, dstPath, options);
+}
+
+TFuture<bool> TDelegatingClient::NodeExists(
+ const NYPath::TYPath& path,
+ const TNodeExistsOptions& options)
+{
+ return Underlying_->NodeExists(path, options);
+}
+
+TFuture<void> TDelegatingClient::ExternalizeNode(
+ const NYPath::TYPath& path,
+ NObjectClient::TCellTag cellTag,
+ const TExternalizeNodeOptions& options)
+{
+ return Underlying_->ExternalizeNode(path, cellTag, options);
+}
+
+TFuture<void> TDelegatingClient::InternalizeNode(
+ const NYPath::TYPath& path,
+ const TInternalizeNodeOptions& options)
+{
+ return Underlying_->InternalizeNode(path, options);
+}
+
+TFuture<NObjectClient::TObjectId> TDelegatingClient::CreateObject(
+ NObjectClient::EObjectType type,
+ const TCreateObjectOptions& options)
+{
+ return Underlying_->CreateObject(type, options);
+}
+
+TFuture<IFileReaderPtr> TDelegatingClient::CreateFileReader(
+ const NYPath::TYPath& path,
+ const TFileReaderOptions& options)
+{
+ return Underlying_->CreateFileReader(path, options);
+}
+
+IFileWriterPtr TDelegatingClient::CreateFileWriter(
+ const NYPath::TRichYPath& path,
+ const TFileWriterOptions& options)
+{
+ return Underlying_->CreateFileWriter(path, options);
+}
+
+IJournalReaderPtr TDelegatingClient::CreateJournalReader(
+ const NYPath::TYPath& path,
+ const TJournalReaderOptions& options)
+{
+ return Underlying_->CreateJournalReader(path, options);
+}
+
+IJournalWriterPtr TDelegatingClient::CreateJournalWriter(
+ const NYPath::TYPath& path,
+ const TJournalWriterOptions& options)
+{
+ return Underlying_->CreateJournalWriter(path, options);
+}
+
+void TDelegatingClient::Terminate()
+{
+ return Underlying_->Terminate();
+}
+
+const NTabletClient::ITableMountCachePtr& TDelegatingClient::GetTableMountCache()
+{
+ return Underlying_->GetTableMountCache();
+}
+const NChaosClient::IReplicationCardCachePtr& TDelegatingClient::GetReplicationCardCache()
+{
+ return Underlying_->GetReplicationCardCache();
+}
+const NTransactionClient::ITimestampProviderPtr& TDelegatingClient::GetTimestampProvider() {
+ return Underlying_->GetTimestampProvider();
+}
+
+ITransactionPtr TDelegatingClient::AttachTransaction(
+ NTransactionClient::TTransactionId transactionId,
+ const TTransactionAttachOptions& options)
+{
+ return Underlying_->AttachTransaction(transactionId, options);
+}
+
+TFuture<void> TDelegatingClient::MountTable(
+ const NYPath::TYPath& path,
+ const TMountTableOptions& options)
+{
+ return Underlying_->MountTable(path, options);
+}
+
+TFuture<void> TDelegatingClient::UnmountTable(
+ const NYPath::TYPath& path,
+ const TUnmountTableOptions& options)
+{
+ return Underlying_->UnmountTable(path, options);
+}
+
+TFuture<void> TDelegatingClient::RemountTable(
+ const NYPath::TYPath& path,
+ const TRemountTableOptions& options)
+{
+ return Underlying_->RemountTable(path, options);
+}
+
+TFuture<void> TDelegatingClient::FreezeTable(
+ const NYPath::TYPath& path,
+ const TFreezeTableOptions& options)
+{
+ return Underlying_->FreezeTable(path, options);
+}
+
+TFuture<void> TDelegatingClient::UnfreezeTable(
+ const NYPath::TYPath& path,
+ const TUnfreezeTableOptions& options)
+{
+ return Underlying_->UnfreezeTable(path, options);
+}
+
+TFuture<void> TDelegatingClient::ReshardTable(
+ const NYPath::TYPath& path,
+ const std::vector<NTableClient::TLegacyOwningKey>& pivotKeys,
+ const TReshardTableOptions& options)
+{
+ return Underlying_->ReshardTable(path, pivotKeys, options);
+}
+
+TFuture<void> TDelegatingClient::ReshardTable(
+ const NYPath::TYPath& path,
+ int tabletCount,
+ const TReshardTableOptions& options)
+{
+ return Underlying_->ReshardTable(path, tabletCount, options);
+}
+
+TFuture<std::vector<NTabletClient::TTabletActionId>> TDelegatingClient::ReshardTableAutomatic(
+ const NYPath::TYPath& path,
+ const TReshardTableAutomaticOptions& options)
+{
+ return Underlying_->ReshardTableAutomatic(path, options);
+}
+
+TFuture<void> TDelegatingClient::TrimTable(
+ const NYPath::TYPath& path,
+ int tabletIndex,
+ i64 trimmedRowCount,
+ const TTrimTableOptions& options)
+{
+ return Underlying_->TrimTable(path, tabletIndex, trimmedRowCount, options);
+}
+
+TFuture<void> TDelegatingClient::AlterTable(
+ const NYPath::TYPath& path,
+ const TAlterTableOptions& options)
+{
+ return Underlying_->AlterTable(path, options);
+}
+
+TFuture<void> TDelegatingClient::AlterTableReplica(
+ NTabletClient::TTableReplicaId replicaId,
+ const TAlterTableReplicaOptions& options)
+{
+ return Underlying_->AlterTableReplica(replicaId, options);
+}
+
+TFuture<void> TDelegatingClient::AlterReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TAlterReplicationCardOptions& options)
+{
+ return Underlying_->AlterReplicationCard(replicationCardId, options);
+}
+
+TFuture<NYson::TYsonString> TDelegatingClient::GetTablePivotKeys(
+ const NYPath::TYPath& path,
+ const TGetTablePivotKeysOptions& options)
+{
+ return Underlying_->GetTablePivotKeys(path, options);
+}
+
+TFuture<void> TDelegatingClient::CreateTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TCreateTableBackupOptions& options)
+{
+ return Underlying_->CreateTableBackup(manifest, options);
+}
+
+TFuture<void> TDelegatingClient::RestoreTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TRestoreTableBackupOptions& options)
+{
+ return Underlying_->RestoreTableBackup(manifest, options);
+}
+
+TFuture<std::vector<NTabletClient::TTableReplicaId>> TDelegatingClient::GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const NTableClient::TNameTablePtr& nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TGetInSyncReplicasOptions& options)
+{
+ return Underlying_->GetInSyncReplicas(path, nameTable, keys, options);
+}
+
+TFuture<std::vector<NTabletClient::TTableReplicaId>> TDelegatingClient::GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const TGetInSyncReplicasOptions& options)
+{
+ return Underlying_->GetInSyncReplicas(path, options);
+}
+
+TFuture<std::vector<TTabletInfo>> TDelegatingClient::GetTabletInfos(
+ const NYPath::TYPath& path,
+ const std::vector<int>& tabletIndexes,
+ const TGetTabletInfosOptions& options)
+{
+ return Underlying_->GetTabletInfos(path, tabletIndexes, options);
+}
+
+TFuture<TGetTabletErrorsResult> TDelegatingClient::GetTabletErrors(
+ const NYPath::TYPath& path,
+ const TGetTabletErrorsOptions& options)
+{
+ return Underlying_->GetTabletErrors(path, options);
+}
+
+TFuture<std::vector<NTabletClient::TTabletActionId>> TDelegatingClient::BalanceTabletCells(
+ const TString& tabletCellBundle,
+ const std::vector<NYPath::TYPath>& movableTables,
+ const TBalanceTabletCellsOptions& options)
+{
+ return Underlying_->BalanceTabletCells(tabletCellBundle, movableTables, options);
+}
+
+TFuture<NChaosClient::TReplicationCardPtr> TDelegatingClient::GetReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TGetReplicationCardOptions& options)
+{
+ return Underlying_->GetReplicationCard(replicationCardId, options);
+}
+
+TFuture<void> TDelegatingClient::UpdateChaosTableReplicaProgress(
+ NChaosClient::TReplicaId replicaId,
+ const TUpdateChaosTableReplicaProgressOptions& options)
+{
+ return Underlying_->UpdateChaosTableReplicaProgress(replicaId, options);
+}
+
+TFuture<TSkynetSharePartsLocationsPtr> TDelegatingClient::LocateSkynetShare(
+ const NYPath::TRichYPath& path,
+ const TLocateSkynetShareOptions& options)
+{
+ return Underlying_->LocateSkynetShare(path, options);
+}
+
+TFuture<std::vector<NTableClient::TColumnarStatistics>> TDelegatingClient::GetColumnarStatistics(
+ const std::vector<NYPath::TRichYPath>& path,
+ const TGetColumnarStatisticsOptions& options)
+{
+ return Underlying_->GetColumnarStatistics(path, options);
+}
+
+TFuture<TMultiTablePartitions> TDelegatingClient::PartitionTables(
+ const std::vector<NYPath::TRichYPath>& paths,
+ const TPartitionTablesOptions& options)
+{
+ return Underlying_->PartitionTables(paths, options);
+}
+
+TFuture<void> TDelegatingClient::TruncateJournal(
+ const NYPath::TYPath& path,
+ i64 rowCount,
+ const TTruncateJournalOptions& options)
+{
+ return Underlying_->TruncateJournal(path, rowCount, options);
+}
+
+TFuture<TGetFileFromCacheResult> TDelegatingClient::GetFileFromCache(
+ const TString& md5,
+ const TGetFileFromCacheOptions& options)
+{
+ return Underlying_->GetFileFromCache(md5, options);
+}
+
+TFuture<TPutFileToCacheResult> TDelegatingClient::PutFileToCache(
+ const NYPath::TYPath& path,
+ const TString& expectedMD5,
+ const TPutFileToCacheOptions& options)
+{
+ return Underlying_->PutFileToCache(path, expectedMD5, options);
+}
+
+TFuture<void> TDelegatingClient::AddMember(
+ const TString& group,
+ const TString& member,
+ const TAddMemberOptions& options)
+{
+ return Underlying_->AddMember(group, member, options);
+}
+
+TFuture<void> TDelegatingClient::RemoveMember(
+ const TString& group,
+ const TString& member,
+ const TRemoveMemberOptions& options)
+{
+ return Underlying_->RemoveMember(group, member, options);
+}
+
+TFuture<TCheckPermissionResponse> TDelegatingClient::CheckPermission(
+ const TString& user,
+ const NYPath::TYPath& path,
+ NYTree::EPermission permission,
+ const TCheckPermissionOptions& options)
+{
+ return Underlying_->CheckPermission(user, path, permission, options);
+}
+
+TFuture<TCheckPermissionByAclResult> TDelegatingClient::CheckPermissionByAcl(
+ const std::optional<TString>& user,
+ NYTree::EPermission permission,
+ NYTree::INodePtr acl,
+ const TCheckPermissionByAclOptions& options)
+{
+ return Underlying_->CheckPermissionByAcl(user, permission, std::move(acl), options);
+}
+
+TFuture<void> TDelegatingClient::TransferAccountResources(
+ const TString& srcAccount,
+ const TString& dstAccount,
+ NYTree::INodePtr resourceDelta,
+ const TTransferAccountResourcesOptions& options)
+{
+ return Underlying_->TransferAccountResources(
+ srcAccount, dstAccount, std::move(resourceDelta), options
+ );
+}
+
+TFuture<void> TDelegatingClient::TransferPoolResources(
+ const TString& srcPool,
+ const TString& dstPool,
+ const TString& poolTree,
+ NYTree::INodePtr resourceDelta,
+ const TTransferPoolResourcesOptions& options)
+{
+ return Underlying_->TransferPoolResources(
+ srcPool, dstPool, poolTree, std::move(resourceDelta), options
+ );
+}
+
+TFuture<NScheduler::TOperationId> TDelegatingClient::StartOperation(
+ NScheduler::EOperationType type,
+ const NYson::TYsonString& spec,
+ const TStartOperationOptions& options)
+{
+ return Underlying_->StartOperation(type, spec, options);
+}
+
+TFuture<void> TDelegatingClient::AbortOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TAbortOperationOptions& options)
+{
+ return Underlying_->AbortOperation(operationIdOrAlias, options);
+}
+
+TFuture<void> TDelegatingClient::SuspendOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TSuspendOperationOptions& options)
+{
+ return Underlying_->SuspendOperation(operationIdOrAlias, options);
+}
+
+TFuture<void> TDelegatingClient::ResumeOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TResumeOperationOptions& options)
+{
+ return Underlying_->ResumeOperation(operationIdOrAlias, options);
+}
+
+TFuture<void> TDelegatingClient::CompleteOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TCompleteOperationOptions& options)
+{
+ return Underlying_->CompleteOperation(operationIdOrAlias, options);
+}
+
+TFuture<void> TDelegatingClient::UpdateOperationParameters(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NYson::TYsonString& parameters,
+ const TUpdateOperationParametersOptions& options)
+{
+ return Underlying_->UpdateOperationParameters(operationIdOrAlias, parameters, options);
+}
+
+TFuture<TOperation> TDelegatingClient::GetOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TGetOperationOptions& options)
+{
+ return Underlying_->GetOperation(operationIdOrAlias, options);
+}
+
+TFuture<void> TDelegatingClient::DumpJobContext(
+ NJobTrackerClient::TJobId jobId,
+ const NYPath::TYPath& path,
+ const TDumpJobContextOptions& options)
+{
+ return Underlying_->DumpJobContext(jobId, path, options);
+}
+
+TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr> TDelegatingClient::GetJobInput(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputOptions& options)
+{
+ return Underlying_->GetJobInput(jobId, options);
+}
+
+TFuture<NYson::TYsonString> TDelegatingClient::GetJobInputPaths(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputPathsOptions& options)
+{
+ return Underlying_->GetJobInputPaths(jobId, options);
+}
+
+TFuture<NYson::TYsonString> TDelegatingClient::GetJobSpec(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobSpecOptions& options)
+{
+ return Underlying_->GetJobSpec(jobId, options);
+}
+
+TFuture<TSharedRef> TDelegatingClient::GetJobStderr(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobStderrOptions& options)
+{
+ return Underlying_->GetJobStderr(operationIdOrAlias, jobId, options);
+}
+
+TFuture<TSharedRef> TDelegatingClient::GetJobFailContext(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobFailContextOptions& options)
+{
+ return Underlying_->GetJobFailContext(operationIdOrAlias, jobId, options);
+}
+
+TFuture<TListOperationsResult> TDelegatingClient::ListOperations(
+ const TListOperationsOptions& options)
+{
+ return Underlying_->ListOperations(options);
+}
+
+TFuture<TListJobsResult> TDelegatingClient::ListJobs(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TListJobsOptions& options)
+{
+ return Underlying_->ListJobs(operationIdOrAlias, options);
+}
+
+TFuture<NYson::TYsonString> TDelegatingClient::GetJob(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobOptions& options)
+{
+ return Underlying_->GetJob(operationIdOrAlias, jobId, options);
+}
+
+TFuture<void> TDelegatingClient::AbandonJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbandonJobOptions& options)
+{
+ return Underlying_->AbandonJob(jobId, options);
+}
+
+TFuture<TPollJobShellResponse> TDelegatingClient::PollJobShell(
+ NJobTrackerClient::TJobId jobId,
+ const std::optional<TString>& shellName,
+ const NYson::TYsonString& parameters,
+ const TPollJobShellOptions& options)
+{
+ return Underlying_->PollJobShell(jobId, shellName, parameters, options);
+}
+
+TFuture<void> TDelegatingClient::AbortJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbortJobOptions& options)
+{
+ return Underlying_->AbortJob(jobId, options);
+}
+
+TFuture<TClusterMeta> TDelegatingClient::GetClusterMeta(
+ const TGetClusterMetaOptions& options)
+{
+ return Underlying_->GetClusterMeta(options);
+}
+
+TFuture<void> TDelegatingClient::CheckClusterLiveness(
+ const TCheckClusterLivenessOptions& options)
+{
+ return Underlying_->CheckClusterLiveness(options);
+}
+
+TFuture<int> TDelegatingClient::BuildSnapshot(
+ const TBuildSnapshotOptions& options)
+{
+ return Underlying_->BuildSnapshot(options);
+}
+
+TFuture<TCellIdToSnapshotIdMap> TDelegatingClient::BuildMasterSnapshots(
+ const TBuildMasterSnapshotsOptions& options)
+{
+ return Underlying_->BuildMasterSnapshots(options);
+}
+
+TFuture<void> TDelegatingClient::SwitchLeader(
+ NHydra::TCellId cellId,
+ const TString& newLeaderAddress,
+ const TSwitchLeaderOptions& options)
+{
+ return Underlying_->SwitchLeader(cellId, newLeaderAddress, options);
+}
+
+TFuture<void> TDelegatingClient::ResetStateHash(
+ NHydra::TCellId cellId,
+ const TResetStateHashOptions& options)
+{
+ return Underlying_->ResetStateHash(cellId, options);
+}
+
+TFuture<void> TDelegatingClient::GCCollect(
+ const TGCCollectOptions& options)
+{
+ return Underlying_->GCCollect(options);
+}
+
+TFuture<void> TDelegatingClient::KillProcess(
+ const TString& address,
+ const TKillProcessOptions& options)
+{
+ return Underlying_->KillProcess(address, options);
+}
+
+TFuture<TString> TDelegatingClient::WriteCoreDump(
+ const TString& address,
+ const TWriteCoreDumpOptions& options)
+{
+ return Underlying_->WriteCoreDump(address, options);
+}
+
+TFuture<TGuid> TDelegatingClient::WriteLogBarrier(
+ const TString& address,
+ const TWriteLogBarrierOptions& options)
+{
+ return Underlying_->WriteLogBarrier(address, options);
+}
+
+TFuture<TString> TDelegatingClient::WriteOperationControllerCoreDump(
+ NJobTrackerClient::TOperationId operationId,
+ const TWriteOperationControllerCoreDumpOptions& options)
+{
+ return Underlying_->WriteOperationControllerCoreDump(operationId, options);
+}
+
+TFuture<void> TDelegatingClient::HealExecNode(
+ const TString& address,
+ const THealExecNodeOptions& options)
+{
+ return Underlying_->HealExecNode(address, options);
+}
+
+TFuture<void> TDelegatingClient::SuspendCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TSuspendCoordinatorOptions& options)
+{
+ return Underlying_->SuspendCoordinator(coordinatorCellId, options);
+}
+
+TFuture<void> TDelegatingClient::ResumeCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TResumeCoordinatorOptions& options)
+{
+ return Underlying_->ResumeCoordinator(coordinatorCellId, options);
+}
+
+TFuture<void> TDelegatingClient::MigrateReplicationCards(
+ NObjectClient::TCellId chaosCellId,
+ const TMigrateReplicationCardsOptions& options)
+{
+ return Underlying_->MigrateReplicationCards(chaosCellId, options);
+}
+
+TFuture<void> TDelegatingClient::SuspendChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendChaosCellsOptions& options)
+{
+ return Underlying_->SuspendChaosCells(cellIds, options);
+}
+
+TFuture<void> TDelegatingClient::ResumeChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeChaosCellsOptions& options)
+{
+ return Underlying_->ResumeChaosCells(cellIds, options);
+}
+
+TFuture<void> TDelegatingClient::SuspendTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendTabletCellsOptions& options)
+{
+ return Underlying_->SuspendTabletCells(cellIds, options);
+}
+
+TFuture<void> TDelegatingClient::ResumeTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeTabletCellsOptions& options)
+{
+ return Underlying_->ResumeTabletCells(cellIds, options);
+}
+
+TFuture<TMaintenanceId> TDelegatingClient::AddMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ EMaintenanceType type,
+ const TString& comment,
+ const TAddMaintenanceOptions& options)
+{
+ return Underlying_->AddMaintenance(component, address, type, comment, options);
+}
+
+TFuture<TMaintenanceCounts> TDelegatingClient::RemoveMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ const TMaintenanceFilter& filter,
+ const TRemoveMaintenanceOptions& options)
+{
+ return Underlying_->RemoveMaintenance(component, address, filter, options);
+}
+
+TFuture<TDisableChunkLocationsResult> TDelegatingClient::DisableChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDisableChunkLocationsOptions& options)
+{
+ return Underlying_->DisableChunkLocations(nodeAddress, locationUuids, options);
+}
+
+TFuture<TDestroyChunkLocationsResult> TDelegatingClient::DestroyChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDestroyChunkLocationsOptions& options)
+{
+ return Underlying_->DestroyChunkLocations(nodeAddress, locationUuids, options);
+}
+
+TFuture<TResurrectChunkLocationsResult> TDelegatingClient::ResurrectChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TResurrectChunkLocationsOptions& options)
+{
+ return Underlying_->ResurrectChunkLocations(nodeAddress, locationUuids, options);
+}
+
+TFuture<TRequestRebootResult> TDelegatingClient::RequestReboot(
+ const TString& nodeAddress,
+ const TRequestRebootOptions& options)
+{
+ return Underlying_->RequestReboot(nodeAddress, options);
+}
+
+TFuture<void> TDelegatingClient::SetUserPassword(
+ const TString& user,
+ const TString& currentPasswordSha256,
+ const TString& newPasswordSha256,
+ const TSetUserPasswordOptions& options)
+{
+ return Underlying_->SetUserPassword(
+ user,
+ currentPasswordSha256,
+ newPasswordSha256,
+ options);
+}
+
+TFuture<TIssueTokenResult> TDelegatingClient::IssueToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TIssueTokenOptions& options)
+{
+ return Underlying_->IssueToken(
+ user,
+ passwordSha256,
+ options);
+}
+
+TFuture<void> TDelegatingClient::RevokeToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TString& tokenSha256,
+ const TRevokeTokenOptions& options)
+{
+ return Underlying_->RevokeToken(
+ user,
+ passwordSha256,
+ tokenSha256,
+ options);
+}
+
+TFuture<TListUserTokensResult> TDelegatingClient::ListUserTokens(
+ const TString& user,
+ const TString& passwordSha256,
+ const TListUserTokensOptions& options)
+{
+ return Underlying_->ListUserTokens(
+ user,
+ passwordSha256,
+ options);
+}
+
+TFuture<NQueryTrackerClient::TQueryId> TDelegatingClient::StartQuery(
+ NQueryTrackerClient::EQueryEngine engine,
+ const TString& query,
+ const TStartQueryOptions& options)
+{
+ return Underlying_->StartQuery(engine, query, options);
+}
+
+TFuture<void> TDelegatingClient::AbortQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAbortQueryOptions& options)
+{
+ return Underlying_->AbortQuery(queryId, options);
+}
+
+TFuture<TQueryResult> TDelegatingClient::GetQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex,
+ const TGetQueryResultOptions& options)
+{
+ return Underlying_->GetQueryResult(queryId, resultIndex, options);
+}
+
+TFuture<IUnversionedRowsetPtr> TDelegatingClient::ReadQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex,
+ const TReadQueryResultOptions& options)
+{
+ return Underlying_->ReadQueryResult(queryId, resultIndex, options);
+}
+
+TFuture<TQuery> TDelegatingClient::GetQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TGetQueryOptions& options)
+{
+ return Underlying_->GetQuery(queryId, options);
+}
+
+TFuture<TListQueriesResult> TDelegatingClient::ListQueries(const TListQueriesOptions& options)
+{
+ return Underlying_->ListQueries(options);
+}
+
+TFuture<void> TDelegatingClient::AlterQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAlterQueryOptions& options)
+{
+ return Underlying_->AlterQuery(queryId, options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Method below ensures that delegating client contains implementations for all
+// methods of IClient. Tthis reduces the number of PR iterations you need to
+// find that some out-of-yt/yt implementation of IClient does not compile.
+void InstantiateDelegatingClient()
+{
+ auto delegatingClient = New<TDelegatingClient>(/*client*/ nullptr);
+ Y_UNUSED(delegatingClient);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/delegating_client.h b/yt/yt/client/api/delegating_client.h
new file mode 100644
index 0000000000..9f2c5fd59c
--- /dev/null
+++ b/yt/yt/client/api/delegating_client.h
@@ -0,0 +1,617 @@
+#pragma once
+
+#include "client.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A simple base class that implements IClient and delegates
+//! all calls to an underlying instance.
+class TDelegatingClient
+ : public IClient
+{
+public:
+ explicit TDelegatingClient(IClientPtr underlying);
+
+ // IClientBase methods
+
+ IConnectionPtr GetConnection() override;
+ std::optional<TStringBuf> GetClusterName(bool fetchIfNull = true) override;
+
+ // Transactions
+ TFuture<ITransactionPtr> StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const TTransactionStartOptions& options = {}) override;
+
+ // Tables
+ TFuture<IUnversionedRowsetPtr> LookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TLookupRowsOptions& options = {}) override;
+
+ TFuture<IVersionedRowsetPtr> VersionedLookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TVersionedLookupRowsOptions& options = {}) override;
+
+ TFuture<std::vector<IUnversionedRowsetPtr>> MultiLookup(
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options = {}) override;
+
+ TFuture<TSelectRowsResult> SelectRows(
+ const TString& query,
+ const TSelectRowsOptions& options = {}) override;
+
+ TFuture<NYson::TYsonString> ExplainQuery(
+ const TString& query,
+ const TExplainQueryOptions& options = {}) override;
+
+ TFuture<TPullRowsResult> PullRows(
+ const NYPath::TYPath& path,
+ const TPullRowsOptions& options = {}) override;
+
+ TFuture<ITableReaderPtr> CreateTableReader(
+ const NYPath::TRichYPath& path,
+ const TTableReaderOptions& options = {}) override;
+
+ TFuture<ITableWriterPtr> CreateTableWriter(
+ const NYPath::TRichYPath& path,
+ const TTableWriterOptions& options = {}) override;
+
+ // Queues
+ TFuture<NQueueClient::IQueueRowsetPtr> PullQueue(
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullQueueOptions& options = {}) override;
+
+ TFuture<NQueueClient::IQueueRowsetPtr> PullConsumer(
+ const NYPath::TRichYPath& consumerPath,
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullConsumerOptions& options = {}) override;
+
+ TFuture<void> RegisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ bool vital,
+ const TRegisterQueueConsumerOptions& options = {}) override;
+
+ TFuture<void> UnregisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ const TUnregisterQueueConsumerOptions& options = {}) override;
+
+ TFuture<std::vector<TListQueueConsumerRegistrationsResult>> ListQueueConsumerRegistrations(
+ const std::optional<NYPath::TRichYPath>& queuePath,
+ const std::optional<NYPath::TRichYPath>& consumerPath,
+ const TListQueueConsumerRegistrationsOptions& options = {}) override;
+
+ // Cypress
+ TFuture<NYson::TYsonString> GetNode(
+ const NYPath::TYPath& path,
+ const TGetNodeOptions& options = {}) override;
+
+ TFuture<void> SetNode(
+ const NYPath::TYPath& path,
+ const NYson::TYsonString& value,
+ const TSetNodeOptions& options = {}) override;
+
+ TFuture<void> MultisetAttributesNode(
+ const NYPath::TYPath& path,
+ const NYTree::IMapNodePtr& attributes,
+ const TMultisetAttributesNodeOptions& options = {}) override;
+
+ TFuture<void> RemoveNode(
+ const NYPath::TYPath& path,
+ const TRemoveNodeOptions& options = {}) override;
+
+ TFuture<NYson::TYsonString> ListNode(
+ const NYPath::TYPath& path,
+ const TListNodeOptions& options = {}) override;
+
+ TFuture<NCypressClient::TNodeId> CreateNode(
+ const NYPath::TYPath& path,
+ NObjectClient::EObjectType type,
+ const TCreateNodeOptions& options = {}) override;
+
+ TFuture<TLockNodeResult> LockNode(
+ const NYPath::TYPath& path,
+ NCypressClient::ELockMode mode,
+ const TLockNodeOptions& options = {}) override;
+
+ TFuture<void> UnlockNode(
+ const NYPath::TYPath& path,
+ const TUnlockNodeOptions& options = {}) override;
+
+ TFuture<NCypressClient::TNodeId> CopyNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TCopyNodeOptions& options = {}) override;
+
+ TFuture<NCypressClient::TNodeId> MoveNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TMoveNodeOptions& options = {}) override;
+
+ TFuture<NCypressClient::TNodeId> LinkNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TLinkNodeOptions& options = {}) override;
+
+ TFuture<void> ConcatenateNodes(
+ const std::vector<NYPath::TRichYPath>& srcPaths,
+ const NYPath::TRichYPath& dstPath,
+ const TConcatenateNodesOptions& options = {}) override;
+
+ TFuture<bool> NodeExists(
+ const NYPath::TYPath& path,
+ const TNodeExistsOptions& options = {}) override;
+
+ TFuture<void> ExternalizeNode(
+ const NYPath::TYPath& path,
+ NObjectClient::TCellTag cellTag,
+ const TExternalizeNodeOptions& options = {}) override;
+
+ TFuture<void> InternalizeNode(
+ const NYPath::TYPath& path,
+ const TInternalizeNodeOptions& options = {}) override;
+
+ // Objects
+ TFuture<NObjectClient::TObjectId> CreateObject(
+ NObjectClient::EObjectType type,
+ const TCreateObjectOptions& options = {}) override;
+
+
+ // Files
+ TFuture<IFileReaderPtr> CreateFileReader(
+ const NYPath::TYPath& path,
+ const TFileReaderOptions& options = {}) override;
+
+ IFileWriterPtr CreateFileWriter(
+ const NYPath::TRichYPath& path,
+ const TFileWriterOptions& options = {}) override;
+
+ // Journals
+ IJournalReaderPtr CreateJournalReader(
+ const NYPath::TYPath& path,
+ const TJournalReaderOptions& options = {}) override;
+
+ IJournalWriterPtr CreateJournalWriter(
+ const NYPath::TYPath& path,
+ const TJournalWriterOptions& options = {}) override;
+
+ // IClient methods
+
+ void Terminate() override;
+
+ const NTabletClient::ITableMountCachePtr& GetTableMountCache() override;
+
+ const NChaosClient::IReplicationCardCachePtr& GetReplicationCardCache() override;
+
+ const NTransactionClient::ITimestampProviderPtr& GetTimestampProvider() override;
+
+ // Transactions
+ ITransactionPtr AttachTransaction(
+ NTransactionClient::TTransactionId transactionId,
+ const TTransactionAttachOptions& options = {}) override;
+
+ // Tables
+ TFuture<void> MountTable(
+ const NYPath::TYPath& path,
+ const TMountTableOptions& options = {}) override;
+
+ TFuture<void> UnmountTable(
+ const NYPath::TYPath& path,
+ const TUnmountTableOptions& options = {}) override;
+
+ TFuture<void> RemountTable(
+ const NYPath::TYPath& path,
+ const TRemountTableOptions& options = {}) override;
+
+ TFuture<void> FreezeTable(
+ const NYPath::TYPath& path,
+ const TFreezeTableOptions& options = {}) override;
+
+ TFuture<void> UnfreezeTable(
+ const NYPath::TYPath& path,
+ const TUnfreezeTableOptions& options = {}) override;
+
+ TFuture<void> ReshardTable(
+ const NYPath::TYPath& path,
+ const std::vector<NTableClient::TLegacyOwningKey>& pivotKeys,
+ const TReshardTableOptions& options = {}) override;
+
+ TFuture<void> ReshardTable(
+ const NYPath::TYPath& path,
+ int tabletCount,
+ const TReshardTableOptions& options = {}) override;
+
+ TFuture<std::vector<NTabletClient::TTabletActionId>> ReshardTableAutomatic(
+ const NYPath::TYPath& path,
+ const TReshardTableAutomaticOptions& options = {}) override;
+
+ TFuture<void> TrimTable(
+ const NYPath::TYPath& path,
+ int tabletIndex,
+ i64 trimmedRowCount,
+ const TTrimTableOptions& options = {}) override;
+
+ TFuture<void> AlterTable(
+ const NYPath::TYPath& path,
+ const TAlterTableOptions& options = {}) override;
+
+ TFuture<void> AlterTableReplica(
+ NTabletClient::TTableReplicaId replicaId,
+ const TAlterTableReplicaOptions& options = {}) override;
+
+ TFuture<void> AlterReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TAlterReplicationCardOptions& options = {}) override;
+
+ TFuture<NYson::TYsonString> GetTablePivotKeys(
+ const NYPath::TYPath& path,
+ const TGetTablePivotKeysOptions& options = {}) override;
+
+ TFuture<void> CreateTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TCreateTableBackupOptions& options = {}) override;
+
+ TFuture<void> RestoreTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TRestoreTableBackupOptions& options = {}) override;
+
+ TFuture<std::vector<NTabletClient::TTableReplicaId>> GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const NTableClient::TNameTablePtr& nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TGetInSyncReplicasOptions& options = {}) override;
+
+ TFuture<std::vector<NTabletClient::TTableReplicaId>> GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const TGetInSyncReplicasOptions& options = {}) override;
+
+ TFuture<std::vector<TTabletInfo>> GetTabletInfos(
+ const NYPath::TYPath& path,
+ const std::vector<int>& tabletIndexes,
+ const TGetTabletInfosOptions& options = {}) override;
+
+ TFuture<TGetTabletErrorsResult> GetTabletErrors(
+ const NYPath::TYPath& path,
+ const TGetTabletErrorsOptions& options = {}) override;
+
+ TFuture<std::vector<NTabletClient::TTabletActionId>> BalanceTabletCells(
+ const TString& tabletCellBundle,
+ const std::vector<NYPath::TYPath>& movableTables,
+ const TBalanceTabletCellsOptions& options = {}) override;
+
+ TFuture<NChaosClient::TReplicationCardPtr> GetReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TGetReplicationCardOptions& options = {}) override;
+
+ TFuture<void> UpdateChaosTableReplicaProgress(
+ NChaosClient::TReplicaId replicaId,
+ const TUpdateChaosTableReplicaProgressOptions& options = {}) override;
+
+ TFuture<TSkynetSharePartsLocationsPtr> LocateSkynetShare(
+ const NYPath::TRichYPath& path,
+ const TLocateSkynetShareOptions& options = {}) override;
+
+ TFuture<std::vector<NTableClient::TColumnarStatistics>> GetColumnarStatistics(
+ const std::vector<NYPath::TRichYPath>& path,
+ const TGetColumnarStatisticsOptions& options = {}) override;
+
+ TFuture<TMultiTablePartitions> PartitionTables(
+ const std::vector<NYPath::TRichYPath>& paths,
+ const TPartitionTablesOptions& options) override;
+
+ // Journals
+ TFuture<void> TruncateJournal(
+ const NYPath::TYPath& path,
+ i64 rowCount,
+ const TTruncateJournalOptions& options = {}) override;
+
+ // Files
+ TFuture<TGetFileFromCacheResult> GetFileFromCache(
+ const TString& md5,
+ const TGetFileFromCacheOptions& options = {}) override;
+
+ TFuture<TPutFileToCacheResult> PutFileToCache(
+ const NYPath::TYPath& path,
+ const TString& expectedMD5,
+ const TPutFileToCacheOptions& options = {}) override;
+
+ // Security
+ TFuture<void> AddMember(
+ const TString& group,
+ const TString& member,
+ const TAddMemberOptions& options = {}) override;
+
+ TFuture<void> RemoveMember(
+ const TString& group,
+ const TString& member,
+ const TRemoveMemberOptions& options = {}) override;
+
+ TFuture<TCheckPermissionResponse> CheckPermission(
+ const TString& user,
+ const NYPath::TYPath& path,
+ NYTree::EPermission permission,
+ const TCheckPermissionOptions& options = {}) override;
+
+ TFuture<TCheckPermissionByAclResult> CheckPermissionByAcl(
+ const std::optional<TString>& user,
+ NYTree::EPermission permission,
+ NYTree::INodePtr acl,
+ const TCheckPermissionByAclOptions& options = {}) override;
+
+ TFuture<void> TransferAccountResources(
+ const TString& srcAccount,
+ const TString& dstAccount,
+ NYTree::INodePtr resourceDelta,
+ const TTransferAccountResourcesOptions& options = {}) override;
+
+ // Scheduler
+ TFuture<void> TransferPoolResources(
+ const TString& srcPool,
+ const TString& dstPool,
+ const TString& poolTree,
+ NYTree::INodePtr resourceDelta,
+ const TTransferPoolResourcesOptions& options = {}) override;
+
+ TFuture<NScheduler::TOperationId> StartOperation(
+ NScheduler::EOperationType type,
+ const NYson::TYsonString& spec,
+ const TStartOperationOptions& options = {}) override;
+
+ TFuture<void> AbortOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TAbortOperationOptions& options = {}) override;
+
+ TFuture<void> SuspendOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TSuspendOperationOptions& options = {}) override;
+
+ TFuture<void> ResumeOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TResumeOperationOptions& options = {}) override;
+
+ TFuture<void> CompleteOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TCompleteOperationOptions& options = {}) override;
+
+ TFuture<void> UpdateOperationParameters(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NYson::TYsonString& parameters,
+ const TUpdateOperationParametersOptions& options = {}) override;
+
+ TFuture<TOperation> GetOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TGetOperationOptions& options = {}) override;
+
+ TFuture<void> DumpJobContext(
+ NJobTrackerClient::TJobId jobId,
+ const NYPath::TYPath& path,
+ const TDumpJobContextOptions& options = {}) override;
+
+ TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr> GetJobInput(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputOptions& options = {}) override;
+
+ TFuture<NYson::TYsonString> GetJobInputPaths(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputPathsOptions& options = {}) override;
+
+ TFuture<NYson::TYsonString> GetJobSpec(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobSpecOptions& options = {}) override;
+
+ TFuture<TSharedRef> GetJobStderr(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobStderrOptions& options = {}) override;
+
+ TFuture<TSharedRef> GetJobFailContext(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobFailContextOptions& options = {}) override;
+
+ TFuture<TListOperationsResult> ListOperations(
+ const TListOperationsOptions& options = {}) override;
+
+ TFuture<TListJobsResult> ListJobs(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TListJobsOptions& options = {}
+ ) override;
+
+ TFuture<NYson::TYsonString> GetJob(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobOptions& options = {}) override;
+
+ TFuture<void> AbandonJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbandonJobOptions& options = {}) override;
+
+ TFuture<TPollJobShellResponse> PollJobShell(
+ NJobTrackerClient::TJobId jobId,
+ const std::optional<TString>& shellName,
+ const NYson::TYsonString& parameters,
+ const TPollJobShellOptions& options = {}) override;
+
+ TFuture<void> AbortJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbortJobOptions& options = {}) override;
+
+ // Metadata
+ TFuture<TClusterMeta> GetClusterMeta(
+ const TGetClusterMetaOptions& options = {}) override;
+
+ TFuture<void> CheckClusterLiveness(
+ const TCheckClusterLivenessOptions& options = {}) override;
+
+ // Administration
+ TFuture<int> BuildSnapshot(
+ const TBuildSnapshotOptions& options = {}) override;
+
+ TFuture<TCellIdToSnapshotIdMap> BuildMasterSnapshots(
+ const TBuildMasterSnapshotsOptions& options = {}) override;
+
+ TFuture<void> SwitchLeader(
+ NHydra::TCellId cellId,
+ const TString& newLeaderAddress,
+ const TSwitchLeaderOptions& options = {}) override;
+
+ TFuture<void> ResetStateHash(
+ NHydra::TCellId cellId,
+ const TResetStateHashOptions& options = {}) override;
+
+ TFuture<void> GCCollect(
+ const TGCCollectOptions& options = {}) override;
+
+ TFuture<void> KillProcess(
+ const TString& address,
+ const TKillProcessOptions& options = {}) override;
+
+ TFuture<TString> WriteCoreDump(
+ const TString& address,
+ const TWriteCoreDumpOptions& options = {}) override;
+
+ TFuture<TGuid> WriteLogBarrier(
+ const TString& address,
+ const TWriteLogBarrierOptions& options = {}) override;
+
+ TFuture<TString> WriteOperationControllerCoreDump(
+ NJobTrackerClient::TOperationId operationId,
+ const TWriteOperationControllerCoreDumpOptions& options = {}) override;
+
+ TFuture<void> HealExecNode(
+ const TString& address,
+ const THealExecNodeOptions& options = {}) override;
+
+ TFuture<void> SuspendCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TSuspendCoordinatorOptions& options = {}) override;
+
+ TFuture<void> ResumeCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TResumeCoordinatorOptions& options = {}) override;
+
+ TFuture<void> MigrateReplicationCards(
+ NObjectClient::TCellId chaosCellId,
+ const TMigrateReplicationCardsOptions& options = {}) override;
+
+ TFuture<void> SuspendChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendChaosCellsOptions& options = {}) override;
+
+ TFuture<void> ResumeChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeChaosCellsOptions& options = {}) override;
+
+ TFuture<void> SuspendTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendTabletCellsOptions& options = {}) override;
+
+ TFuture<void> ResumeTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeTabletCellsOptions& options = {}) override;
+
+ TFuture<TMaintenanceId> AddMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ EMaintenanceType type,
+ const TString& comment,
+ const TAddMaintenanceOptions& options = {}) override;
+
+ TFuture<TMaintenanceCounts> RemoveMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ const TMaintenanceFilter& filter,
+ const TRemoveMaintenanceOptions& options = {}) override;
+
+ TFuture<TDisableChunkLocationsResult> DisableChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDisableChunkLocationsOptions& options = {}) override;
+
+ TFuture<TDestroyChunkLocationsResult> DestroyChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDestroyChunkLocationsOptions& options = {}) override;
+
+ TFuture<TResurrectChunkLocationsResult> ResurrectChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TResurrectChunkLocationsOptions& options = {}) override;
+
+ TFuture<TRequestRebootResult> RequestReboot(
+ const TString& nodeAddress,
+ const TRequestRebootOptions& options = {}) override;
+
+ TFuture<void> SetUserPassword(
+ const TString& user,
+ const TString& currentPasswordSha256,
+ const TString& newPasswordSha256,
+ const TSetUserPasswordOptions& options) override;
+
+ TFuture<TIssueTokenResult> IssueToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TIssueTokenOptions& options) override;
+
+ TFuture<void> RevokeToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TString& tokenSha256,
+ const TRevokeTokenOptions& options) override;
+
+ TFuture<TListUserTokensResult> ListUserTokens(
+ const TString& user,
+ const TString& passwordSha256,
+ const TListUserTokensOptions& options) override;
+
+ // Query tracker
+
+ TFuture<NQueryTrackerClient::TQueryId> StartQuery(
+ NQueryTrackerClient::EQueryEngine engine,
+ const TString& query,
+ const TStartQueryOptions& options) override;
+
+ TFuture<void> AbortQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAbortQueryOptions& options) override;
+
+ TFuture<TQueryResult> GetQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex,
+ const TGetQueryResultOptions& options) override;
+
+ TFuture<IUnversionedRowsetPtr> ReadQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex,
+ const TReadQueryResultOptions& options) override;
+
+ TFuture<TQuery> GetQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TGetQueryOptions& options) override;
+
+ TFuture<TListQueriesResult> ListQueries(const TListQueriesOptions& options) override;
+
+ TFuture<void> AlterQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAlterQueryOptions& options) override;
+
+protected:
+ const IClientPtr Underlying_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/etc_client.cpp b/yt/yt/client/api/etc_client.cpp
new file mode 100644
index 0000000000..5610e179b3
--- /dev/null
+++ b/yt/yt/client/api/etc_client.cpp
@@ -0,0 +1,26 @@
+#include "etc_client.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TCheckClusterLivenessOptions::IsCheckTrivial() const
+{
+ return
+ !CheckCypressRoot &&
+ !CheckSecondaryMasterCells &&
+ !CheckTabletCellBundle;
+}
+
+bool TCheckClusterLivenessOptions::operator==(const TCheckClusterLivenessOptions& other) const
+{
+ return
+ CheckCypressRoot == other.CheckCypressRoot &&
+ CheckSecondaryMasterCells == other.CheckSecondaryMasterCells &&
+ CheckTabletCellBundle == other.CheckTabletCellBundle;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/etc_client.h b/yt/yt/client/api/etc_client.h
new file mode 100644
index 0000000000..1ab15d9900
--- /dev/null
+++ b/yt/yt/client/api/etc_client.h
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.pb.h>
+
+#include <yt/yt_proto/yt/client/hive/proto/cluster_directory.pb.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TGetClusterMetaOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+{
+ bool PopulateNodeDirectory = false;
+ bool PopulateClusterDirectory = false;
+ bool PopulateMediumDirectory = false;
+ bool PopulateCellDirectory = false;
+ bool PopulateMasterCacheNodeAddresses = false;
+ bool PopulateTimestampProviderAddresses = false;
+ bool PopulateFeatures = false;
+};
+
+struct TClusterMeta
+{
+ std::shared_ptr<NNodeTrackerClient::NProto::TNodeDirectory> NodeDirectory;
+ std::shared_ptr<NHiveClient::NProto::TClusterDirectory> ClusterDirectory;
+ std::shared_ptr<NChunkClient::NProto::TMediumDirectory> MediumDirectory;
+ std::vector<TString> MasterCacheNodeAddresses;
+ std::vector<TString> TimestampProviderAddresses;
+ NYTree::IMapNodePtr Features;
+};
+
+struct TCheckClusterLivenessOptions
+ : public TTimeoutOptions
+{
+ //! Checks cypress root availability.
+ bool CheckCypressRoot = false;
+ //! Checks secondary master cells generic availability.
+ bool CheckSecondaryMasterCells = false;
+ //! Unless null checks tablet cell bundle health.
+ std::optional<TString> CheckTabletCellBundle;
+
+ bool IsCheckTrivial() const;
+
+ // NB: For testing purposes.
+ bool operator==(const TCheckClusterLivenessOptions& other) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IEtcClientBase
+{
+ virtual TFuture<NObjectClient::TObjectId> CreateObject(
+ NObjectClient::EObjectType type,
+ const TCreateObjectOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IEtcClient
+{
+ virtual TFuture<TClusterMeta> GetClusterMeta(
+ const TGetClusterMetaOptions& options = {}) = 0;
+
+ virtual TFuture<void> CheckClusterLiveness(
+ const TCheckClusterLivenessOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/file_client.h b/yt/yt/client/api/file_client.h
new file mode 100644
index 0000000000..351c81bc52
--- /dev/null
+++ b/yt/yt/client/api/file_client.h
@@ -0,0 +1,87 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/core/ypath/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TFileReaderOptions
+ : public TTransactionalOptions
+ , public TSuppressableAccessTrackingOptions
+{
+ std::optional<i64> Offset;
+ std::optional<i64> Length;
+ TFileReaderConfigPtr Config;
+};
+
+struct TFileWriterOptions
+ : public TTransactionalOptions
+ , public TPrerequisiteOptions
+{
+ bool ComputeMD5 = false;
+ TFileWriterConfigPtr Config;
+};
+
+struct TGetFileFromCacheOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+ , public TTransactionalOptions
+{
+ NYPath::TYPath CachePath;
+};
+
+struct TGetFileFromCacheResult
+{
+ NYPath::TYPath Path;
+};
+
+struct TPutFileToCacheOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+ , public TTransactionalOptions
+{
+ NYPath::TYPath CachePath;
+ bool PreserveExpirationTimeout = false;
+ int RetryCount = 10;
+};
+
+struct TPutFileToCacheResult
+{
+ NYPath::TYPath Path;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IFileClientBase
+{
+ virtual TFuture<IFileReaderPtr> CreateFileReader(
+ const NYPath::TYPath& path,
+ const TFileReaderOptions& options = {}) = 0;
+
+ virtual IFileWriterPtr CreateFileWriter(
+ const NYPath::TRichYPath& path,
+ const TFileWriterOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IFileClient
+{
+ virtual TFuture<TGetFileFromCacheResult> GetFileFromCache(
+ const TString& md5,
+ const TGetFileFromCacheOptions& options = {}) = 0;
+
+ virtual TFuture<TPutFileToCacheResult> PutFileToCache(
+ const NYPath::TYPath& path,
+ const TString& expectedMD5,
+ const TPutFileToCacheOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/file_reader.h b/yt/yt/client/api/file_reader.h
new file mode 100644
index 0000000000..bf4855fef3
--- /dev/null
+++ b/yt/yt/client/api/file_reader.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/hydra/public.h>
+#include <yt/yt/client/object_client/public.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IFileReader
+ : public NConcurrency::IAsyncZeroCopyInputStream
+{
+ //! Returns ID of file node.
+ virtual NObjectClient::TObjectId GetId() const = 0;
+
+ //! Returns revision of file node.
+ virtual NHydra::TRevision GetRevision() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IFileReader)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/file_writer.h b/yt/yt/client/api/file_writer.h
new file mode 100644
index 0000000000..1e8742fcfe
--- /dev/null
+++ b/yt/yt/client/api/file_writer.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IFileWriter
+ : public virtual TRefCounted
+{
+ //! Opens the writer.
+ //! No other method can be called prior to the success of this one.
+ virtual TFuture<void> Open() = 0;
+
+ //! Writes the next portion of file data.
+ /*!
+ * #data must remain alive until this asynchronous operation completes.
+ */
+ virtual TFuture<void> Write(const TSharedRef& data) = 0;
+
+ //! Closes the writer.
+ //! No other method can be called after this one.
+ virtual TFuture<void> Close() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IFileWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/helpers.cpp b/yt/yt/client/api/helpers.cpp
new file mode 100644
index 0000000000..ab2b06d7db
--- /dev/null
+++ b/yt/yt/client/api/helpers.cpp
@@ -0,0 +1,24 @@
+#include "helpers.h"
+
+#include "public.h"
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateMaintenanceComment(const TString& comment)
+{
+ constexpr int MaxMaintenanceCommentLength = 512;
+
+ if (comment.size() > MaxMaintenanceCommentLength) {
+ THROW_ERROR_EXCEPTION("Maintenance comment is too long")
+ << TErrorAttribute("comment_length", comment.size())
+ << TErrorAttribute("max_comment_length", MaxMaintenanceCommentLength);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/helpers.h b/yt/yt/client/api/helpers.h
new file mode 100644
index 0000000000..d9eff87d4d
--- /dev/null
+++ b/yt/yt/client/api/helpers.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <util/generic/string.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateMaintenanceComment(const TString& comment);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/internal_client.cpp b/yt/yt/client/api/internal_client.cpp
new file mode 100644
index 0000000000..2fb7da7506
--- /dev/null
+++ b/yt/yt/client/api/internal_client.cpp
@@ -0,0 +1,27 @@
+#include "internal_client.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TSerializableHunkDescriptor::Register(TRegistrar registrar)
+{
+ registrar.BaseClassParameter("chunk_id", &TThis::ChunkId);
+ registrar.BaseClassParameter("erasure_codec", &TThis::ErasureCodec)
+ .Optional();
+ registrar.BaseClassParameter("block_index", &TThis::BlockIndex);
+ registrar.BaseClassParameter("block_offset", &TThis::BlockOffset);
+ registrar.BaseClassParameter("block_size", &TThis::BlockSize)
+ .Optional();
+ registrar.BaseClassParameter("length", &TThis::Length);
+}
+
+TSerializableHunkDescriptor::TSerializableHunkDescriptor(const THunkDescriptor& descriptor)
+ : THunkDescriptor(descriptor)
+{
+ ::NYT::NYTree::TYsonStructRegistry::Get()->InitializeStruct(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/internal_client.h b/yt/yt/client/api/internal_client.h
new file mode 100644
index 0000000000..0c569021b9
--- /dev/null
+++ b/yt/yt/client/api/internal_client.h
@@ -0,0 +1,136 @@
+#pragma once
+
+#include "client.h"
+
+#include <yt/yt/client/chunk_client/public.h>
+
+#include <yt/yt/library/erasure/public.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct THunkDescriptor
+{
+ NChunkClient::TChunkId ChunkId;
+ NErasure::ECodec ErasureCodec = NErasure::ECodec::None;
+ int BlockIndex = -1;
+ i64 BlockOffset = -1;
+ i64 Length = -1;
+ std::optional<i64> BlockSize;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSerializableHunkDescriptor
+ : public THunkDescriptor
+ , public NYTree::TYsonStruct
+{
+public:
+ TSerializableHunkDescriptor(const THunkDescriptor& descriptor);
+
+ REGISTER_YSON_STRUCT(TSerializableHunkDescriptor);
+
+ static void Register(TRegistrar registrar);
+};
+
+using TSerializableHunkDescriptorPtr = TIntrusivePtr<TSerializableHunkDescriptor>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TReadHunksOptions
+ : public TTimeoutOptions
+{
+ NChunkClient::TChunkFragmentReaderConfigPtr Config;
+
+ //! If false, chunk fragment is returned as is.
+ bool ParseHeader;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWriteHunksOptions
+ : public TTimeoutOptions
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TLockHunkStoreOptions
+ : public TTimeoutOptions
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TUnlockHunkStoreOptions
+ : public TTimeoutOptions
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TGetOrderedTabletSafeTrimRowCountOptions
+ : public TTimeoutOptions
+{ };
+
+struct TGetOrderedTabletSafeTrimRowCountRequest
+{
+ NYTree::TYPath Path;
+ int TabletIndex;
+ NTransactionClient::TTimestamp Timestamp;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Provides a set of private APIs.
+/*!
+ * Only native clients are expected to implement this.
+ */
+struct IInternalClient
+ : public virtual TRefCounted
+{
+ virtual TFuture<std::vector<TSharedRef>> ReadHunks(
+ const std::vector<THunkDescriptor>& descriptors,
+ const TReadHunksOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<THunkDescriptor>> WriteHunks(
+ const NYTree::TYPath& path,
+ int tabletIndex,
+ const std::vector<TSharedRef>& payloads,
+ const TWriteHunksOptions& options = {}) = 0;
+
+ virtual TFuture<void> LockHunkStore(
+ const NYTree::TYPath& path,
+ int tabletIndex,
+ NTabletClient::TStoreId storeId,
+ NTabletClient::TTabletId lockerTabletId,
+ const TLockHunkStoreOptions& options = {}) = 0;
+ virtual TFuture<void> UnlockHunkStore(
+ const NYTree::TYPath& path,
+ int tabletIndex,
+ NTabletClient::TStoreId storeId,
+ NTabletClient::TTabletId lockerTabletId,
+ const TUnlockHunkStoreOptions& options = {}) = 0;
+
+ //! Same as NApi::IClient::PullQueue, but without authentication.
+ //! This is used inside methods like NApi::IClient::PullConsumer, which perform their own authentication
+ //! and allow reading from a queue without having read permissions for the underlying dynamic table.
+ virtual TFuture<NQueueClient::IQueueRowsetPtr> PullQueueUnauthenticated(
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullQueueOptions& options = {}) = 0;
+
+ //! For each request, finds and returns some index bounds in the given ordered tablet based on the given timestamp.
+ //! See response definition for details.
+ virtual TFuture<std::vector<TErrorOr<i64>>> GetOrderedTabletSafeTrimRowCount(
+ const std::vector<TGetOrderedTabletSafeTrimRowCountRequest>& requests,
+ const TGetOrderedTabletSafeTrimRowCountOptions& options = {}) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IInternalClient)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/journal_client.cpp b/yt/yt/client/api/journal_client.cpp
new file mode 100644
index 0000000000..b377ed9bbf
--- /dev/null
+++ b/yt/yt/client/api/journal_client.cpp
@@ -0,0 +1,33 @@
+#include "journal_client.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TJournalWriterPerformanceCounters::TJournalWriterPerformanceCounters(const NProfiling::TProfiler& profiler)
+{
+#define XX(name) \
+ name ## Timer = profiler.Timer("/" + CamelCaseToUnderscoreCase(#name) + "_time");
+
+ XX(GetBasicAttributes)
+ XX(BeginUpload)
+ XX(GetExtendedAttributes)
+ XX(GetUploadParameters)
+ XX(EndUpload)
+ XX(OpenSession)
+ XX(CreateChunk)
+ XX(AllocateWriteTargets)
+ XX(StartNodeSession)
+ XX(ConfirmChunk)
+ XX(AttachChunk)
+ XX(SealChunk)
+
+#undef XX
+
+ WriteQuorumLag = profiler.Timer("/write_quorum_lag");
+ MaxReplicaLag = profiler.Timer("/max_replica_lag");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/journal_client.h b/yt/yt/client/api/journal_client.h
new file mode 100644
index 0000000000..086e7634c6
--- /dev/null
+++ b/yt/yt/client/api/journal_client.h
@@ -0,0 +1,86 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/client/journal_client/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TJournalReaderOptions
+ : public TTransactionalOptions
+ , public TSuppressableAccessTrackingOptions
+{
+ std::optional<i64> FirstRowIndex;
+ std::optional<i64> RowCount;
+ TJournalReaderConfigPtr Config;
+};
+
+struct TJournalWriterPerformanceCounters
+{
+ TJournalWriterPerformanceCounters() = default;
+ explicit TJournalWriterPerformanceCounters(const NProfiling::TProfiler& profiler);
+
+ NProfiling::TEventTimer GetBasicAttributesTimer;
+ NProfiling::TEventTimer BeginUploadTimer;
+ NProfiling::TEventTimer GetExtendedAttributesTimer;
+ NProfiling::TEventTimer GetUploadParametersTimer;
+ NProfiling::TEventTimer EndUploadTimer;
+ NProfiling::TEventTimer OpenSessionTimer;
+ NProfiling::TEventTimer CreateChunkTimer;
+ NProfiling::TEventTimer AllocateWriteTargetsTimer;
+ NProfiling::TEventTimer StartNodeSessionTimer;
+ NProfiling::TEventTimer ConfirmChunkTimer;
+ NProfiling::TEventTimer AttachChunkTimer;
+ NProfiling::TEventTimer SealChunkTimer;
+ NProfiling::TEventTimer WriteQuorumLag;
+ NProfiling::TEventTimer MaxReplicaLag;
+};
+
+struct TJournalWriterOptions
+ : public TTransactionalOptions
+ , public TPrerequisiteOptions
+{
+ TJournalWriterConfigPtr Config;
+ bool EnableMultiplexing = true;
+ // TODO(babenko): enable by default
+ bool EnableChunkPreallocation = false;
+
+ i64 ReplicaLagLimit = NJournalClient::DefaultReplicaLagLimit;
+
+ TJournalWriterPerformanceCounters Counters;
+};
+
+struct TTruncateJournalOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IJournalClientBase
+{
+ virtual IJournalReaderPtr CreateJournalReader(
+ const NYPath::TYPath& path,
+ const TJournalReaderOptions& options = {}) = 0;
+
+ virtual IJournalWriterPtr CreateJournalWriter(
+ const NYPath::TYPath& path,
+ const TJournalWriterOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IJournalClient
+{
+ virtual TFuture<void> TruncateJournal(
+ const NYPath::TYPath& path,
+ i64 rowCount,
+ const TTruncateJournalOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/journal_reader.h b/yt/yt/client/api/journal_reader.h
new file mode 100644
index 0000000000..f8b763b9a5
--- /dev/null
+++ b/yt/yt/client/api/journal_reader.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/actions/future.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IJournalReader
+ : public virtual TRefCounted
+{
+ //! Opens the reader.
+ //! No other method can be called prior to the success of this one.
+ virtual TFuture<void> Open() = 0;
+
+ //! Reads another portion of the journal.
+ //! Each row is passed in its own TSharedRef.
+ //! When no more rows remain, an empty vector is returned.
+ //! One must not call #Read again before the previous call is complete.
+ virtual TFuture<std::vector<TSharedRef>> Read() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IJournalReader)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/journal_writer.h b/yt/yt/client/api/journal_writer.h
new file mode 100644
index 0000000000..bdfab47345
--- /dev/null
+++ b/yt/yt/client/api/journal_writer.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/misc/error.h>
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IJournalWriter
+ : public virtual TRefCounted
+{
+ //! Opens the writer.
+ //! No other method can be called prior to the success of this one.
+ virtual TFuture<void> Open() = 0;
+
+ //! Writes another portion of rows into the journal.
+ //! The result is set when the rows are successfully flushed by an appropriate number
+ //! of replicas.
+ virtual TFuture<void> Write(TRange<TSharedRef> rows) = 0;
+
+ //! Closes the writer.
+ //! No other method can be called after this one.
+ virtual TFuture<void> Close() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IJournalWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/operation_archive_schema.cpp b/yt/yt/client/api/operation_archive_schema.cpp
new file mode 100644
index 0000000000..86b3f6aa1d
--- /dev/null
+++ b/yt/yt/client/api/operation_archive_schema.cpp
@@ -0,0 +1,127 @@
+#include "operation_archive_schema.h"
+
+namespace NYT::NApi {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOrderedByIdTableDescriptor::TOrderedByIdTableDescriptor()
+ : NameTable(New<TNameTable>())
+ , Index(NameTable)
+{ }
+
+const TOrderedByIdTableDescriptor& TOrderedByIdTableDescriptor::Get()
+{
+ static const TOrderedByIdTableDescriptor descriptor;
+ return descriptor;
+}
+
+TOrderedByIdTableDescriptor::TIndex::TIndex(const TNameTablePtr& nameTable)
+ : IdHash(nameTable->RegisterName("id_hash"))
+ , IdHi(nameTable->RegisterName("id_hi"))
+ , IdLo(nameTable->RegisterName("id_lo"))
+ , State(nameTable->RegisterName("state"))
+ , AuthenticatedUser(nameTable->RegisterName("authenticated_user"))
+ , OperationType(nameTable->RegisterName("operation_type"))
+ , Progress(nameTable->RegisterName("progress"))
+ , Spec(nameTable->RegisterName("spec"))
+ , BriefProgress(nameTable->RegisterName("brief_progress"))
+ , BriefSpec(nameTable->RegisterName("brief_spec"))
+ , StartTime(nameTable->RegisterName("start_time"))
+ , FinishTime(nameTable->RegisterName("finish_time"))
+ , FilterFactors(nameTable->RegisterName("filter_factors"))
+ , Result(nameTable->RegisterName("result"))
+ , Events(nameTable->RegisterName("events"))
+ , Alerts(nameTable->RegisterName("alerts"))
+ , SlotIndex(nameTable->RegisterName("slot_index"))
+ , UnrecognizedSpec(nameTable->RegisterName("unrecognized_spec"))
+ , FullSpec(nameTable->RegisterName("full_spec"))
+ , RuntimeParameters(nameTable->RegisterName("runtime_parameters"))
+ , SlotIndexPerPoolTree(nameTable->RegisterName("slot_index_per_pool_tree"))
+ , TaskNames(nameTable->RegisterName("task_names"))
+ , ExperimentAssignments(nameTable->RegisterName("experiment_assignments"))
+ , ExperimentAssignmentNames(nameTable->RegisterName("experiment_assignment_names"))
+ , ControllerFeatures(nameTable->RegisterName("controller_features"))
+ , AlertEvents(nameTable->RegisterName("alert_events"))
+ , ProvidedSpec(nameTable->RegisterName("provided_spec"))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOrderedByStartTimeTableDescriptor::TOrderedByStartTimeTableDescriptor()
+ : NameTable(New<TNameTable>())
+ , Index(NameTable)
+{ }
+
+const TOrderedByStartTimeTableDescriptor& TOrderedByStartTimeTableDescriptor::Get()
+{
+ static const TOrderedByStartTimeTableDescriptor descriptor;
+ return descriptor;
+}
+
+TOrderedByStartTimeTableDescriptor::TIndex::TIndex(const TNameTablePtr& nameTable)
+ : StartTime(nameTable->RegisterName("start_time"))
+ , IdHi(nameTable->RegisterName("id_hi"))
+ , IdLo(nameTable->RegisterName("id_lo"))
+ , OperationType(nameTable->RegisterName("operation_type"))
+ , State(nameTable->RegisterName("state"))
+ , AuthenticatedUser(nameTable->RegisterName("authenticated_user"))
+ , FilterFactors(nameTable->RegisterName("filter_factors"))
+ , Pool(nameTable->RegisterName("pool"))
+ , Pools(nameTable->RegisterName("pools"))
+ , HasFailedJobs(nameTable->RegisterName("has_failed_jobs"))
+ , Acl(nameTable->RegisterName("acl"))
+ , PoolTreeToPool(nameTable->RegisterName("pool_tree_to_pool"))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TJobTableDescriptor::TJobTableDescriptor()
+ : NameTable(New<TNameTable>())
+ , Index(NameTable)
+{ }
+
+const TJobTableDescriptor& TJobTableDescriptor::Get()
+{
+ static const TJobTableDescriptor descriptor;
+ return descriptor;
+}
+
+TJobTableDescriptor::TIndex::TIndex(const TNameTablePtr& nameTable)
+ : OperationIdHi(nameTable->RegisterName("operation_id_hi"))
+ , OperationIdLo(nameTable->RegisterName("operation_id_lo"))
+ , JobIdHi(nameTable->RegisterName("job_id_hi"))
+ , JobIdLo(nameTable->RegisterName("job_id_lo"))
+ , Type(nameTable->RegisterName("type"))
+ , State(nameTable->RegisterName("state"))
+ , TransientState(nameTable->RegisterName("transient_state"))
+ , StartTime(nameTable->RegisterName("start_time"))
+ , FinishTime(nameTable->RegisterName("finish_time"))
+ , UpdateTime(nameTable->RegisterName("update_time"))
+ , Address(nameTable->RegisterName("address"))
+ , Error(nameTable->RegisterName("error"))
+ , Statistics(nameTable->RegisterName("statistics"))
+ , BriefStatistics(nameTable->RegisterName("brief_statistics"))
+ , StatisticsLz4(nameTable->RegisterName("statistics_lz4"))
+ , Events(nameTable->RegisterName("events"))
+ , StderrSize(nameTable->RegisterName("stderr_size"))
+ , HasSpec(nameTable->RegisterName("has_spec"))
+ , HasFailContext(nameTable->RegisterName("has_fail_context"))
+ , FailContextSize(nameTable->RegisterName("fail_context_size"))
+ , CoreInfos(nameTable->RegisterName("core_infos"))
+ , JobCompetitionId(nameTable->RegisterName("job_competition_id"))
+ , ProbingJobCompetitionId(nameTable->RegisterName("probing_job_competition_id"))
+ , HasCompetitors(nameTable->RegisterName("has_competitors"))
+ , HasProbingCompetitors(nameTable->RegisterName("has_probing_competitors"))
+ , ExecAttributes(nameTable->RegisterName("exec_attributes"))
+ , TaskName(nameTable->RegisterName("task_name"))
+ , PoolTree(nameTable->RegisterName("pool_tree"))
+ , MonitoringDescriptor(nameTable->RegisterName("monitoring_descriptor"))
+ , JobCookie(nameTable->RegisterName("job_cookie"))
+ , ControllerState(nameTable->RegisterName("controller_state"))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/operation_archive_schema.h b/yt/yt/client/api/operation_archive_schema.h
new file mode 100644
index 0000000000..ed5abd9ce3
--- /dev/null
+++ b/yt/yt/client/api/operation_archive_schema.h
@@ -0,0 +1,133 @@
+#pragma once
+
+#include <yt/yt/client/table_client/name_table.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TOrderedByIdTableDescriptor
+{
+ TOrderedByIdTableDescriptor();
+
+ static const TOrderedByIdTableDescriptor& Get();
+
+ struct TIndex
+ {
+ explicit TIndex(const NTableClient::TNameTablePtr& nameTable);
+
+ const int IdHash;
+ const int IdHi;
+ const int IdLo;
+ const int State;
+ const int AuthenticatedUser;
+ const int OperationType;
+ const int Progress;
+ const int Spec;
+ const int BriefProgress;
+ const int BriefSpec;
+ const int StartTime;
+ const int FinishTime;
+ const int FilterFactors;
+ const int Result;
+ const int Events;
+ const int Alerts;
+ const int SlotIndex; // TODO(renadeen): delete this column when version with this comment will be on every cluster
+ const int UnrecognizedSpec;
+ const int FullSpec;
+ const int RuntimeParameters;
+ const int SlotIndexPerPoolTree;
+ const int TaskNames;
+ const int ExperimentAssignments;
+ const int ExperimentAssignmentNames;
+ const int ControllerFeatures;
+ const int AlertEvents;
+ const int ProvidedSpec;
+ };
+
+ const NTableClient::TNameTablePtr NameTable;
+ const TIndex Index;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TOrderedByStartTimeTableDescriptor
+{
+ TOrderedByStartTimeTableDescriptor();
+
+ static const TOrderedByStartTimeTableDescriptor& Get();
+
+ struct TIndex
+ {
+ explicit TIndex(const NTableClient::TNameTablePtr& nameTable);
+
+ const int StartTime;
+ const int IdHi;
+ const int IdLo;
+ const int OperationType;
+ const int State;
+ const int AuthenticatedUser;
+ const int FilterFactors;
+ const int Pool;
+ const int Pools;
+ const int HasFailedJobs;
+ const int Acl;
+ const int PoolTreeToPool;
+ };
+
+ const NTableClient::TNameTablePtr NameTable;
+ const TIndex Index;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TJobTableDescriptor
+{
+ TJobTableDescriptor();
+
+ static const TJobTableDescriptor& Get();
+
+ struct TIndex
+ {
+ explicit TIndex(const NTableClient::TNameTablePtr& nameTable);
+
+ const int OperationIdHi;
+ const int OperationIdLo;
+ const int JobIdHi;
+ const int JobIdLo;
+ const int Type;
+ const int State;
+ const int TransientState;
+ const int StartTime;
+ const int FinishTime;
+ const int UpdateTime;
+ const int Address;
+ const int Error;
+ const int Statistics;
+ const int BriefStatistics;
+ const int StatisticsLz4;
+ const int Events;
+ const int StderrSize;
+ const int HasSpec;
+ const int HasFailContext;
+ const int FailContextSize;
+ const int CoreInfos;
+ const int JobCompetitionId;
+ const int ProbingJobCompetitionId;
+ const int HasCompetitors;
+ const int HasProbingCompetitors;
+ const int ExecAttributes;
+ const int TaskName;
+ const int PoolTree;
+ const int MonitoringDescriptor;
+ const int JobCookie;
+ const int ControllerState;
+ };
+
+ const NTableClient::TNameTablePtr NameTable;
+ const TIndex Index;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} //namespace NYT::NApi
diff --git a/yt/yt/client/api/operation_client.cpp b/yt/yt/client/api/operation_client.cpp
new file mode 100644
index 0000000000..b2f6420052
--- /dev/null
+++ b/yt/yt/client/api/operation_client.cpp
@@ -0,0 +1,226 @@
+#include "operation_client.h"
+
+#include <yt/yt/client/job_tracker_client/helpers.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NApi {
+
+using namespace NYTree;
+using namespace NJobTrackerClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(
+ const TOperation& operation,
+ NYson::IYsonConsumer* consumer,
+ bool needType,
+ bool needOperationType,
+ bool idWithAttributes)
+{
+ auto fillItems = [&] (TFluentMap fluent) {
+ fluent
+ .OptionalItem("id", operation.Id)
+ .OptionalItem("state", operation.State)
+ .DoIf(operation.Type.operator bool(), [&] (TFluentMap fluent) {
+ if (needType) {
+ fluent.Item("type").Value(operation.Type);
+ }
+ if (needOperationType) {
+ fluent.Item("operation_type").Value(operation.Type);
+ }
+ })
+ .OptionalItem("authenticated_user", operation.AuthenticatedUser)
+ .OptionalItem("start_time", operation.StartTime)
+ .OptionalItem("finish_time", operation.FinishTime)
+ .OptionalItem("brief_progress", operation.BriefProgress)
+ .OptionalItem("progress", operation.Progress)
+ .OptionalItem("brief_spec", operation.BriefSpec)
+ .OptionalItem("full_spec", operation.FullSpec)
+ .OptionalItem("spec", operation.Spec)
+ .OptionalItem("provided_spec", operation.ProvidedSpec)
+ .OptionalItem("experiment_assignments", operation.ExperimentAssignments)
+ .OptionalItem("experiment_assignment_names", operation.ExperimentAssignmentNames)
+ .OptionalItem("unrecognized_spec", operation.UnrecognizedSpec)
+ .OptionalItem("runtime_parameters", operation.RuntimeParameters)
+ .OptionalItem("suspended", operation.Suspended)
+ .OptionalItem("result", operation.Result)
+ .OptionalItem("events", operation.Events)
+ .OptionalItem("slot_index_per_pool_tree", operation.SlotIndexPerPoolTree)
+ .OptionalItem("alerts", operation.Alerts)
+ .OptionalItem("alert_events", operation.AlertEvents)
+ .OptionalItem("task_names", operation.TaskNames)
+ .OptionalItem("controller_features", operation.ControllerFeatures)
+ .DoIf(operation.OtherAttributes.operator bool(), [&] (TFluentMap fluent) {
+ for (const auto& [key, value] : operation.OtherAttributes->ListPairs()) {
+ fluent.Item(key).Value(value);
+ }
+ });
+ };
+
+ if (idWithAttributes) {
+ if (!operation.Id) {
+ THROW_ERROR_EXCEPTION(
+ "Cannot serialize operation in id-with-attributes format "
+ "as \"id\" attribute is missing from attribute filter");
+ }
+ BuildYsonFluently(consumer)
+ .BeginAttributes()
+ .Do(fillItems)
+ .EndAttributes()
+ .Value(*operation.Id);
+ } else {
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Do(fillItems)
+ .EndMap();
+ }
+}
+
+void Deserialize(TOperation& operation, NYTree::IAttributeDictionaryPtr attributes, bool clone)
+{
+ if (clone) {
+ attributes = attributes->Clone();
+ }
+
+ auto setField = [&] (auto& field, const TString& name) {
+ using T = std::remove_reference_t<decltype(field)>;
+ if constexpr (std::is_same_v<T, NYson::TYsonString>) {
+ if (auto value = attributes->FindYson(name)) {
+ field = std::move(value);
+ attributes->Remove(name);
+ } else {
+ field = {};
+ }
+ } else {
+ using TValue = typename TOptionalTraits<T>::TValue;
+ if (auto value = attributes->FindAndRemove<TValue>(name)) {
+ field = std::move(value);
+ } else {
+ field.reset();
+ }
+ }
+ };
+
+ setField(operation.Id, "id");
+ setField(operation.Type, "type");
+ setField(operation.State, "state");
+ setField(operation.StartTime, "start_time");
+ setField(operation.FinishTime, "finish_time");
+ setField(operation.AuthenticatedUser, "authenticated_user");
+ setField(operation.BriefSpec, "brief_spec");
+ setField(operation.Spec, "spec");
+ setField(operation.ProvidedSpec, "provided_spec");
+ setField(operation.ExperimentAssignments, "experiment_assignments");
+ setField(operation.ExperimentAssignmentNames, "experiment_assignment_names");
+ setField(operation.FullSpec, "full_spec");
+ setField(operation.UnrecognizedSpec, "unrecognized_spec");
+ setField(operation.BriefProgress, "brief_progress");
+ setField(operation.Progress, "progress");
+ setField(operation.RuntimeParameters, "runtime_parameters");
+ setField(operation.Suspended, "suspended");
+ setField(operation.Events, "events");
+ setField(operation.Result, "result");
+ setField(operation.SlotIndexPerPoolTree, "slot_index_per_pool_tree");
+ setField(operation.Alerts, "alerts");
+ setField(operation.AlertEvents, "alert_events");
+ setField(operation.TaskNames, "task_names");
+ setField(operation.ControllerFeatures, "controller_features");
+
+ operation.OtherAttributes = std::move(attributes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<EJobState> TJob::GetState() const
+{
+ if (ArchiveState && ControllerState) {
+ if (IsJobInProgress(*ArchiveState)) {
+ return ControllerState;
+ } else {
+ return ArchiveState;
+ }
+ } else if (ArchiveState) {
+ return ArchiveState;
+ } else if (ControllerState) {
+ return ControllerState;
+ }
+ return std::nullopt;
+}
+
+// Tries to find "abort_reason" attribute in the error and parse it as |EAbortReason|.
+// Returns |std::nullopt| if the attribute is not found or any of two parsings is unsuccessful.
+static std::optional<NScheduler::EAbortReason> TryGetJobAbortReasonFromError(const NYson::TYsonString& errorYson)
+{
+ if (!errorYson) {
+ return std::nullopt;
+ }
+
+ TError error;
+ try {
+ error = ConvertTo<TError>(errorYson);
+ } catch (const std::exception& exception) {
+ return std::nullopt;
+ }
+
+ if (auto yson = error.Attributes().FindYson("abort_reason")) {
+ try {
+ return ConvertTo<NScheduler::EAbortReason>(yson);
+ } catch (const std::exception& exception) {
+ return std::nullopt;
+ }
+ }
+
+ return std::nullopt;
+}
+
+void Serialize(const TJob& job, NYson::IYsonConsumer* consumer, TStringBuf idKey)
+{
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .OptionalItem(idKey, job.Id)
+ .OptionalItem("operation_id", job.OperationId)
+ .OptionalItem("type", job.Type)
+ .OptionalItem("state", job.GetState())
+ .OptionalItem("controller_state", job.ControllerState)
+ .OptionalItem("archive_state", job.ArchiveState)
+ .OptionalItem("address", job.Address)
+ .OptionalItem("start_time", job.StartTime)
+ .OptionalItem("finish_time", job.FinishTime)
+ .OptionalItem("has_spec", job.HasSpec)
+ .OptionalItem("job_competition_id", job.JobCompetitionId)
+ .OptionalItem("probing_job_competition_id", job.ProbingJobCompetitionId)
+ .OptionalItem("has_competitors", job.HasCompetitors)
+ .OptionalItem("has_probing_competitors", job.HasProbingCompetitors)
+ .OptionalItem("progress", job.Progress)
+ .OptionalItem("stderr_size", job.StderrSize)
+ .OptionalItem("fail_context_size", job.FailContextSize)
+ .OptionalItem("error", job.Error)
+ .OptionalItem("abort_reason", TryGetJobAbortReasonFromError(job.Error))
+ .OptionalItem("brief_statistics", job.BriefStatistics)
+ .OptionalItem("input_paths", job.InputPaths)
+ .OptionalItem("core_infos", job.CoreInfos)
+ .OptionalItem("events", job.Events)
+ .OptionalItem("statistics", job.Statistics)
+ .OptionalItem("exec_attributes", job.ExecAttributes)
+ .OptionalItem("task_name", job.TaskName)
+ .OptionalItem("pool_tree", job.PoolTree)
+ .OptionalItem("pool", job.Pool)
+ .OptionalItem("monitoring_descriptor", job.MonitoringDescriptor)
+ .OptionalItem("is_stale", job.IsStale)
+ .OptionalItem("job_cookie", job.JobCookie)
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TListOperationsAccessFilter::Register(TRegistrar registrar)
+{
+ registrar.Parameter("subject", &TThis::Subject);
+ registrar.Parameter("permissions", &TThis::Permissions);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/operation_client.h b/yt/yt/client/api/operation_client.h
new file mode 100644
index 0000000000..364dabd409
--- /dev/null
+++ b/yt/yt/client/api/operation_client.h
@@ -0,0 +1,447 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/client/scheduler/operation_id_or_alias.h>
+
+#include <yt/yt/client/scheduler/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TStartOperationOptions
+ : public TTimeoutOptions
+ , public TTransactionalOptions
+ , public TMutatingOptions
+{ };
+
+struct TAbortOperationOptions
+ : public TTimeoutOptions
+{
+ std::optional<TString> AbortMessage;
+};
+
+struct TSuspendOperationOptions
+ : public TTimeoutOptions
+{
+ bool AbortRunningJobs = false;
+};
+
+struct TResumeOperationOptions
+ : public TTimeoutOptions
+{ };
+
+struct TCompleteOperationOptions
+ : public TTimeoutOptions
+{ };
+
+struct TUpdateOperationParametersOptions
+ : public TTimeoutOptions
+{ };
+
+struct TDumpJobContextOptions
+ : public TTimeoutOptions
+{ };
+
+//! Source to fetch job spec from. Useful in tests.
+DEFINE_BIT_ENUM_WITH_UNDERLYING_TYPE(EJobSpecSource, ui16,
+ //! Job spec is fetched from exec node.
+ ((Node) (1))
+
+ //! Job spec is fetched from job archive.
+ ((Archive) (2))
+
+ //! Job spec is fetched from any available source.
+ ((Auto) (0xFFFF))
+);
+
+struct TGetJobInputOptions
+ : public TTimeoutOptions
+{
+ //! Where job spec should be retrieved from.
+ EJobSpecSource JobSpecSource = EJobSpecSource::Auto;
+};
+
+struct TGetJobInputPathsOptions
+ : public TTimeoutOptions
+{
+ //! Where job spec should be retrieved from.
+ EJobSpecSource JobSpecSource = EJobSpecSource::Auto;
+};
+
+struct TGetJobSpecOptions
+ : public TTimeoutOptions
+{
+ //! Where job spec should be retrieved from.
+ EJobSpecSource JobSpecSource = EJobSpecSource::Auto;
+
+ bool OmitNodeDirectory = false;
+ bool OmitInputTableSpecs = false;
+ bool OmitOutputTableSpecs = false;
+};
+
+struct TGetJobStderrOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+{ };
+
+struct TGetJobFailContextOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+{ };
+
+struct TListOperationsAccessFilter
+ : public NYTree::TYsonStruct
+{
+ TString Subject;
+ NYTree::EPermissionSet Permissions;
+
+ // This parameter cannot be set from YSON, it must be computed.
+ THashSet<TString> SubjectTransitiveClosure;
+
+ REGISTER_YSON_STRUCT(TListOperationsAccessFilter);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TListOperationsAccessFilter)
+
+struct TListOperationsOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+{
+ std::optional<TInstant> FromTime;
+ std::optional<TInstant> ToTime;
+ std::optional<TInstant> CursorTime;
+ EOperationSortDirection CursorDirection = EOperationSortDirection::Past;
+ std::optional<TString> UserFilter;
+
+ TListOperationsAccessFilterPtr AccessFilter;
+
+ std::optional<NScheduler::EOperationState> StateFilter;
+ std::optional<NScheduler::EOperationType> TypeFilter;
+ std::optional<TString> SubstrFilter;
+ std::optional<TString> PoolTree;
+ std::optional<TString> Pool;
+ std::optional<bool> WithFailedJobs;
+ bool IncludeArchive = false;
+ bool IncludeCounters = true;
+ ui64 Limit = 100;
+
+ std::optional<THashSet<TString>> Attributes;
+
+ // TODO(ignat): Remove this mode when UI migrate to list_operations without enabled UI mode.
+ // See st/YTFRONT-1360.
+ bool EnableUIMode = false;
+
+ TDuration ArchiveFetchingTimeout = TDuration::Seconds(3);
+
+ TListOperationsOptions()
+ {
+ ReadFrom = EMasterChannelKind::Cache;
+ }
+};
+
+struct TPollJobShellResponse
+{
+ NYson::TYsonString Result;
+ // YT-14507: Logging context is required for SOC audit.
+ NYson::TYsonString LoggingContext;
+};
+
+DEFINE_ENUM(EJobSortField,
+ ((None) (0))
+ ((Type) (1))
+ ((State) (2))
+ ((StartTime) (3))
+ ((FinishTime) (4))
+ ((Address) (5))
+ ((Duration) (6))
+ ((Progress) (7))
+ ((Id) (8))
+);
+
+DEFINE_ENUM(EJobSortDirection,
+ ((Ascending) (0))
+ ((Descending) (1))
+);
+
+DEFINE_ENUM(EDataSource,
+ ((Archive) (0))
+ ((Runtime) (1))
+ ((Auto) (2))
+ // Should be used only in tests.
+ ((Manual) (3))
+);
+
+struct TListJobsOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+{
+ NJobTrackerClient::TJobId JobCompetitionId;
+ std::optional<NJobTrackerClient::EJobType> Type;
+ std::optional<NJobTrackerClient::EJobState> State;
+ std::optional<TString> Address;
+ std::optional<bool> WithStderr;
+ std::optional<bool> WithFailContext;
+ std::optional<bool> WithSpec;
+ std::optional<bool> WithCompetitors;
+ std::optional<TString> TaskName;
+
+ TDuration RunningJobsLookbehindPeriod = TDuration::Max();
+
+ EJobSortField SortField = EJobSortField::None;
+ EJobSortDirection SortOrder = EJobSortDirection::Ascending;
+
+ i64 Limit = 1000;
+ i64 Offset = 0;
+
+ // All options below are deprecated.
+ bool IncludeCypress = false;
+ bool IncludeControllerAgent = false;
+ bool IncludeArchive = false;
+ EDataSource DataSource = EDataSource::Auto;
+};
+
+struct TAbandonJobOptions
+ : public TTimeoutOptions
+{ };
+
+struct TPollJobShellOptions
+ : public TTimeoutOptions
+{ };
+
+struct TAbortJobOptions
+ : public TTimeoutOptions
+{
+ std::optional<TDuration> InterruptTimeout;
+};
+
+struct TGetOperationOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+{
+ std::optional<THashSet<TString>> Attributes;
+ TDuration ArchiveTimeout = TDuration::Seconds(5);
+ TDuration MaximumCypressProgressAge = TDuration::Minutes(2);
+ bool IncludeRuntime = false;
+};
+
+struct TGetJobOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+{
+ std::optional<THashSet<TString>> Attributes;
+};
+
+struct TOperation
+{
+ std::optional<NScheduler::TOperationId> Id;
+
+ std::optional<NScheduler::EOperationType> Type;
+ std::optional<NScheduler::EOperationState> State;
+
+ std::optional<TInstant> StartTime;
+ std::optional<TInstant> FinishTime;
+
+ std::optional<TString> AuthenticatedUser;
+
+ NYson::TYsonString BriefSpec;
+ NYson::TYsonString Spec;
+ NYson::TYsonString ProvidedSpec;
+ NYson::TYsonString ExperimentAssignments;
+ NYson::TYsonString ExperimentAssignmentNames;
+ NYson::TYsonString FullSpec;
+ NYson::TYsonString UnrecognizedSpec;
+
+ NYson::TYsonString BriefProgress;
+ NYson::TYsonString Progress;
+
+ NYson::TYsonString RuntimeParameters;
+
+ std::optional<bool> Suspended;
+
+ NYson::TYsonString Events;
+ NYson::TYsonString Result;
+
+ NYson::TYsonString SlotIndexPerPoolTree;
+ NYson::TYsonString Alerts;
+ NYson::TYsonString AlertEvents;
+
+ NYson::TYsonString TaskNames;
+
+ NYson::TYsonString ControllerFeatures;
+
+ NYTree::IAttributeDictionaryPtr OtherAttributes;
+};
+
+void Serialize(
+ const TOperation& operation,
+ NYson::IYsonConsumer* consumer,
+ bool needType = true,
+ bool needOperationType = false,
+ bool idWithAttributes = false);
+
+void Deserialize(TOperation& operation, NYTree::IAttributeDictionaryPtr attriubutes, bool clone = true);
+
+struct TListOperationsResult
+{
+ std::vector<TOperation> Operations;
+ std::optional<THashMap<TString, i64>> PoolTreeCounts;
+ std::optional<THashMap<TString, i64>> PoolCounts;
+ std::optional<THashMap<TString, i64>> UserCounts;
+ std::optional<TEnumIndexedVector<NScheduler::EOperationState, i64>> StateCounts;
+ std::optional<TEnumIndexedVector<NScheduler::EOperationType, i64>> TypeCounts;
+ std::optional<i64> FailedJobsCount;
+ bool Incomplete = false;
+};
+
+struct TJob
+{
+ NJobTrackerClient::TJobId Id;
+ NJobTrackerClient::TJobId OperationId;
+ std::optional<NJobTrackerClient::EJobType> Type;
+ std::optional<NJobTrackerClient::EJobState> ControllerState;
+ std::optional<NJobTrackerClient::EJobState> ArchiveState;
+ std::optional<TInstant> StartTime;
+ std::optional<TInstant> FinishTime;
+ std::optional<TString> Address;
+ std::optional<double> Progress;
+ std::optional<ui64> StderrSize;
+ std::optional<ui64> FailContextSize;
+ std::optional<bool> HasSpec;
+ std::optional<bool> HasCompetitors;
+ std::optional<bool> HasProbingCompetitors;
+ NJobTrackerClient::TJobId JobCompetitionId;
+ NJobTrackerClient::TJobId ProbingJobCompetitionId;
+ NYson::TYsonString Error;
+ NYson::TYsonString BriefStatistics;
+ NYson::TYsonString Statistics;
+ NYson::TYsonString InputPaths;
+ NYson::TYsonString CoreInfos;
+ NYson::TYsonString Events;
+ NYson::TYsonString ExecAttributes;
+ std::optional<TString> TaskName;
+ std::optional<TString> PoolTree;
+ std::optional<TString> Pool;
+ std::optional<TString> MonitoringDescriptor;
+ std::optional<ui64> JobCookie;
+
+ std::optional<bool> IsStale;
+
+ std::optional<NJobTrackerClient::EJobState> GetState() const;
+};
+
+void Serialize(const TJob& job, NYson::IYsonConsumer* consumer, TStringBuf idKey);
+
+struct TListJobsStatistics
+{
+ TEnumIndexedVector<NJobTrackerClient::EJobState, i64> StateCounts;
+ TEnumIndexedVector<NJobTrackerClient::EJobType, i64> TypeCounts;
+};
+
+struct TListJobsResult
+{
+ std::vector<TJob> Jobs;
+ std::optional<int> CypressJobCount;
+ std::optional<int> ControllerAgentJobCount;
+ std::optional<int> ArchiveJobCount;
+
+ TListJobsStatistics Statistics;
+
+ std::vector<TError> Errors;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IOperationClient
+{
+ virtual TFuture<NScheduler::TOperationId> StartOperation(
+ NScheduler::EOperationType type,
+ const NYson::TYsonString& spec,
+ const TStartOperationOptions& options = {}) = 0;
+
+ virtual TFuture<void> AbortOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TAbortOperationOptions& options = {}) = 0;
+
+ virtual TFuture<void> SuspendOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TSuspendOperationOptions& options = {}) = 0;
+
+ virtual TFuture<void> ResumeOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TResumeOperationOptions& options = {}) = 0;
+
+ virtual TFuture<void> CompleteOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TCompleteOperationOptions& options = {}) = 0;
+
+ virtual TFuture<void> UpdateOperationParameters(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NYson::TYsonString& parameters,
+ const TUpdateOperationParametersOptions& options = {}) = 0;
+
+ virtual TFuture<TOperation> GetOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TGetOperationOptions& options = {}) = 0;
+
+ virtual TFuture<void> DumpJobContext(
+ NJobTrackerClient::TJobId jobId,
+ const NYPath::TYPath& path,
+ const TDumpJobContextOptions& options = {}) = 0;
+
+ virtual TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr> GetJobInput(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputOptions& options = {}) = 0;
+
+ virtual TFuture<NYson::TYsonString> GetJobInputPaths(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputPathsOptions& options = {}) = 0;
+
+ virtual TFuture<NYson::TYsonString> GetJobSpec(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobSpecOptions& options = {}) = 0;
+
+ virtual TFuture<TSharedRef> GetJobStderr(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobStderrOptions& options = {}) = 0;
+
+ virtual TFuture<TSharedRef> GetJobFailContext(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobFailContextOptions& options = {}) = 0;
+
+ virtual TFuture<TListOperationsResult> ListOperations(
+ const TListOperationsOptions& options = {}) = 0;
+
+ virtual TFuture<TListJobsResult> ListJobs(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TListJobsOptions& options = {}) = 0;
+
+ virtual TFuture<NYson::TYsonString> GetJob(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobOptions& options = {}) = 0;
+
+ virtual TFuture<void> AbandonJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbandonJobOptions& options = {}) = 0;
+
+ virtual TFuture<TPollJobShellResponse> PollJobShell(
+ NJobTrackerClient::TJobId jobId,
+ const std::optional<TString>& shellName,
+ const NYson::TYsonString& parameters,
+ const TPollJobShellOptions& options = {}) = 0;
+
+ virtual TFuture<void> AbortJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbortJobOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/persistent_queue.cpp b/yt/yt/client/api/persistent_queue.cpp
new file mode 100644
index 0000000000..fa3274b616
--- /dev/null
+++ b/yt/yt/client/api/persistent_queue.cpp
@@ -0,0 +1,1054 @@
+#include "persistent_queue.h"
+#include "client.h"
+#include "transaction.h"
+#include "config.h"
+#include "private.h"
+
+#include <yt/yt/client/table_client/column_sort_schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/helpers.h>
+
+#include <yt/yt/core/ytree/helpers.h>
+
+#include <yt/yt/core/concurrency/thread_affinity.h>
+#include <yt/yt/core/concurrency/periodic_executor.h>
+#include <yt/yt/core/concurrency/delayed_executor.h>
+
+namespace NYT::NApi {
+
+using namespace NYPath;
+using namespace NYTree;
+using namespace NConcurrency;
+using namespace NTableClient;
+using namespace NObjectClient;
+using namespace NTransactionClient;
+using namespace NApi;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPersistentQueuePollerBufferTag
+{ };
+
+namespace {
+
+DEFINE_ENUM(ERowState,
+ ((Consumed) (0))
+ ((ConsumedAndTrimmed) (1))
+);
+
+struct TStateTableRow
+{
+ int TabletIndex;
+ i64 RowIndex;
+ ERowState State;
+};
+
+struct TStateTable
+{
+ static const TString TabletIndexColumnName;
+ static const TString RowIndexColumnName;
+ static const TString StateColumnName;
+};
+
+const TString TStateTable::TabletIndexColumnName("tablet_index");
+const TString TStateTable::RowIndexColumnName("row_index");
+const TString TStateTable::StateColumnName("state");
+
+std::vector<int> PrepareTabletIndexes(std::vector<int> tabletIndexes)
+{
+ std::sort(tabletIndexes.begin(), tabletIndexes.end());
+ tabletIndexes.erase(std::unique(tabletIndexes.begin(), tabletIndexes.end()), tabletIndexes.end());
+ return tabletIndexes;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPersistentQueuePoller::TImpl
+ : public TRefCounted
+{
+public:
+ TImpl(
+ TPersistentQueuePollerConfigPtr config,
+ IClientPtr client,
+ const TYPath& dataTablePath,
+ const TYPath& stateTablePath,
+ const std::vector<int>& tabletIndexes)
+ : Config_(std::move(config))
+ , Client_(std::move(client))
+ , DataTablePath_(dataTablePath)
+ , StateTablePath_(stateTablePath)
+ , TabletIndexes_(PrepareTabletIndexes(tabletIndexes))
+ , PollerId_(TGuid::Create())
+ , Logger(ApiLogger.WithTag("PollerId: %v", PollerId_))
+ , Invoker_(Client_->GetConnection()->GetInvoker())
+ {
+ YT_VERIFY(Config_);
+
+ RecreateState(false);
+
+ YT_LOG_INFO("Persistent queue poller initialized (DataTablePath: %v, StateTablePath: %v, TabletIndexes: %v)",
+ DataTablePath_,
+ StateTablePath_,
+ TabletIndexes_);
+
+ for (int tabletIndex : TabletIndexes_) {
+ auto executor = New<TPeriodicExecutor>(
+ Invoker_,
+ BIND(&TImpl::FetchTablet, MakeWeak(this), tabletIndex),
+ Config_->DataPollPeriod);
+ PollExecutors_.push_back(executor);
+ executor->Start();
+ }
+
+ {
+ TrimExecutor_ = New<TPeriodicExecutor>(
+ Invoker_,
+ BIND(&TImpl::Trim, MakeWeak(this)),
+ Config_->StateTrimPeriod);
+ TrimExecutor_->Start();
+ }
+ }
+
+ TFuture<IPersistentQueueRowsetPtr> Poll()
+ {
+ VERIFY_THREAD_AFFINITY_ANY();
+
+ auto promise = NewPromise<IPersistentQueueRowsetPtr>();
+ auto state = GetState();
+ auto guard = Guard(state->SpinLock);
+ state->Promises.push_back(promise);
+ TryFulfillPromises(state, &guard);
+ return promise;
+ }
+
+private:
+ struct TBatch
+ {
+ IUnversionedRowsetPtr Rowset;
+ int RowCount;
+ i64 DataWeight;
+ int TabletIndex;
+ i64 RowsetStartRowIndex;
+ i64 BeginRowIndex;
+ i64 EndRowIndex;
+ };
+
+ struct TTablet
+ {
+ THashSet<i64> ConsumedRowIndexes;
+ i64 MaxConsumedRowIndex = std::numeric_limits<i64>::min();
+ i64 FetchRowIndex = std::numeric_limits<i64>::max();
+ i64 LastTrimmedRowIndex = std::numeric_limits<i64>::min();
+ };
+
+ struct TState
+ : public TRefCounted
+ {
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock);
+ std::deque<TPromise<IPersistentQueueRowsetPtr>> Promises;
+ std::deque<TBatch> Batches;
+ int BatchesRowCount = 0;
+ i64 BatchesDataWeight = 0;
+ THashMap<int, TTablet> TabletMap;
+ std::atomic<bool> Failed = {false};
+ };
+
+ using TStatePtr = TIntrusivePtr<TState>;
+
+ class TPolledRowset
+ : public IPersistentQueueRowset
+ {
+ public:
+ TPolledRowset(
+ TIntrusivePtr<TImpl> owner,
+ TStatePtr state,
+ TBatch batch)
+ : Owner_(std::move(owner))
+ , State_(std::move(state))
+ , Batch_(std::move(batch))
+ { }
+
+ ~TPolledRowset()
+ {
+ if (!Committed_) {
+ Owner_->ReclaimBatch(State_, std::move(Batch_));
+ }
+ }
+
+ const TTableSchemaPtr& GetSchema() const override
+ {
+ return Batch_.Rowset->GetSchema();
+ }
+
+ const TNameTablePtr& GetNameTable() const override
+ {
+ return Batch_.Rowset->GetNameTable();
+ }
+
+ TSharedRange<TUnversionedRow> GetRows() const override
+ {
+ return Batch_.Rowset->GetRows().Slice(
+ Batch_.BeginRowIndex - Batch_.RowsetStartRowIndex,
+ Batch_.EndRowIndex - Batch_.RowsetStartRowIndex);
+ }
+
+ TFuture<void> Confirm(const ITransactionPtr& transaction) override
+ {
+ transaction->SubscribeCommitted(BIND(&TPolledRowset::OnCommitted, MakeStrong(this)));
+ return Owner_->ConfirmBatch(State_, Batch_, transaction);
+ }
+
+ private:
+ const TIntrusivePtr<TImpl> Owner_;
+ const TStatePtr State_;
+ const TBatch Batch_;
+
+ bool Committed_ = false;
+
+
+ void OnCommitted()
+ {
+ Owner_->OnBatchCommitted(Batch_);
+ Committed_ = true;
+ }
+ };
+
+
+ const TPersistentQueuePollerConfigPtr Config_;
+ const IClientPtr Client_;
+ const NYPath::TYPath DataTablePath_;
+ const NYPath::TYPath StateTablePath_;
+ const std::vector<int> TabletIndexes_;
+
+ const TGuid PollerId_;
+ const NLogging::TLogger Logger;
+ const IInvokerPtr Invoker_;
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock_);
+ TStatePtr State_;
+
+ std::vector<TPeriodicExecutorPtr> PollExecutors_;
+ TPeriodicExecutorPtr TrimExecutor_;
+
+
+ TStatePtr GetState()
+ {
+ auto guard = Guard(SpinLock_);
+ return State_;
+ }
+
+
+ std::vector<TStateTableRow> ReadStateTable(const IClientBasePtr& client)
+ {
+ // TODO(babenko): escaping
+ auto query = Format(
+ "[%v], [%v], [%v] from [%v] where [%v] in (%v)",
+ TStateTable::TabletIndexColumnName,
+ TStateTable::RowIndexColumnName,
+ TStateTable::StateColumnName,
+ StateTablePath_,
+ TStateTable::TabletIndexColumnName,
+ JoinToString(TabletIndexes_));
+ auto result = WaitFor(client->SelectRows(query))
+ .ValueOrThrow();
+ const auto& rowset = result.Rowset;
+ const auto& schema = rowset->GetSchema();
+ auto tabletIndexColumnId = schema->GetColumnIndexOrThrow(TStateTable::TabletIndexColumnName);
+ auto rowIndexColumnId = schema->GetColumnIndexOrThrow(TStateTable::RowIndexColumnName);
+ auto stateColumnId = schema->GetColumnIndexOrThrow(TStateTable::StateColumnName);
+
+ std::vector<TStateTableRow> rows;
+
+ for (auto row : rowset->GetRows()) {
+ TStateTableRow stateRow;
+
+ YT_ASSERT(row[tabletIndexColumnId].Type == EValueType::Int64);
+ stateRow.TabletIndex = static_cast<int>(row[tabletIndexColumnId].Data.Int64);
+
+ YT_ASSERT(row[rowIndexColumnId].Type == EValueType::Int64);
+ stateRow.RowIndex = row[rowIndexColumnId].Data.Int64;
+
+ YT_ASSERT(row[rowIndexColumnId].Type == EValueType::Int64);
+ stateRow.State = ERowState(row[stateColumnId].Data.Int64);
+
+ rows.push_back(stateRow);
+ }
+
+ return rows;
+ }
+
+
+ void DoLoadState(const TStatePtr& state)
+ {
+ YT_LOG_INFO("Loading queue poller state for initialization");
+
+ auto stateRows = ReadStateTable(Client_);
+
+ auto guard = Guard(state->SpinLock);
+
+ for (auto& [tabletId, tabletState] : state->TabletMap) {
+ tabletState.FetchRowIndex = 0;
+ tabletState.LastTrimmedRowIndex = -1;
+ }
+
+ for (const auto& row : stateRows) {
+ auto& tabletState = GetOrCrash(state->TabletMap, row.TabletIndex);
+
+ tabletState.ConsumedRowIndexes.insert(row.RowIndex);
+ tabletState.MaxConsumedRowIndex = std::max(tabletState.MaxConsumedRowIndex, row.RowIndex);
+
+ if (row.State == ERowState::ConsumedAndTrimmed) {
+ tabletState.FetchRowIndex = row.RowIndex;
+ tabletState.LastTrimmedRowIndex = std::max(tabletState.LastTrimmedRowIndex, row.RowIndex);
+ }
+ }
+
+ for (auto& [tabletId, tablet] : state->TabletMap) {
+ while (tablet.ConsumedRowIndexes.find(tablet.FetchRowIndex) != tablet.ConsumedRowIndexes.end()) {
+ YT_VERIFY(tablet.ConsumedRowIndexes.erase(tablet.FetchRowIndex) == 1);
+ ++tablet.FetchRowIndex;
+ }
+ }
+
+ for (const auto& [tabletIndex, tablet] : state->TabletMap) {
+ YT_LOG_DEBUG("Tablet state collected (TabletIndex: %v, ConsumedRowIndexes: %v, FetchRowIndex: %v)",
+ tabletIndex,
+ tablet.ConsumedRowIndexes,
+ tablet.FetchRowIndex);
+ }
+
+ YT_LOG_INFO("Queue poller state loaded");
+ }
+
+ void LoadState(const TStatePtr& state)
+ {
+ try {
+ DoLoadState(state);
+ } catch (const std::exception& ex) {
+ OnStateFailed(state);
+ YT_LOG_ERROR(ex, "Error loading queue poller state");
+ }
+ }
+
+ void RecreateState(bool backoff)
+ {
+ auto state = New<TState>();
+ for (int tabletIndex : TabletIndexes_) {
+ YT_VERIFY(state->TabletMap.emplace(tabletIndex, TTablet()).second);
+ }
+
+ {
+ auto guard = Guard(SpinLock_);
+ if (State_) {
+ state->Promises = std::move(State_->Promises);
+ }
+ State_ = state;
+ }
+
+ TDelayedExecutor::Submit(
+ BIND(&TImpl::LoadState, MakeStrong(this), state),
+ backoff ? Config_->BackoffTime : TDuration::Zero());
+ }
+
+
+ void DoFetchTablet(int tabletIndex)
+ {
+ auto state = GetState();
+ if (state->Failed) {
+ return;
+ }
+
+ auto& tablet = GetOrCrash(state->TabletMap, tabletIndex);
+
+ auto rowLimit = Config_->MaxRowsPerFetch;
+ {
+ auto guard = Guard(state->SpinLock);
+ if (tablet.FetchRowIndex > tablet.LastTrimmedRowIndex + Config_->MaxFetchedUntrimmedRowCount) {
+ YT_LOG_INFO("Number of fetched but trimmed rows exceeds the limit; fetching new rows suspended (TabletIndex: %v, RowCount: %v, Limit: %v)",
+ tabletIndex,
+ tablet.FetchRowIndex - tablet.LastTrimmedRowIndex,
+ Config_->MaxFetchedUntrimmedRowCount);
+ return;
+ }
+ if (state->BatchesDataWeight > Config_->MaxPrefetchDataWeight) {
+ return;
+ }
+ rowLimit = std::min(rowLimit, Config_->MaxPrefetchRowCount - state->BatchesRowCount);
+ }
+
+ if (rowLimit <= 0) {
+ return;
+ }
+
+ YT_LOG_DEBUG("Started fetching data (TabletIndex: %v, FetchRowIndex: %v, RowLimit: %v)",
+ tabletIndex,
+ tablet.FetchRowIndex,
+ rowLimit);
+
+ // TODO(babenko): escaping
+ auto query = Format(
+ "* from [%v] where [%v] = %v and [%v] between %v and %v order by [%v] limit %v",
+ DataTablePath_,
+ TabletIndexColumnName,
+ tabletIndex,
+ RowIndexColumnName,
+ tablet.FetchRowIndex,
+ tablet.FetchRowIndex + rowLimit - 1,
+ RowIndexColumnName,
+ rowLimit);
+ auto result = WaitFor(Client_->SelectRows(query))
+ .ValueOrThrow();
+ const auto& rowset = result.Rowset;
+ const auto& schema = rowset->GetSchema();
+ auto rows = rowset->GetRows();
+
+ YT_LOG_DEBUG("Finished fetching data (TabletIndex: %v, RowCount: %v)",
+ tabletIndex,
+ rows.Size());
+
+ if (rows.Empty()) {
+ return;
+ }
+
+ auto rowIndexColumnId = schema->GetColumnIndexOrThrow(RowIndexColumnName);
+
+ std::vector<TBatch> batches;
+ i64 currentRowIndex = tablet.FetchRowIndex;
+ i64 batchBeginRowIndex = -1;
+
+
+ auto beginBatch = [&] () {
+ YT_VERIFY(batchBeginRowIndex < 0);
+ batchBeginRowIndex = currentRowIndex;
+ };
+
+ auto endBatch = [&] () {
+ if (batchBeginRowIndex < 0) {
+ return;
+ }
+
+ i64 batchEndRowIndex = currentRowIndex;
+ YT_VERIFY(batchBeginRowIndex < batchEndRowIndex);
+
+ TBatch batch;
+ batch.TabletIndex = tabletIndex;
+ batch.Rowset = rowset;
+ batch.RowCount = static_cast<int>(batchEndRowIndex - batchBeginRowIndex);
+ batch.RowsetStartRowIndex = tablet.FetchRowIndex;
+ batch.BeginRowIndex = batchBeginRowIndex;
+ batch.EndRowIndex = batchEndRowIndex;
+ batch.DataWeight = 0;
+ for (i64 index = batchBeginRowIndex; index < batchEndRowIndex; ++index) {
+ batch.DataWeight += GetDataWeight(rows[index - tablet.FetchRowIndex]);
+ }
+ batches.emplace_back(std::move(batch));
+
+ YT_LOG_DEBUG("Rows fetched (TabletIndex: %v, RowIndexes: %v-%v, DataWeight: %v)",
+ tabletIndex,
+ batchBeginRowIndex,
+ batchEndRowIndex - 1,
+ batch.DataWeight);
+
+ batchBeginRowIndex = -1;
+ };
+
+ for (auto row : rows) {
+ YT_ASSERT(row[rowIndexColumnId].Type == EValueType::Int64);
+ auto queryRowIndex = row[rowIndexColumnId].Data.Int64;
+ if (queryRowIndex != currentRowIndex) {
+ OnStateFailed(state);
+ THROW_ERROR_EXCEPTION("Fetched row index mismatch: expected %v, got %v",
+ currentRowIndex,
+ queryRowIndex);
+ }
+
+ if (tablet.ConsumedRowIndexes.find(currentRowIndex) == tablet.ConsumedRowIndexes.end()) {
+ if (batchBeginRowIndex >= 0 && currentRowIndex - batchBeginRowIndex >= Config_->MaxRowsPerPoll) {
+ endBatch();
+ }
+ if (batchBeginRowIndex < 0) {
+ beginBatch();
+ }
+ } else {
+ endBatch();
+ }
+
+ ++currentRowIndex;
+ }
+
+ endBatch();
+
+ {
+ auto guard = Guard(state->SpinLock);
+
+ for (const auto& batch : batches) {
+ state->Batches.push_back(batch);
+ state->BatchesRowCount += batch.RowCount;
+ state->BatchesDataWeight += batch.DataWeight;
+ }
+
+ tablet.FetchRowIndex += rows.Size();
+ if (tablet.FetchRowIndex > tablet.MaxConsumedRowIndex) {
+ // No need to keep them anymore.
+ tablet.ConsumedRowIndexes.clear();
+ }
+
+ TryFulfillPromises(state, &guard);
+ }
+ }
+
+ void FetchTablet(int tabletIndex)
+ {
+ try {
+ DoFetchTablet(tabletIndex);
+ } catch (const std::exception& ex) {
+ YT_LOG_ERROR(ex, "Error fetching queue data (TabletIndex: %v)",
+ tabletIndex);
+ }
+ }
+
+
+ void TryFulfillPromises(const TStatePtr& state, TGuard<NThreading::TSpinLock>* guard)
+ {
+ if (state->Failed) {
+ return;
+ }
+
+ std::vector<std::tuple<TBatch, TPromise<IPersistentQueueRowsetPtr>>> toFulfill;
+ while (!state->Batches.empty() && !state->Promises.empty()) {
+ const auto& batch = state->Batches.front();
+ const auto& promise = state->Promises.front();
+ toFulfill.push_back(std::make_tuple(batch, promise));
+ state->Batches.pop_front();
+ state->Promises.pop_front();
+ state->BatchesRowCount -= batch.RowCount;
+ state->BatchesDataWeight -= batch.DataWeight;
+ }
+
+ guard->Release();
+
+ for (auto& tuple : toFulfill) {
+ const auto& rowset = std::get<0>(tuple);
+ auto& promise = std::get<1>(tuple);
+ YT_LOG_DEBUG("Rows offered (TabletIndex: %v, RowIndexes: %v-%v)",
+ rowset.TabletIndex,
+ rowset.BeginRowIndex,
+ rowset.EndRowIndex - 1);
+ promise.Set(New<TPolledRowset>(this, state, rowset));
+ }
+ }
+
+ void ReclaimBatch(
+ const TStatePtr& state,
+ TBatch batch)
+ {
+ auto guard = Guard(state->SpinLock);
+
+ if (State_ != state) {
+ return;
+ }
+
+ State_->BatchesRowCount += batch.RowCount;
+ State_->BatchesDataWeight += batch.DataWeight;
+ State_->Batches.emplace_back(std::move(batch));
+
+ YT_LOG_DEBUG("Rows reclaimed (TabletIndex: %v RowIndexes: %v-%v)",
+ batch.TabletIndex,
+ batch.BeginRowIndex,
+ batch.EndRowIndex - 1);
+
+ TryFulfillPromises(state, &guard);
+ }
+
+
+ TFuture<void> ConfirmBatch(
+ const TStatePtr& state,
+ const TBatch& batch,
+ const ITransactionPtr& transaction)
+ {
+ return BIND(&TImpl::DoConfirmBatch, MakeStrong(this))
+ .AsyncVia(Invoker_)
+ .Run(state, batch, transaction);
+ }
+
+ void DoConfirmBatch(
+ const TStatePtr& state,
+ const TBatch& batch,
+ const ITransactionPtr& transaction)
+ {
+ try {
+ // Check that none of the dequeued rows were consumed in another transaction.
+ {
+ // TODO(babenko): escaping
+ auto query = Format("[%v] from [%v] where [%v] = %v and [%v] between %v and %v",
+ TStateTable::RowIndexColumnName,
+ StateTablePath_,
+ TStateTable::TabletIndexColumnName,
+ batch.TabletIndex,
+ TStateTable::RowIndexColumnName,
+ batch.BeginRowIndex,
+ batch.EndRowIndex - 1);
+ auto result = WaitFor(transaction->SelectRows(query))
+ .ValueOrThrow();
+ const auto& rowset = result.Rowset;
+ auto rows = rowset->GetRows();
+ if (!rows.Empty()) {
+ std::vector<i64> rowIndexes;
+ for (auto row : rows) {
+ i64 rowIndex;
+ FromUnversionedRow(
+ row,
+ &rowIndex);
+ rowIndexes.push_back(rowIndex);
+ }
+ OnStateFailed(state);
+ THROW_ERROR_EXCEPTION("Some of the offered rows were already consumed")
+ << TErrorAttribute("consumed_row_indexes", rowIndexes);
+ }
+ }
+
+ // Check that none of the dequeued rows were trimmed.
+ {
+ // TODO(babenko): escaping
+ auto query = Format("[%v] from [%v] where [%v] = %v and [%v] = %v order by [%v] limit 1",
+ TStateTable::RowIndexColumnName,
+ StateTablePath_,
+ TStateTable::TabletIndexColumnName,
+ batch.TabletIndex,
+ TStateTable::StateColumnName,
+ static_cast<int>(ERowState::ConsumedAndTrimmed),
+ TStateTable::RowIndexColumnName);
+ auto result = WaitFor(transaction->SelectRows(query))
+ .ValueOrThrow();
+ const auto& rowset = result.Rowset;
+ const auto& schema = rowset->GetSchema();
+ auto rows = rowset->GetRows();
+ if (!rows.Empty()) {
+ YT_VERIFY(rows.Size() == 1);
+ auto row = rows[0];
+
+ auto rowIndexColumnId = schema->GetColumnIndexOrThrow(TStateTable::RowIndexColumnName);
+
+ YT_ASSERT(row[rowIndexColumnId].Type == EValueType::Int64);
+ auto rowIndex = row[rowIndexColumnId].Data.Int64;
+
+ if (rowIndex >= batch.BeginRowIndex) {
+ OnStateFailed(state);
+ THROW_ERROR_EXCEPTION("Some of the offered rows were already trimmed")
+ << TErrorAttribute("trimmed_row_index", rowIndex);
+ }
+ }
+ }
+
+ // Mark rows as consumed in state table.
+ {
+ auto nameTable = New<TNameTable>();
+ auto tabletIndexColumnId = nameTable->RegisterName(TStateTable::TabletIndexColumnName);
+ auto rowIndexColumnId = nameTable->RegisterName(TStateTable::RowIndexColumnName);
+ auto stateColumnId = nameTable->RegisterName(TStateTable::StateColumnName);
+
+ auto rowBuffer = New<TRowBuffer>(TPersistentQueuePollerBufferTag());
+ std::vector<TUnversionedRow> rows;
+ for (i64 rowIndex = batch.BeginRowIndex; rowIndex < batch.EndRowIndex; ++rowIndex) {
+ auto row = rowBuffer->AllocateUnversioned(3);
+ row[0] = MakeUnversionedInt64Value(batch.TabletIndex, tabletIndexColumnId);
+ row[1] = MakeUnversionedInt64Value(rowIndex, rowIndexColumnId);
+ row[2] = MakeUnversionedInt64Value(static_cast<int>(ERowState::Consumed), stateColumnId);
+ rows.push_back(row);
+ }
+ transaction->WriteRows(
+ StateTablePath_,
+ std::move(nameTable),
+ MakeSharedRange(std::move(rows), std::move(rowBuffer)));
+ }
+
+ YT_LOG_DEBUG("Rows processing confirmed (TabletIndex: %v, RowIndexes: %v-%v, TransactionId: %v)",
+ batch.TabletIndex,
+ batch.BeginRowIndex,
+ batch.EndRowIndex - 1,
+ transaction->GetId());
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error confirming persistent queue rows",
+ batch.TabletIndex,
+ batch.BeginRowIndex,
+ batch.EndRowIndex - 1)
+ << TErrorAttribute("poller_id", PollerId_)
+ << TErrorAttribute("transaction_id", transaction->GetId())
+ << TErrorAttribute("tablet_index", batch.TabletIndex)
+ << TErrorAttribute("begin_row_index", batch.BeginRowIndex)
+ << TErrorAttribute("end_row_index", batch.EndRowIndex)
+ << TErrorAttribute("data_table_path", DataTablePath_)
+ << TErrorAttribute("state_table_path", StateTablePath_)
+ << ex;
+ }
+ }
+
+
+ void OnBatchCommitted(const TBatch& batch)
+ {
+ YT_LOG_DEBUG("Rows processing committed (TabletIndex: %v RowIndexes: %v-%v)",
+ batch.TabletIndex,
+ batch.BeginRowIndex,
+ batch.EndRowIndex - 1);
+ }
+
+
+ void GuardedTrim()
+ {
+ // NB: Not actually needed, just for a backoff.
+ auto state = GetState();
+ if (state->Failed) {
+ return;
+ }
+
+ YT_LOG_DEBUG("Getting tablet infos");
+
+ auto asyncTabletInfos = Client_->GetTabletInfos(
+ DataTablePath_,
+ TabletIndexes_);
+ auto tabletInfos = WaitFor(asyncTabletInfos)
+ .ValueOrThrow();
+
+ THashMap<int, const TTabletInfo*> tabletIndexToInfo;
+ YT_VERIFY(TabletIndexes_.size() == tabletInfos.size());
+ for (size_t index = 0; index < TabletIndexes_.size(); ++index) {
+ YT_VERIFY(tabletIndexToInfo.emplace(TabletIndexes_[index], &tabletInfos[index]).second);
+ }
+
+ YT_LOG_DEBUG("Tablet infos received");
+
+ YT_LOG_DEBUG("Starting state trim transaction");
+
+ auto transaction = WaitFor(Client_->StartTransaction(ETransactionType::Tablet))
+ .ValueOrThrow();
+
+ YT_LOG_DEBUG("State trim transaction started (TransactionId: %v)",
+ transaction->GetId());
+
+ YT_LOG_DEBUG("Loading queue poller state for trim");
+
+ auto stateRows = ReadStateTable(transaction);
+
+ YT_LOG_DEBUG("Queue poller state loaded");
+
+ struct TTabletStatistics
+ {
+ i64 LastTrimmedRowIndex = -1;
+ THashSet<i64> ConsumedRowIndexes;
+ i64 TrimmedRowCountRequest = -1;
+ };
+
+ THashMap<int, TTabletStatistics> tabletStatisticsMap;
+
+ for (const auto& row : stateRows) {
+ auto& tablet = tabletStatisticsMap[row.TabletIndex];
+ if (row.State == ERowState::ConsumedAndTrimmed) {
+ tablet.LastTrimmedRowIndex = std::max(tablet.LastTrimmedRowIndex, row.RowIndex);
+ }
+ YT_VERIFY(tablet.ConsumedRowIndexes.insert(row.RowIndex).second);
+ }
+
+ {
+ auto guard = Guard(state->SpinLock);
+ for (const auto& [tabletIndex, statistics] : tabletStatisticsMap) {
+ auto& tablet = GetOrCrash(state->TabletMap, tabletIndex);
+ tablet.LastTrimmedRowIndex = statistics.LastTrimmedRowIndex;
+ }
+ }
+
+ {
+ auto nameTable = New<TNameTable>();
+ auto tabletIndexColumnId = nameTable->RegisterName(TStateTable::TabletIndexColumnName);
+ auto rowIndexColumnId = nameTable->RegisterName(TStateTable::RowIndexColumnName);
+ auto stateColumnId = nameTable->RegisterName(TStateTable::StateColumnName);
+
+ for (auto& [tabletIndex, statistics] : tabletStatisticsMap) {
+ i64 stateTrimRowIndex = statistics.LastTrimmedRowIndex;
+ while (statistics.ConsumedRowIndexes.find(stateTrimRowIndex + 1) != statistics.ConsumedRowIndexes.end()) {
+ ++stateTrimRowIndex;
+ }
+
+ if (stateTrimRowIndex > statistics.LastTrimmedRowIndex) {
+ auto rowBuffer = New<TRowBuffer>(TPersistentQueuePollerBufferTag());
+
+ std::vector<TUnversionedRow> deleteKeys;
+ for (i64 rowIndex = statistics.LastTrimmedRowIndex; rowIndex < stateTrimRowIndex; ++rowIndex) {
+ auto key = rowBuffer->AllocateUnversioned(2);
+ key[0] = MakeUnversionedInt64Value(tabletIndex, tabletIndexColumnId);
+ key[1] = MakeUnversionedInt64Value(rowIndex, rowIndexColumnId);
+ deleteKeys.push_back(key);
+ }
+ transaction->DeleteRows(
+ StateTablePath_,
+ nameTable,
+ MakeSharedRange(std::move(deleteKeys), rowBuffer));
+
+ std::vector<TUnversionedRow> writeRows;
+ {
+ auto row = rowBuffer->AllocateUnversioned(3);
+ row[0] = MakeUnversionedInt64Value(tabletIndex, tabletIndexColumnId);
+ row[1] = MakeUnversionedInt64Value(stateTrimRowIndex, rowIndexColumnId);
+ row[2] = MakeUnversionedInt64Value(static_cast<int>(ERowState::ConsumedAndTrimmed), stateColumnId);
+ writeRows.push_back(row);
+ }
+ transaction->WriteRows(
+ StateTablePath_,
+ nameTable,
+ MakeSharedRange(std::move(writeRows), std::move(rowBuffer)));
+
+ YT_LOG_DEBUG("Tablet state update scheduled (TabletIndex: %v, TrimRowIndex: %v)",
+ tabletIndex,
+ stateTrimRowIndex);
+ }
+
+ const auto& tabletInfo = GetOrCrash(tabletIndexToInfo, tabletIndex);
+ if (stateTrimRowIndex - tabletInfo->TrimmedRowCount >= Config_->UntrimmedDataRowsHigh) {
+ statistics.TrimmedRowCountRequest = stateTrimRowIndex - Config_->UntrimmedDataRowsLow;
+ YT_LOG_DEBUG("Tablet data trim scheduled (TabletIndex: %v, TrimmedRowCount: %v)",
+ tabletIndex,
+ statistics.TrimmedRowCountRequest);
+ }
+ }
+ }
+
+ YT_LOG_DEBUG("Committing state trim transaction");
+
+ WaitFor(transaction->Commit())
+ .ThrowOnError();
+
+ YT_LOG_DEBUG("State trim transaction committed");
+
+ std::vector<TFuture<void>> dataTrimAsyncResults;
+ for (const auto& [tabletIndex, statistics] : tabletStatisticsMap) {
+ if (statistics.TrimmedRowCountRequest > 0) {
+ dataTrimAsyncResults.push_back(Client_->TrimTable(
+ DataTablePath_,
+ tabletIndex,
+ statistics.TrimmedRowCountRequest));
+ }
+ }
+
+ if (!dataTrimAsyncResults.empty()) {
+ WaitFor(AllSucceeded(dataTrimAsyncResults))
+ .ThrowOnError();
+
+ YT_LOG_DEBUG("Tablet data trim completed");
+ }
+ }
+
+ void Trim()
+ {
+ try {
+ GuardedTrim();
+ } catch (const std::exception& ex) {
+ YT_LOG_ERROR(ex, "Error trimming queue poller");
+ }
+ }
+
+
+ void OnStateFailed(const TStatePtr& state)
+ {
+ bool expected = false;
+ if (state->Failed.compare_exchange_strong(expected, true)) {
+ RecreateState(true);
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TPersistentQueuePoller::TPersistentQueuePoller(
+ TPersistentQueuePollerConfigPtr config,
+ IClientPtr client,
+ const TYPath& dataTablePath,
+ const TYPath& stateTablePath,
+ const std::vector<int>& tabletIndexes)
+ : Impl_(New<TImpl>(
+ std::move(config),
+ std::move(client),
+ dataTablePath,
+ stateTablePath,
+ tabletIndexes))
+{ }
+
+TPersistentQueuePoller::~TPersistentQueuePoller()
+{ }
+
+TFuture<IPersistentQueueRowsetPtr> TPersistentQueuePoller::Poll()
+{
+ return Impl_->Poll();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<void> CreatePersistentQueueStateTable(
+ const IClientBasePtr& client,
+ const TYPath& path)
+{
+ TTableSchema schema({
+ TColumnSchema(TStateTable::TabletIndexColumnName, EValueType::Int64)
+ .SetSortOrder(ESortOrder::Ascending),
+ TColumnSchema(TStateTable::RowIndexColumnName, EValueType::Int64)
+ .SetSortOrder(ESortOrder::Ascending),
+ TColumnSchema(TStateTable::StateColumnName, EValueType::Int64)
+ });
+
+ auto attributes = CreateEphemeralAttributes();
+ attributes->Set("dynamic", true);
+ attributes->Set("schema", schema);
+
+ TCreateNodeOptions options;
+ options.Attributes = std::move(attributes);
+ return client->CreateNode(path, EObjectType::Table, options).As<void>();
+}
+
+TFuture<THashMap<int, TPersistentQueueTabletState>> ReadPersistentQueueTabletsState(
+ const IClientBasePtr& client,
+ const NYPath::TYPath& path,
+ const std::vector<int>& tabletIndexes)
+{
+ return
+ BIND([=] {
+ // TODO(babenko): escaping
+ auto query = Format(
+ "[%v], [%v], [%v] from [%v] where [%v] in (%v)",
+ TStateTable::TabletIndexColumnName,
+ TStateTable::RowIndexColumnName,
+ TStateTable::StateColumnName,
+ path,
+ TStateTable::TabletIndexColumnName,
+ JoinToString(tabletIndexes));
+ auto result = WaitFor(client->SelectRows(query))
+ .ValueOrThrow();
+ const auto& rowset = result.Rowset;
+ const auto& schema = rowset->GetSchema();
+ auto tabletIndexColumnId = schema->GetColumnIndexOrThrow(TStateTable::TabletIndexColumnName);
+ auto rowIndexColumnId = schema->GetColumnIndexOrThrow(TStateTable::RowIndexColumnName);
+ auto stateColumnId = schema->GetColumnIndexOrThrow(TStateTable::StateColumnName);
+
+ THashMap<int, TPersistentQueueTabletState> tabletMap;
+
+ for (auto row : rowset->GetRows()) {
+ YT_ASSERT(row[tabletIndexColumnId].Type == EValueType::Int64);
+ int tabletIndex = static_cast<int>(row[tabletIndexColumnId].Data.Int64);
+
+ YT_ASSERT(row[rowIndexColumnId].Type == EValueType::Int64);
+ i64 rowIndex = row[rowIndexColumnId].Data.Int64;
+
+ YT_ASSERT(row[rowIndexColumnId].Type == EValueType::Int64);
+ auto state = ERowState(row[stateColumnId].Data.Int64);
+
+ auto& tabletState = tabletMap[tabletIndex];
+ if (state == ERowState::ConsumedAndTrimmed) {
+ tabletState.FirstUntrimmedRowIndex = std::max(tabletState.FirstUntrimmedRowIndex, rowIndex + 1);
+ }
+ }
+
+ for (auto& [tabetId, tabletState] : tabletMap) {
+ tabletState.ConsumedRowCount = tabletState.FirstUntrimmedRowIndex;
+ }
+
+ for (auto row : rowset->GetRows()) {
+ YT_ASSERT(row[tabletIndexColumnId].Type == EValueType::Int64);
+ int tabletIndex = static_cast<int>(row[tabletIndexColumnId].Data.Int64);
+
+ YT_ASSERT(row[rowIndexColumnId].Type == EValueType::Int64);
+ i64 rowIndex = row[rowIndexColumnId].Data.Int64;
+
+ auto& tabletState = tabletMap[tabletIndex];
+ if (rowIndex >= tabletState.FirstUntrimmedRowIndex) {
+ ++tabletState.ConsumedRowCount;
+ }
+ }
+
+ return tabletMap;
+ })
+ .AsyncVia(client->GetConnection()->GetInvoker())
+ .Run();
+}
+
+TFuture<void> UpdatePersistentQueueTabletsState(
+ const IClientBasePtr& client,
+ const NYPath::TYPath& path,
+ const THashMap<int, TPersistentQueueTabletUpdate>& tabletMap)
+{
+ return
+ BIND([=] {
+ auto transaction = WaitFor(client->StartTransaction(ETransactionType::Tablet))
+ .ValueOrThrow();
+
+ // TODO(babenko): escaping
+ auto query = Format(
+ "[%v], [%v], [%v] from [%v] where [%v] in (%v)",
+ TStateTable::TabletIndexColumnName,
+ TStateTable::RowIndexColumnName,
+ TStateTable::StateColumnName,
+ path,
+ TStateTable::TabletIndexColumnName,
+ JoinToString(GetKeys(tabletMap)));
+ auto result = WaitFor(client->SelectRows(query))
+ .ValueOrThrow();
+ const auto& rowset = result.Rowset;
+ const auto& schema = rowset->GetSchema();
+ auto rowsetTabletIndexColumnId = schema->GetColumnIndexOrThrow(TStateTable::TabletIndexColumnName);
+ auto rowsetRowIndexColumnId = schema->GetColumnIndexOrThrow(TStateTable::RowIndexColumnName);
+
+ auto nameTable = New<TNameTable>();
+ auto nameTableTabletIndexColumnId = nameTable->RegisterName(TStateTable::TabletIndexColumnName);
+ auto nameTableRowIndexColumnId = nameTable->RegisterName(TStateTable::RowIndexColumnName);
+ auto nameTableStateColumnId = nameTable->RegisterName(TStateTable::StateColumnName);
+
+ auto rowBuffer = New<TRowBuffer>(TPersistentQueuePollerBufferTag());
+ std::vector<TRowModification> modifications;
+
+ for (auto rowsetRow : rowset->GetRows()) {
+ YT_ASSERT(rowsetRow[rowsetTabletIndexColumnId].Type == EValueType::Int64);
+ int tabletIndex = static_cast<int>(rowsetRow[rowsetTabletIndexColumnId].Data.Int64);
+
+ YT_ASSERT(rowsetRow[rowsetRowIndexColumnId].Type == EValueType::Int64);
+ i64 rowIndex = rowsetRow[rowsetRowIndexColumnId].Data.Int64;
+
+ auto rowToDelete = rowBuffer->AllocateUnversioned(2);
+ rowToDelete[0] = MakeUnversionedInt64Value(tabletIndex, nameTableTabletIndexColumnId);
+ rowToDelete[1] = MakeUnversionedInt64Value(rowIndex, nameTableRowIndexColumnId);
+ modifications.push_back(TRowModification{
+ ERowModificationType::Delete,
+ rowToDelete.ToTypeErasedRow(),
+ TLockMask()
+ });
+ }
+
+ for (const auto& [tabletIndex, tabletUpdate] : tabletMap) {
+ YT_ASSERT(tabletUpdate.FirstUnconsumedRowIndex >= 0);
+ if (tabletUpdate.FirstUnconsumedRowIndex > 0) {
+ auto rowToWrite = rowBuffer->AllocateUnversioned(3);
+ rowToWrite[0] = MakeUnversionedInt64Value(tabletIndex, nameTableTabletIndexColumnId);
+ rowToWrite[1] = MakeUnversionedInt64Value(tabletUpdate.FirstUnconsumedRowIndex - 1, nameTableRowIndexColumnId);
+ rowToWrite[2] = MakeUnversionedInt64Value(static_cast<i64>(ERowState::ConsumedAndTrimmed), nameTableStateColumnId);
+ modifications.push_back(TRowModification{
+ ERowModificationType::Write,
+ rowToWrite.ToTypeErasedRow(),
+ TLockMask()
+ });
+ }
+ }
+
+ transaction->ModifyRows(
+ path,
+ nameTable,
+ MakeSharedRange(std::move(modifications), std::move(rowBuffer)));
+
+ WaitFor(transaction->Commit())
+ .ThrowOnError();
+ })
+ .AsyncVia(client->GetConnection()->GetInvoker())
+ .Run();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/persistent_queue.h b/yt/yt/client/api/persistent_queue.h
new file mode 100644
index 0000000000..ac3b63dc84
--- /dev/null
+++ b/yt/yt/client/api/persistent_queue.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#include "rowset.h"
+
+#include <yt/yt/core/ypath/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Represents a rowset extracted from a persistent queue via
+//! TPersistentQueue::Poll.
+struct IPersistentQueueRowset
+ : public IUnversionedRowset
+{
+ //! Confirms that the rowset has been successfully processed
+ //! within #transaction and must not be consumed again.
+ virtual TFuture<void> Confirm(const ITransactionPtr& transaction) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IPersistentQueueRowset)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Enables polling and consuming a subset of tablets of an ordered dynamic table.
+/*!
+ * The best practices for using TPersistentQueuePoller are as follows:
+ *
+ * - Create as many as |N * K| tablets within the queue, where |N| is the number of
+ * consumer processes and |K| is a small constant allowing for tuning |N| afterwards.
+ *
+ * - Within each consumer process, create a single instance of TPersistentQueuePoller
+ * and assign it a unique subset of |K| tablets. Assigning the same tablet to multiple
+ * instances will not lead to any data corruption but will cause lock conflicts and
+ * performance degradation.
+ *
+ * - Within each consumer process, spawn a number of worker fibers, possibly
+ * within a thread pool. Each fiber must do the following:
+ * - poll the queue (via #TPersistentQueuePoller::Poll); wait until rows arrive;
+ * - start a transaction
+ * - process the data within the transaction; make any writes necessary;
+ * - mark the dequeued rows as consumed (via IPersistentQueueRowset::Confirm)
+ * - commit the transaction
+ *
+ * Thread affinity: any
+ */
+class TPersistentQueuePoller
+ : public TRefCounted
+{
+public:
+ //! Constructs a poller.
+ /*
+ * \param config poller configuration
+ * \param dataTablePath points to an ordered table with queue data
+ * \param stateTablePath points to a sorted per-consumer table holding the state of the consumer
+ * \param tabletIndexes contains the indexes of the set of tablets to be polled
+ */
+ TPersistentQueuePoller(
+ TPersistentQueuePollerConfigPtr config,
+ IClientPtr client,
+ const NYPath::TYPath& dataTablePath,
+ const NYPath::TYPath& stateTablePath,
+ const std::vector<int>& tabletIndexes);
+
+ ~TPersistentQueuePoller();
+
+ //! Polls the tablets of the queue.
+ /*!
+ * When unconsumed rows become available, the returned future gets
+ * populated with the queue rows. At most #TPersistentQueuePollerConfig::MaxRowsPerPoll
+ * rows are returned.
+ *
+ * This does not constitute a dequeue operation yet,
+ * however as long the the returned IPersistentQueueRowset instance is alive,
+ * the client is assumed to be holding a (transient) lock for these rows.
+ *
+ * Is is assumed that upon receiving IPersistentQueueRowset the client initiates
+ * a transaction to process these rows, carries out all the required updates within this
+ * transaction and marks the rows are dequeued by calling IPersistentQueueRowset::Confirm.
+ * When this transaction commits, these rows are persistently marked as consumed.
+ *
+ * Under any circumstances, it is guaranteed that any queued row is processed at most once
+ * by a consumer transaction that was able to commit successfully.
+ */
+ TFuture<IPersistentQueueRowsetPtr> Poll();
+
+private:
+ class TImpl;
+ const TIntrusivePtr<TImpl> Impl_;
+
+};
+
+DEFINE_REFCOUNTED_TYPE(TPersistentQueuePoller)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Creates an empty table holding the state of a consumer.
+TFuture<void> CreatePersistentQueueStateTable(
+ const IClientBasePtr& client,
+ const NYPath::TYPath& path);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPersistentQueueTabletState
+{
+ //! Index of the first (minimum) untrimmed row in this particular tablet.
+ //! This row may already be consumed.
+ i64 FirstUntrimmedRowIndex = 0;
+
+ //! The total number of rows consumed in this tablet.
+ i64 ConsumedRowCount = 0;
+};
+
+//! Reads the state of tablets with certain #tabletIndexes.
+//! The resulting hashtable maps tablet indexes to tablet states.
+TFuture<THashMap<int, TPersistentQueueTabletState>> ReadPersistentQueueTabletsState(
+ const IClientBasePtr& client,
+ const NYPath::TYPath& path,
+ const std::vector<int>& tabletIndexes);
+
+struct TPersistentQueueTabletUpdate
+{
+ //! Index of the first (minimum) unconsumed row in this particular tablet.
+ i64 FirstUnconsumedRowIndex = 0;
+};
+
+//! Updates the poller state for certain tablets; in particular this enables rewinding or
+//! skipping some rows in these tablets.
+TFuture<void> UpdatePersistentQueueTabletsState(
+ const IClientBasePtr& client,
+ const NYPath::TYPath& path,
+ const THashMap<int, TPersistentQueueTabletUpdate>& tabletMap);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/private.h b/yt/yt/client/api/private.h
new file mode 100644
index 0000000000..60a8f6a4fa
--- /dev/null
+++ b/yt/yt/client/api/private.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger ApiLogger("Api");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/public.cpp b/yt/yt/client/api/public.cpp
new file mode 100644
index 0000000000..7bf0377af1
--- /dev/null
+++ b/yt/yt/client/api/public.cpp
@@ -0,0 +1,10 @@
+#include "public.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/public.h b/yt/yt/client/api/public.h
new file mode 100644
index 0000000000..4579a1fd6a
--- /dev/null
+++ b/yt/yt/client/api/public.h
@@ -0,0 +1,219 @@
+#pragma once
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/client/transaction_client/public.h>
+
+#include <yt/yt/library/auth/authentication_options.h>
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TClusterTag = NObjectClient::TCellTag;
+
+// Keep in sync with NRpcProxy::NProto::EMasterReadKind.
+// On cache miss request is redirected to next level cache:
+// Local cache -> (node) cache -> master cache
+DEFINE_ENUM(EMasterChannelKind,
+ ((Leader) (0))
+ ((Follower) (1))
+ // Use local (per-connection) cache.
+ ((LocalCache) (4))
+ // Use cache located on nodes.
+ ((Cache) (2))
+ // Use cache located on masters (if caching on masters is enabled).
+ ((MasterCache) (3))
+);
+
+DEFINE_ENUM(EUserWorkloadCategory,
+ (Batch)
+ (Interactive)
+ (Realtime)
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((TooManyConcurrentRequests) (1900))
+ ((JobArchiveUnavailable) (1910))
+ ((RetriableArchiveError) (1911))
+ ((NoSuchOperation) (1915))
+ ((NoSuchJob) (1916))
+ ((UncertainOperationControllerState) (1917))
+ ((NoSuchAttribute) (1920))
+ ((FormatDisabled) (1925))
+ ((ClusterLivenessCheckFailed) (1926))
+);
+
+DEFINE_ENUM(ERowModificationType,
+ ((Write) (0))
+ ((Delete) (1))
+ ((VersionedWrite) (2))
+ ((WriteAndLock) (3))
+);
+
+DEFINE_ENUM(EReplicaConsistency,
+ ((None) (0))
+ ((Sync) (1))
+);
+
+DEFINE_ENUM(ETransactionCoordinatorCommitMode,
+ // Success is reported when phase 2 starts (all participants have prepared but not yet committed).
+ ((Eager) (0))
+ // Success is reported when transaction is finished (all participants have committed).
+ ((Lazy) (1))
+);
+
+DEFINE_ENUM(ETransactionCoordinatorPrepareMode,
+ // Coordinator is prepared just like every other participant.
+ ((Early) (0))
+ // Coordinator is prepared after other participants prepared and
+ // after commit timestamp generation. In this mode prepare and commit
+ // for coordinator are executed in the same mutation, so no preparation
+ // locks are required.
+ ((Late) (1))
+);
+
+DEFINE_ENUM(EProxyType,
+ ((Http) (1))
+ ((Rpc) (2))
+ ((Grpc) (3))
+);
+
+DEFINE_ENUM(EOperationSortDirection,
+ ((None) (0))
+ ((Past) (1))
+ ((Future) (2))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+struct IRowset;
+template <class TRow>
+using IRowsetPtr = TIntrusivePtr<IRowset<TRow>>;
+
+using IUnversionedRowset = IRowset<NTableClient::TUnversionedRow>;
+using IVersionedRowset = IRowset<NTableClient::TVersionedRow>;
+using ITypeErasedRowset = IRowset<NTableClient::TTypeErasedRow>;
+
+DECLARE_REFCOUNTED_TYPE(IUnversionedRowset)
+DECLARE_REFCOUNTED_TYPE(IVersionedRowset)
+DECLARE_REFCOUNTED_TYPE(ITypeErasedRowset)
+DECLARE_REFCOUNTED_STRUCT(IPersistentQueueRowset)
+DECLARE_REFCOUNTED_STRUCT(TSkynetSharePartsLocations)
+
+struct TConnectionOptions;
+
+using TClientOptions = NAuth::TAuthenticationOptions;
+
+struct TTransactionParticipantOptions;
+
+struct TTimeoutOptions;
+struct TTransactionalOptions;
+struct TPrerequisiteOptions;
+struct TMasterReadOptions;
+struct TMutatingOptions;
+struct TReshardTableOptions;
+struct TSuppressableAccessTrackingOptions;
+struct TTabletRangeOptions;
+
+struct TGetFileFromCacheResult;
+struct TPutFileToCacheResult;
+
+DECLARE_REFCOUNTED_STRUCT(IConnection)
+DECLARE_REFCOUNTED_STRUCT(IClientBase)
+DECLARE_REFCOUNTED_STRUCT(IClient)
+DECLARE_REFCOUNTED_STRUCT(IInternalClient)
+DECLARE_REFCOUNTED_STRUCT(ITransaction)
+DECLARE_REFCOUNTED_STRUCT(IStickyTransactionPool)
+
+DECLARE_REFCOUNTED_STRUCT(ITableReader)
+DECLARE_REFCOUNTED_STRUCT(ITableWriter)
+
+DECLARE_REFCOUNTED_STRUCT(IFileReader)
+DECLARE_REFCOUNTED_STRUCT(IFileWriter)
+
+DECLARE_REFCOUNTED_STRUCT(IJournalReader)
+DECLARE_REFCOUNTED_STRUCT(IJournalWriter)
+
+DECLARE_REFCOUNTED_CLASS(TPersistentQueuePoller)
+
+DECLARE_REFCOUNTED_CLASS(TTableMountCacheConfig)
+DECLARE_REFCOUNTED_CLASS(TConnectionConfig)
+DECLARE_REFCOUNTED_CLASS(TConnectionDynamicConfig)
+DECLARE_REFCOUNTED_CLASS(TPersistentQueuePollerConfig)
+
+DECLARE_REFCOUNTED_CLASS(TFileReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TFileWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TJournalReaderConfig)
+
+DECLARE_REFCOUNTED_CLASS(TJournalChunkWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TJournalWriterConfig)
+
+DECLARE_REFCOUNTED_CLASS(TJournalChunkWriterOptions)
+
+DECLARE_REFCOUNTED_STRUCT(TSerializableMasterReadOptions)
+
+DECLARE_REFCOUNTED_STRUCT(TPrerequisiteRevisionConfig)
+
+DECLARE_REFCOUNTED_STRUCT(TDetailedProfilingInfo)
+
+DECLARE_REFCOUNTED_STRUCT(TSchedulingOptions)
+
+DECLARE_REFCOUNTED_CLASS(TJobInputReader)
+
+DECLARE_REFCOUNTED_CLASS(TClientCache)
+
+DECLARE_REFCOUNTED_STRUCT(TTableBackupManifest)
+DECLARE_REFCOUNTED_STRUCT(TBackupManifest)
+
+DECLARE_REFCOUNTED_STRUCT(TListOperationsAccessFilter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const TString ClusterNamePath("//sys/@cluster_name");
+inline const TString HttpProxiesPath("//sys/http_proxies");
+inline const TString RpcProxiesPath("//sys/rpc_proxies");
+inline const TString GrpcProxiesPath("//sys/grpc_proxies");
+inline const TString AliveNodeName("alive");
+inline const TString BannedAttributeName("banned");
+inline const TString RoleAttributeName("role");
+inline const TString AddressesAttributeName("addresses");
+inline const TString BalancersAttributeName("balancers");
+inline const TString DefaultRpcProxyRole("default");
+inline const TString DefaultHttpProxyRole("data");
+inline const TString JournalPayloadKey("payload");
+inline const TString HunkPayloadKey("payload");
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EMaintenanceType,
+ // 0 is reserved for None.
+ ((Ban) (1))
+ ((Decommission) (2))
+ ((DisableSchedulerJobs) (3))
+ ((DisableWriteSessions) (4))
+ ((DisableTabletCells) (5))
+ ((PendingRestart) (6))
+);
+
+DEFINE_ENUM(EMaintenanceComponent,
+ ((ClusterNode) (1))
+ ((HttpProxy) (2))
+ ((RpcProxy) (3))
+ ((Host) (4))
+);
+
+using TMaintenanceId = TGuid;
+using TMaintenanceCounts = TEnumIndexedVector<EMaintenanceType, int>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/query_tracker_client.cpp b/yt/yt/client/api/query_tracker_client.cpp
new file mode 100644
index 0000000000..158f51405f
--- /dev/null
+++ b/yt/yt/client/api/query_tracker_client.cpp
@@ -0,0 +1,61 @@
+#include "query_tracker_client.h"
+
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <contrib/libs/pfr/include/pfr/tuple_size.hpp>
+
+namespace NYT::NApi {
+
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TQuery& query, NYson::IYsonConsumer* consumer)
+{
+ static_assert(pfr::tuple_size<TQuery>::value == 13);
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .OptionalItem("id", query.Id)
+ .OptionalItem("engine", query.Engine)
+ .OptionalItem("query", query.Query)
+ .OptionalItem("start_time", query.StartTime)
+ .OptionalItem("finish_time", query.FinishTime)
+ .OptionalItem("settings", query.Settings)
+ .OptionalItem("user", query.User)
+ .OptionalItem("state", query.State)
+ .OptionalItem("result_count", query.ResultCount)
+ .OptionalItem("progress", query.Progress)
+ .OptionalItem("annotations", query.Annotations)
+ .OptionalItem("error", query.Error)
+ .DoIf(static_cast<bool>(query.OtherAttributes), [&] (TFluentMap fluent) {
+ for (const auto& [key, value] : query.OtherAttributes->ListPairs()) {
+ fluent.Item(key).Value(value);
+ }
+ })
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TQueryResult& queryResult, NYson::IYsonConsumer* consumer)
+{
+ static_assert(pfr::tuple_size<TQueryResult>::value == 5);
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("id").Value(queryResult.Id)
+ .Item("result_index").Value(queryResult.ResultIndex)
+ .DoIf(!queryResult.Error.IsOK(), [&] (TFluentMap fluent) {
+ fluent
+ .Item("error").Value(queryResult.Error);
+ })
+ .OptionalItem("schema", queryResult.Schema)
+ .Item("data_statistics").Value(queryResult.DataStatistics)
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/query_tracker_client.h b/yt/yt/client/api/query_tracker_client.h
new file mode 100644
index 0000000000..df785d3b18
--- /dev/null
+++ b/yt/yt/client/api/query_tracker_client.h
@@ -0,0 +1,155 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/data_statistics.pb.h>
+
+#include <yt/yt/client/query_tracker_client/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TQueryTrackerOptions
+{
+ TString QueryTrackerStage = "production";
+};
+
+struct TStartQueryOptions
+ : public TTimeoutOptions
+ , public TQueryTrackerOptions
+{
+ NYTree::INodePtr Settings;
+ bool Draft = false;
+ NYTree::IMapNodePtr Annotations;
+};
+
+struct TAbortQueryOptions
+ : public TTimeoutOptions
+ , public TQueryTrackerOptions
+{
+ std::optional<TString> AbortMessage;
+};
+
+struct TGetQueryResultOptions
+ : public TTimeoutOptions
+ , public TQueryTrackerOptions
+{ };
+
+struct TReadQueryResultOptions
+ : public TTimeoutOptions
+ , public TQueryTrackerOptions
+{
+ std::optional<std::vector<TString>> Columns;
+ std::optional<i64> LowerRowIndex;
+ std::optional<i64> UpperRowIndex;
+};
+
+struct TGetQueryOptions
+ : public TTimeoutOptions
+ , public TQueryTrackerOptions
+{
+ NYTree::TAttributeFilter Attributes;
+ NTransactionClient::TTimestamp Timestamp = NTransactionClient::NullTimestamp;
+};
+
+struct TListQueriesOptions
+ : public TTimeoutOptions
+ , public TQueryTrackerOptions
+{
+ std::optional<TInstant> FromTime;
+ std::optional<TInstant> ToTime;
+ std::optional<TInstant> CursorTime;
+ EOperationSortDirection CursorDirection = EOperationSortDirection::Past;
+ std::optional<TString> UserFilter;
+
+ std::optional<NQueryTrackerClient::EQueryState> StateFilter;
+ std::optional<NQueryTrackerClient::EQueryEngine> EngineFilter;
+ std::optional<TString> SubstrFilter;
+ ui64 Limit = 100;
+
+ NYTree::TAttributeFilter Attributes;
+};
+
+struct TQuery
+{
+ NQueryTrackerClient::TQueryId Id;
+ std::optional<NQueryTrackerClient::EQueryEngine> Engine;
+ std::optional<TString> Query;
+ std::optional<TInstant> StartTime;
+ std::optional<TInstant> FinishTime;
+ NYson::TYsonString Settings;
+ std::optional<TString> User;
+ std::optional<NQueryTrackerClient::EQueryState> State;
+ std::optional<i64> ResultCount;
+ NYson::TYsonString Progress;
+ std::optional<TError> Error;
+ NYson::TYsonString Annotations;
+ NYTree::IAttributeDictionaryPtr OtherAttributes;
+};
+
+void Serialize(const TQuery& query, NYson::IYsonConsumer* consumer);
+
+struct TQueryResult
+{
+ NQueryTrackerClient::TQueryId Id;
+ i64 ResultIndex;
+ TError Error;
+ NTableClient::TTableSchemaPtr Schema;
+ NChunkClient::NProto::TDataStatistics DataStatistics;
+};
+
+void Serialize(const TQueryResult& queryResult, NYson::IYsonConsumer* consumer);
+
+struct TListQueriesResult
+{
+ std::vector<TQuery> Queries;
+ bool Incomplete = false;
+ NTransactionClient::TTimestamp Timestamp;
+};
+
+struct TAlterQueryOptions
+ : public TTimeoutOptions
+ , public TQueryTrackerOptions
+{
+ NYTree::IMapNodePtr Annotations;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IQueryTrackerClient
+{
+ virtual TFuture<NQueryTrackerClient::TQueryId> StartQuery(
+ NQueryTrackerClient::EQueryEngine engine,
+ const TString& query,
+ const TStartQueryOptions& options = {}) = 0;
+
+ virtual TFuture<void> AbortQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAbortQueryOptions& options = {}) = 0;
+
+ virtual TFuture<TQueryResult> GetQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex = 0,
+ const TGetQueryResultOptions& options = {}) = 0;
+
+ virtual TFuture<IUnversionedRowsetPtr> ReadQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex = 0,
+ const TReadQueryResultOptions& options = {}) = 0;
+
+ virtual TFuture<TQuery> GetQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TGetQueryOptions& options = {}) = 0;
+
+ virtual TFuture<TListQueriesResult> ListQueries(const TListQueriesOptions& options = {}) = 0;
+
+ virtual TFuture<void> AlterQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAlterQueryOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/queue_client.h b/yt/yt/client/api/queue_client.h
new file mode 100644
index 0000000000..3a2d8c70e2
--- /dev/null
+++ b/yt/yt/client/api/queue_client.h
@@ -0,0 +1,127 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/client/chaos_client/replication_card.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/client/chaos_client/public.h>
+
+#include <yt/yt/client/queue_client/queue_rowset.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPullRowsOptions
+ : public TTabletReadOptions
+{
+ NChaosClient::TReplicaId UpstreamReplicaId;
+ THashMap<NTabletClient::TTabletId, i64> StartReplicationRowIndexes;
+ i64 TabletRowsPerRead = 1000;
+ bool OrderRowsByTimestamp = false;
+
+ NChaosClient::TReplicationProgress ReplicationProgress;
+ NTransactionClient::TTimestamp UpperTimestamp = NTransactionClient::NullTimestamp;
+ NTableClient::TTableSchemaPtr TableSchema;
+};
+
+struct TPullRowsResult
+{
+ THashMap<NTabletClient::TTabletId, i64> EndReplicationRowIndexes;
+ i64 RowCount = 0;
+ i64 DataWeight = 0;
+ NChaosClient::TReplicationProgress ReplicationProgress;
+ ITypeErasedRowsetPtr Rowset;
+ bool Versioned = true;
+};
+
+struct TPullQueueOptions
+ : public TSelectRowsOptions
+ , public TFallbackReplicaOptions
+{
+ // COMPAT(achulkov2): Remove this once we drop support for legacy PullQueue via SelectRows.
+ bool UseNativeTabletNodeApi = true;
+};
+
+struct TPullConsumerOptions
+ : public TPullQueueOptions
+{ };
+
+struct TRegisterQueueConsumerOptions
+ : public TTimeoutOptions
+{
+ std::optional<std::vector<int>> Partitions;
+};
+
+struct TUnregisterQueueConsumerOptions
+ : public TTimeoutOptions
+{ };
+
+struct TListQueueConsumerRegistrationsOptions
+ : public TTimeoutOptions
+{ };
+
+struct TListQueueConsumerRegistrationsResult
+{
+ NYPath::TRichYPath QueuePath;
+ NYPath::TRichYPath ConsumerPath;
+ bool Vital;
+ std::optional<std::vector<int>> Partitions;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IQueueClientBase
+{
+ virtual TFuture<TPullRowsResult> PullRows(
+ const NYPath::TYPath& path,
+ const TPullRowsOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IQueueClient
+{
+ //! Reads a batch of rows from a given partition of a given queue, starting at (at least) the given offset.
+ //! Requires the user to have read-access to the specified queue.
+ virtual TFuture<NQueueClient::IQueueRowsetPtr> PullQueue(
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullQueueOptions& options = {}) = 0;
+
+ //! Same as PullQueue, but requires user to have read-access to the consumer and the consumer being registered for the given queue.
+ virtual TFuture<NQueueClient::IQueueRowsetPtr> PullConsumer(
+ const NYPath::TRichYPath& consumerPath,
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullConsumerOptions& options = {}) = 0;
+
+ virtual TFuture<void> RegisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ bool vital,
+ const TRegisterQueueConsumerOptions& options = {}) = 0;
+
+ virtual TFuture<void> UnregisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ const TUnregisterQueueConsumerOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<TListQueueConsumerRegistrationsResult>> ListQueueConsumerRegistrations(
+ const std::optional<NYPath::TRichYPath>& queuePath,
+ const std::optional<NYPath::TRichYPath>& consumerPath,
+ const TListQueueConsumerRegistrationsOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/rowset.cpp b/yt/yt/client/api/rowset.cpp
new file mode 100644
index 0000000000..c60d60f0c1
--- /dev/null
+++ b/yt/yt/client/api/rowset.cpp
@@ -0,0 +1,197 @@
+#include "rowset.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/table_client/wire_protocol.h>
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NApi {
+
+using namespace NTabletClient;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+class TRowsetBase
+ : public IRowset<TRow>
+{
+public:
+ explicit TRowsetBase(TTableSchemaPtr schema)
+ : Schema_(std::move(schema))
+ , NameTableInitialized_(false)
+ { }
+
+ explicit TRowsetBase(TNameTablePtr nameTable)
+ : NameTableInitialized_(true)
+ , NameTable_(std::move(nameTable))
+ { }
+
+ const TTableSchemaPtr& GetSchema() const override
+ {
+ return Schema_;
+ }
+
+ const TNameTablePtr& GetNameTable() const override
+ {
+ // Fast path.
+ if (NameTableInitialized_.load()) {
+ return NameTable_;
+ }
+
+ // Slow path.
+ auto guard = Guard(NameTableLock_);
+ if (!NameTable_) {
+ NameTable_ = TNameTable::FromSchema(*Schema_);
+ }
+ NameTableInitialized_ = true;
+ return NameTable_;
+ }
+
+private:
+ const TTableSchemaPtr Schema_;
+
+ mutable std::atomic<bool> NameTableInitialized_;
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, NameTableLock_);
+ mutable TNameTablePtr NameTable_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+class TRowset
+ : public TRowsetBase<TRow>
+{
+public:
+ TRowset(
+ TTableSchemaPtr schema,
+ TSharedRange<TRow> rows)
+ : TRowsetBase<TRow>(std::move(schema))
+ , Rows_(std::move(rows))
+ { }
+
+ TRowset(
+ TNameTablePtr nameTable,
+ TSharedRange<TRow> rows)
+ : TRowsetBase<TRow>(std::move(nameTable))
+ , Rows_(std::move(rows))
+ { }
+
+ TSharedRange<TRow> GetRows() const override
+ {
+ return Rows_;
+ }
+
+private:
+ const TSharedRange<TRow> Rows_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TRowset<TUnversionedRow>)
+DEFINE_REFCOUNTED_TYPE(TRowset<TVersionedRow>)
+
+template <class TRow>
+IRowsetPtr<TRow> CreateRowset(
+ NTableClient::TTableSchemaPtr schema,
+ TSharedRange<TRow> rows)
+{
+ return New<TRowset<TRow>>(std::move(schema), std::move(rows));
+}
+
+template
+IUnversionedRowsetPtr CreateRowset<TUnversionedRow>(
+ NTableClient::TTableSchemaPtr schema,
+ TSharedRange<TUnversionedRow> rows);
+template
+IVersionedRowsetPtr CreateRowset<TVersionedRow>(
+ NTableClient::TTableSchemaPtr schema,
+ TSharedRange<TVersionedRow> rows);
+template
+ITypeErasedRowsetPtr CreateRowset<TTypeErasedRow>(
+ NTableClient::TTableSchemaPtr schema,
+ TSharedRange<TTypeErasedRow> rows);
+
+template <class TRow>
+IRowsetPtr<TRow> CreateRowset(
+ TNameTablePtr nameTable,
+ TSharedRange<TRow> rows)
+{
+ return New<TRowset<TRow>>(std::move(nameTable), std::move(rows));
+}
+
+template
+IUnversionedRowsetPtr CreateRowset<TUnversionedRow>(
+ TNameTablePtr nameTable,
+ TSharedRange<TUnversionedRow> rows);
+template
+IVersionedRowsetPtr CreateRowset<TVersionedRow>(
+ TNameTablePtr nameTable,
+ TSharedRange<TVersionedRow> rows);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemafulRowsetWriter
+ : public TRowsetBase<TUnversionedRow>
+ , public IUnversionedRowsetWriter
+{
+public:
+ using TRowsetBase::TRowsetBase;
+
+ TSharedRange<TUnversionedRow> GetRows() const override
+ {
+ return MakeSharedRange(Rows_, RowBuffer_);
+ }
+
+ TFuture<IUnversionedRowsetPtr> GetResult() const
+ {
+ return Result_.ToFuture();
+ }
+
+ TFuture<void> Close() override
+ {
+ Result_.Set(IUnversionedRowsetPtr(this));
+ Result_.Reset();
+ return VoidFuture;
+ }
+
+ bool Write(TRange<TUnversionedRow> rows) override
+ {
+ for (auto row : rows) {
+ Rows_.push_back(RowBuffer_->CaptureRow(row));
+ }
+ return true;
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ return VoidFuture;
+ }
+
+private:
+ const TTableSchemaPtr Schema_;
+ const TNameTablePtr NameTable_;
+
+ TPromise<IUnversionedRowsetPtr> Result_ = NewPromise<IUnversionedRowsetPtr>();
+
+ struct TSchemafulRowsetWriterBufferTag
+ { };
+
+ const TRowBufferPtr RowBuffer_ = New<TRowBuffer>(TSchemafulRowsetWriterBufferTag());
+ std::vector<TUnversionedRow> Rows_;
+
+};
+
+std::tuple<IUnversionedRowsetWriterPtr, TFuture<IUnversionedRowsetPtr>> CreateSchemafulRowsetWriter(TTableSchemaPtr schema)
+{
+ auto writer = New<TSchemafulRowsetWriter>(std::move(schema));
+ return std::make_tuple(writer, writer->GetResult());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/rowset.h b/yt/yt/client/api/rowset.h
new file mode 100644
index 0000000000..f70dfc438c
--- /dev/null
+++ b/yt/yt/client/api/rowset.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/misc/shared_range.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+struct IRowset
+ : public virtual TRefCounted
+{
+ virtual const NTableClient::TTableSchemaPtr& GetSchema() const = 0;
+ virtual const NTableClient::TNameTablePtr& GetNameTable() const = 0;
+
+ virtual TSharedRange<TRow> GetRows() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IUnversionedRowset)
+DEFINE_REFCOUNTED_TYPE(IVersionedRowset)
+DEFINE_REFCOUNTED_TYPE(ITypeErasedRowset)
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+IRowsetPtr<TRow> CreateRowset(
+ NTableClient::TTableSchemaPtr schema,
+ TSharedRange<TRow> rows);
+
+template <class TRow>
+IRowsetPtr<TRow> CreateRowset(
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<TRow> rows);
+
+std::tuple<NTableClient::IUnversionedRowsetWriterPtr, TFuture<IUnversionedRowsetPtr>>
+ CreateSchemafulRowsetWriter(NTableClient::TTableSchemaPtr schema);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/rpc_proxy/address_helpers.cpp b/yt/yt/client/api/rpc_proxy/address_helpers.cpp
new file mode 100644
index 0000000000..753e95d8a5
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/address_helpers.cpp
@@ -0,0 +1,72 @@
+#include "address_helpers.h"
+
+#include <yt/yt/core/net/address.h>
+#include <yt/yt/core/net/local_address.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NNet;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const EAddressType DefaultAddressType(EAddressType::InternalRpc);
+const TString DefaultNetworkName("default");
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAddressMap GetLocalAddresses(const NNodeTrackerClient::TNetworkAddressList& addresses, int port)
+{
+ // Append port number.
+ TAddressMap result;
+ result.reserve(addresses.size());
+ for (const auto& [networkName, networkAddress] : addresses) {
+ YT_VERIFY(result.emplace(networkName, BuildServiceAddress(networkAddress, port)).second);
+ }
+
+ // Add default address.
+ result.emplace(DefaultNetworkName, BuildServiceAddress(GetLocalHostName(), port));
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<TString> GetAddressOrNull(
+ const TProxyAddressMap& addresses,
+ EAddressType type,
+ const TString& network)
+{
+ auto typeAddressesIt = addresses.find(type);
+ if (typeAddressesIt != addresses.end()) {
+ auto it = typeAddressesIt->second.find(network);
+ if (it != typeAddressesIt->second.end()) {
+ return it->second;
+ }
+ }
+ return std::nullopt;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<std::vector<TString>> GetBalancersOrNull(
+ const TBalancersMap& balancers,
+ const TString& role,
+ EAddressType addressType,
+ const TString& network)
+{
+ auto roleBalancersIt = balancers.find(role);
+ if (roleBalancersIt != balancers.end()) {
+ auto roleTypeBalancersIt = roleBalancersIt->second.find(addressType);
+ if (roleTypeBalancersIt != roleBalancersIt->second.end()) {
+ auto it = roleTypeBalancersIt->second.find(network);
+ if (it != roleTypeBalancersIt->second.end()) {
+ return it->second;
+ }
+ }
+ }
+ return std::nullopt;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/address_helpers.h b/yt/yt/client/api/rpc_proxy/address_helpers.h
new file mode 100644
index 0000000000..c2f31764c6
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/address_helpers.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/node_tracker_client/node_directory.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Network -> host:port.
+using TAddressMap = THashMap<TString, TString>;
+
+// Address type (e.g. RPC, HTTP) -> network -> host:port.
+using TProxyAddressMap = THashMap<EAddressType, TAddressMap>;
+
+extern const EAddressType DefaultAddressType;
+extern const TString DefaultNetworkName;
+
+// Network -> [host:port].
+using TNetworkAddressesMap = THashMap<TString, std::vector<TString>>;
+
+// Address type (e.g. RPC, HTTP) -> network -> [host:port].
+using TAddressTypeAddressesMap = THashMap<EAddressType, TNetworkAddressesMap>;
+
+// Role -> address type -> network -> host:port.
+using TBalancersMap = THashMap<TString, TAddressTypeAddressesMap>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAddressMap GetLocalAddresses(
+ const NNodeTrackerClient::TNetworkAddressList& addresses,
+ int port);
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<TString> GetAddressOrNull(
+ const TProxyAddressMap& addresses,
+ EAddressType addressType,
+ const TString& network);
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<std::vector<TString>> GetBalancersOrNull(
+ const TBalancersMap& balancers,
+ const TString& role,
+ EAddressType addressType,
+ const TString& network);
+
+///////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/api_service_proxy.h b/yt/yt/client/api/rpc_proxy/api_service_proxy.h
new file mode 100644
index 0000000000..c5f788ef8e
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/api_service_proxy.h
@@ -0,0 +1,175 @@
+#pragma once
+
+#include "public.h"
+#include "protocol_version.h"
+
+#include <yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.pb.h>
+
+#include <yt/yt/core/rpc/client.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TApiServiceProxy
+ : public NRpc::TProxyBase
+{
+public:
+ DEFINE_RPC_PROXY(TApiServiceProxy, ApiService,
+ .SetFeaturesType<ERpcProxyFeature>()
+ .SetProtocolVersion({
+ YTRpcProxyProtocolVersionMajor,
+ YTRpcProxyClientProtocolVersionMinor
+ }));
+
+ // Transaction server
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GenerateTimestamps);
+
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, StartTransaction);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, PingTransaction);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CommitTransaction);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, FlushTransaction);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AbortTransaction);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AttachTransaction);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, DetachTransaction);
+
+ // Cypress server
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ExistsNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, SetNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, MultisetAttributesNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, RemoveNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ListNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CreateNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, LockNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, UnlockNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CopyNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, MoveNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, LinkNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ConcatenateNodes);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ExternalizeNode);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, InternalizeNode);
+
+ // Tablet server
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, MountTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, UnmountTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, RemountTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, FreezeTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, UnfreezeTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ReshardTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ReshardTableAutomatic);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, TrimTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AlterTable);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AlterTableReplica);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetTablePivotKeys);
+
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, LookupRows);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, VersionedLookupRows);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, MultiLookup);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, SelectRows);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ExplainQuery);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, PullRows);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetInSyncReplicas);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetTabletInfos);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetTabletErrors);
+
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, BalanceTabletCells);
+
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ModifyRows);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, BatchModifyRows);
+
+ // Chaos
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AlterReplicationCard);
+
+ // Queues
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, PullQueue);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, PullConsumer);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, RegisterQueueConsumer);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, UnregisterQueueConsumer);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ListQueueConsumerRegistrations);
+
+ // Scheduler pools
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, TransferPoolResources);
+
+ // Operations
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, StartOperation);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AbortOperation);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, SuspendOperation);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ResumeOperation);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CompleteOperation);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, UpdateOperationParameters);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetOperation);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ListOperations);
+
+ // Jobs
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ListJobs);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetJob);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, DumpJobContext);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetJobInput,
+ .SetStreamingEnabled(true));
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetJobInputPaths);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetJobSpec);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetJobStderr);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetJobFailContext);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AbandonJob);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, PollJobShell);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AbortJob);
+
+ // Files
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ReadFile,
+ .SetStreamingEnabled(true));
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, WriteFile,
+ .SetStreamingEnabled(true));
+
+ // Journals
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ReadJournal,
+ .SetStreamingEnabled(true));
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, WriteJournal,
+ .SetStreamingEnabled(true));
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, TruncateJournal);
+
+ // Tables
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ReadTable,
+ .SetStreamingEnabled(true));
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, WriteTable,
+ .SetStreamingEnabled(true));
+
+ // File caching
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetFileFromCache);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, PutFileToCache);
+
+ // Object server
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CreateObject);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetTableMountInfo);
+
+ // Administration
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, BuildSnapshot);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GCCollect);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, SuspendCoordinator);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ResumeCoordinator);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, MigrateReplicationCards);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, SuspendChaosCells);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ResumeChaosCells);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AddMaintenance);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, RemoveMaintenance);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, DisableChunkLocations);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, DestroyChunkLocations);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, ResurrectChunkLocations);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, RequestReboot);
+
+ // Security
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, AddMember);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, RemoveMember);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CheckPermission);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CheckPermissionByAcl);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, TransferAccountResources);
+
+ // Metadata
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, GetColumnarStatistics);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, PartitionTables);
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, CheckClusterLiveness);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/client_base.cpp b/yt/yt/client/api/rpc_proxy/client_base.cpp
new file mode 100644
index 0000000000..fdf04b6490
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/client_base.cpp
@@ -0,0 +1,1021 @@
+#include "client_base.h"
+
+#include "api_service_proxy.h"
+#include "config.h"
+#include "file_reader.h"
+#include "file_writer.h"
+#include "helpers.h"
+#include "journal_reader.h"
+#include "journal_writer.h"
+#include "private.h"
+#include "table_reader.h"
+#include "table_writer.h"
+#include "transaction.h"
+
+#include <yt/yt/client/api/file_reader.h>
+#include <yt/yt/client/api/file_writer.h>
+#include <yt/yt/client/api/journal_reader.h>
+#include <yt/yt/client/api/journal_writer.h>
+#include <yt/yt/client/api/rowset.h>
+
+#include <yt/yt/client/chaos_client/replication_card_serialization.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_base.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/wire_protocol.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/library/auth/credentials_injecting_channel.h>
+
+#include <yt/yt/core/net/address.h>
+
+#include <yt/yt/core/ytree/attribute_filter.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NYPath;
+using namespace NYson;
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NObjectClient;
+using namespace NTransactionClient;
+using namespace NYTree;
+
+using NYT::ToProto;
+using NYT::FromProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+IConnectionPtr TClientBase::GetConnection()
+{
+ return GetRpcProxyConnection();
+}
+
+TApiServiceProxy TClientBase::CreateApiServiceProxy(NRpc::IChannelPtr channel)
+{
+ if (!channel) {
+ channel = GetRetryingChannel();
+ }
+ TApiServiceProxy proxy(channel);
+ const auto& config = GetRpcProxyConnection()->GetConfig();
+ proxy.SetDefaultTimeout(config->RpcTimeout);
+ proxy.SetDefaultRequestCodec(config->RequestCodec);
+ proxy.SetDefaultResponseCodec(config->ResponseCodec);
+ proxy.SetDefaultEnableLegacyRpcCodecs(config->EnableLegacyRpcCodecs);
+
+ NRpc::TStreamingParameters streamingParameters;
+ streamingParameters.ReadTimeout = config->DefaultStreamingStallTimeout;
+ streamingParameters.WriteTimeout = config->DefaultStreamingStallTimeout;
+ proxy.DefaultClientAttachmentsStreamingParameters() = streamingParameters;
+ proxy.DefaultServerAttachmentsStreamingParameters() = streamingParameters;
+
+ return proxy;
+}
+
+void TClientBase::InitStreamingRequest(NRpc::TClientRequest& request)
+{
+ auto connection = GetRpcProxyConnection();
+ const auto& config = connection->GetConfig();
+ request.SetTimeout(config->DefaultTotalStreamingTimeout);
+}
+
+TFuture<ITransactionPtr> TClientBase::StartTransaction(
+ ETransactionType type,
+ const TTransactionStartOptions& options)
+{
+ // Keep some stuff to reuse it in the transaction.
+ auto connection = GetRpcProxyConnection();
+ auto client = GetRpcProxyClient();
+ bool sticky = (type == ETransactionType::Tablet) || options.Sticky;
+ // Based on this flag the sticky channel will be wrapped into retrying after the first non-retrying call.
+ bool wrapStickyChannelOnResponse = false;
+ NRpc::IChannelPtr channel;
+ if (sticky) {
+ channel = CreateNonRetryingStickyChannel();
+ if (options.Id == NullTransactionId) {
+ channel = WrapStickyChannelIntoRetrying(std::move(channel));
+ } else {
+ wrapStickyChannelOnResponse = true;
+ }
+ } else {
+ channel = GetRetryingChannel();
+ }
+
+ const auto& config = connection->GetConfig();
+ auto timeout = options.Timeout.value_or(config->DefaultTransactionTimeout);
+ auto pingPeriod = options.PingPeriod.value_or(config->DefaultPingPeriod);
+
+ auto proxy = CreateApiServiceProxy(channel);
+
+ auto req = proxy.StartTransaction();
+ req->SetTimeout(config->RpcTimeout);
+
+ req->set_type(static_cast<NProto::ETransactionType>(type));
+ req->set_timeout(ToProto<i64>(timeout));
+ if (options.Deadline) {
+ req->set_deadline(ToProto<ui64>(*options.Deadline));
+ }
+ if (options.Id) {
+ ToProto(req->mutable_id(), options.Id);
+ }
+ if (options.ParentId) {
+ ToProto(req->mutable_parent_id(), options.ParentId);
+ }
+ ToProto(req->mutable_prerequisite_transaction_ids(), options.PrerequisiteTransactionIds);
+ // XXX(sandello): Better? Remove these fields from the protocol at all?
+ // COMPAT(kiselyovp): remove auto_abort from the protocol
+ req->set_auto_abort(false);
+ req->set_sticky(sticky);
+ req->set_ping(options.Ping);
+ req->set_ping_ancestors(options.PingAncestors);
+ req->set_atomicity(static_cast<NProto::EAtomicity>(options.Atomicity));
+ req->set_durability(static_cast<NProto::EDurability>(options.Durability));
+ if (options.Attributes) {
+ ToProto(req->mutable_attributes(), *options.Attributes);
+ }
+ if (options.StartTimestamp != NullTimestamp) {
+ req->set_start_timestamp(options.StartTimestamp);
+ }
+
+ return req->Invoke().Apply(BIND(
+ [
+ =,
+ this,
+ this_ = MakeStrong(this),
+ connection = std::move(connection),
+ client = std::move(client),
+ channel = std::move(channel)
+ ]
+ (const TApiServiceProxy::TRspStartTransactionPtr& rsp) mutable {
+ std::optional<TStickyTransactionParameters> stickyParameters;
+ if (sticky) {
+ stickyParameters.emplace().ProxyAddress = rsp->GetAddress();
+ }
+ auto transactionId = FromProto<TTransactionId>(rsp->id());
+ auto startTimestamp = FromProto<TTimestamp>(rsp->start_timestamp());
+ if (wrapStickyChannelOnResponse) {
+ // The first call within the sticky channel has been non-retrying,
+ // so wrap the channel into retrying now for future use.
+ channel = WrapStickyChannelIntoRetrying(std::move(channel));
+ }
+ return CreateTransaction(
+ std::move(connection),
+ std::move(client),
+ std::move(channel),
+ transactionId,
+ startTimestamp,
+ type,
+ options.Atomicity,
+ options.Durability,
+ timeout,
+ options.PingAncestors,
+ pingPeriod,
+ std::move(stickyParameters),
+ rsp->sequence_number_source_id(),
+ "Transaction started");
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CYPRESS
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<bool> TClientBase::NodeExists(
+ const TYPath& path,
+ const TNodeExistsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ExistsNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_master_read_options(), options);
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspExistsNodePtr& rsp) {
+ return rsp->exists();
+ }));
+}
+
+TFuture<TYsonString> TClientBase::GetNode(
+ const TYPath& path,
+ const TGetNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ // COMPAT(max42): after 22.3 is everywhere, drop legacy field.
+ if (options.Attributes) {
+ ToProto(req->mutable_legacy_attributes()->mutable_keys(), options.Attributes.Keys);
+ ToProto(req->mutable_attributes(), options.Attributes);
+ } else {
+ req->mutable_legacy_attributes()->set_all(true);
+ }
+
+ if (options.MaxSize) {
+ req->set_max_size(*options.MaxSize);
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_master_read_options(), options);
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+ if (options.Options) {
+ ToProto(req->mutable_options(), *options.Options);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetNodePtr& rsp) {
+ return TYsonString(rsp->value());
+ }));
+}
+
+TFuture<TYsonString> TClientBase::ListNode(
+ const TYPath& path,
+ const TListNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ListNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ // COMPAT(max42): after 22.3 is everywhere, drop legacy field.
+ if (options.Attributes) {
+ ToProto(req->mutable_legacy_attributes()->mutable_keys(), options.Attributes.Keys);
+ ToProto(req->mutable_attributes(), options.Attributes);
+ } else {
+ req->mutable_legacy_attributes()->set_all(true);
+ }
+
+ if (options.MaxSize) {
+ req->set_max_size(*options.MaxSize);
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_master_read_options(), options);
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspListNodePtr& rsp) {
+ return TYsonString(rsp->value());
+ }));
+}
+
+TFuture<NCypressClient::TNodeId> TClientBase::CreateNode(
+ const TYPath& path,
+ NObjectClient::EObjectType type,
+ const TCreateNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.CreateNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ req->set_type(ToProto<int>(type));
+
+ if (options.Attributes) {
+ ToProto(req->mutable_attributes(), *options.Attributes);
+ }
+ req->set_recursive(options.Recursive);
+ req->set_force(options.Force);
+ req->set_ignore_existing(options.IgnoreExisting);
+ req->set_lock_existing(options.LockExisting);
+ req->set_ignore_type_mismatch(options.IgnoreTypeMismatch);
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspCreateNodePtr& rsp) {
+ return FromProto<NCypressClient::TNodeId>(rsp->node_id());
+ }));
+}
+
+TFuture<void> TClientBase::RemoveNode(
+ const TYPath& path,
+ const TRemoveNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.RemoveNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ req->set_recursive(options.Recursive);
+ req->set_force(options.Force);
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClientBase::SetNode(
+ const TYPath& path,
+ const TYsonString& value,
+ const TSetNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.SetNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ req->set_value(value.ToString());
+ req->set_recursive(options.Recursive);
+ req->set_force(options.Force);
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClientBase::MultisetAttributesNode(
+ const TYPath& path,
+ const IMapNodePtr& attributes,
+ const TMultisetAttributesNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.MultisetAttributesNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ auto children = attributes->GetChildren();
+ std::sort(children.begin(), children.end());
+ for (const auto& [attribute, value] : children) {
+ auto* protoSubrequest = req->add_subrequests();
+ protoSubrequest->set_attribute(attribute);
+ protoSubrequest->set_value(ConvertToYsonString(value).ToString());
+ }
+
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<TLockNodeResult> TClientBase::LockNode(
+ const TYPath& path,
+ NCypressClient::ELockMode mode,
+ const TLockNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.LockNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ req->set_mode(ToProto<int>(mode));
+
+ req->set_waitable(options.Waitable);
+ if (options.ChildKey) {
+ req->set_child_key(*options.ChildKey);
+ }
+ if (options.AttributeKey) {
+ req->set_attribute_key(*options.AttributeKey);
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspLockNodePtr& rsp) {
+ TLockNodeResult result;
+ FromProto(&result.NodeId, rsp->node_id());
+ FromProto(&result.LockId, rsp->lock_id());
+ FromProto(&result.Revision, rsp->revision());
+ return result;
+ }));
+}
+
+TFuture<void> TClientBase::UnlockNode(
+ const TYPath& path,
+ const TUnlockNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.UnlockNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<NCypressClient::TNodeId> TClientBase::CopyNode(
+ const TYPath& srcPath,
+ const TYPath& dstPath,
+ const TCopyNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.CopyNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_src_path(srcPath);
+ req->set_dst_path(dstPath);
+
+ req->set_recursive(options.Recursive);
+ req->set_ignore_existing(options.IgnoreExisting);
+ req->set_lock_existing(options.LockExisting);
+ req->set_force(options.Force);
+ req->set_preserve_account(options.PreserveAccount);
+ req->set_preserve_creation_time(options.PreserveCreationTime);
+ req->set_preserve_modification_time(options.PreserveModificationTime);
+ req->set_preserve_expiration_time(options.PreserveExpirationTime);
+ req->set_preserve_expiration_timeout(options.PreserveExpirationTimeout);
+ req->set_preserve_owner(options.PreserveOwner);
+ req->set_preserve_acl(options.PreserveAcl);
+ req->set_pessimistic_quota_check(options.PessimisticQuotaCheck);
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspCopyNodePtr& rsp) {
+ return FromProto<NCypressClient::TNodeId>(rsp->node_id());
+ }));
+}
+
+TFuture<NCypressClient::TNodeId> TClientBase::MoveNode(
+ const TYPath& srcPath,
+ const TYPath& dstPath,
+ const TMoveNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.MoveNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_src_path(srcPath);
+ req->set_dst_path(dstPath);
+
+ req->set_recursive(options.Recursive);
+ req->set_force(options.Force);
+ req->set_preserve_account(options.PreserveAccount);
+ req->set_preserve_creation_time(options.PreserveCreationTime);
+ req->set_preserve_modification_time(options.PreserveModificationTime);
+ req->set_preserve_expiration_time(options.PreserveExpirationTime);
+ req->set_preserve_expiration_timeout(options.PreserveExpirationTimeout);
+ req->set_preserve_owner(options.PreserveOwner);
+ req->set_pessimistic_quota_check(options.PessimisticQuotaCheck);
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspMoveNodePtr& rsp) {
+ return FromProto<NCypressClient::TNodeId>(rsp->node_id());
+ }));
+}
+
+TFuture<NCypressClient::TNodeId> TClientBase::LinkNode(
+ const TYPath& srcPath,
+ const TYPath& dstPath,
+ const TLinkNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.LinkNode();
+ SetTimeoutOptions(*req, options);
+
+ req->set_src_path(srcPath);
+ req->set_dst_path(dstPath);
+
+ req->set_recursive(options.Recursive);
+ req->set_force(options.Force);
+ req->set_ignore_existing(options.IgnoreExisting);
+ req->set_lock_existing(options.LockExisting);
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspLinkNodePtr& rsp) {
+ return FromProto<NCypressClient::TNodeId>(rsp->node_id());
+ }));
+}
+
+TFuture<void> TClientBase::ConcatenateNodes(
+ const std::vector<TRichYPath>& srcPaths,
+ const TRichYPath& dstPath,
+ const TConcatenateNodesOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ConcatenateNodes();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_src_paths(), srcPaths);
+ ToProto(req->mutable_dst_path(), dstPath);
+ ToProto(req->mutable_transactional_options(), options);
+ // TODO(babenko)
+ // ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClientBase::ExternalizeNode(
+ const TYPath& path,
+ TCellTag cellTag,
+ const TExternalizeNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ExternalizeNode();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_path(), path);
+ req->set_cell_tag(ToProto<int>(cellTag));
+ ToProto(req->mutable_transactional_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClientBase::InternalizeNode(
+ const TYPath& path,
+ const TInternalizeNodeOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.InternalizeNode();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_path(), path);
+ ToProto(req->mutable_transactional_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<NObjectClient::TObjectId> TClientBase::CreateObject(
+ NObjectClient::EObjectType type,
+ const TCreateObjectOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.CreateObject();
+
+ req->set_type(ToProto<int>(type));
+ req->set_ignore_existing(options.IgnoreExisting);
+ req->set_sync(options.Sync);
+ if (options.Attributes) {
+ ToProto(req->mutable_attributes(), *options.Attributes);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspCreateObjectPtr& rsp) {
+ return FromProto<NObjectClient::TObjectId>(rsp->object_id());
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<IFileReaderPtr> TClientBase::CreateFileReader(
+ const TYPath& path,
+ const TFileReaderOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.ReadFile();
+ InitStreamingRequest(*req);
+
+ req->set_path(path);
+ if (options.Offset) {
+ req->set_offset(*options.Offset);
+ }
+ if (options.Length) {
+ req->set_length(*options.Length);
+ }
+ if (options.Config) {
+ req->set_config(ConvertToYsonString(*options.Config).ToString());
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+
+ return NRpcProxy::CreateFileReader(std::move(req));
+}
+
+IFileWriterPtr TClientBase::CreateFileWriter(
+ const TRichYPath& path,
+ const TFileWriterOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.WriteFile();
+ InitStreamingRequest(*req);
+
+ ToProto(req->mutable_path(), path);
+
+ req->set_compute_md5(options.ComputeMD5);
+ if (options.Config) {
+ req->set_config(ConvertToYsonString(*options.Config).ToString());
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+
+ return NRpcProxy::CreateFileWriter(std::move(req));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+IJournalReaderPtr TClientBase::CreateJournalReader(
+ const TYPath& path,
+ const TJournalReaderOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.ReadJournal();
+ InitStreamingRequest(*req);
+
+ req->set_path(path);
+
+ if (options.FirstRowIndex) {
+ req->set_first_row_index(*options.FirstRowIndex);
+ }
+ if (options.RowCount) {
+ req->set_row_count(*options.RowCount);
+ }
+ if (options.Config) {
+ req->set_config(ConvertToYsonString(*options.Config).ToString());
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+
+ return NRpcProxy::CreateJournalReader(std::move(req));
+}
+
+IJournalWriterPtr TClientBase::CreateJournalWriter(
+ const TYPath& path,
+ const TJournalWriterOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.WriteJournal();
+ InitStreamingRequest(*req);
+
+ req->set_path(path);
+
+ if (options.Config) {
+ req->set_config(ConvertToYsonString(*options.Config).ToString());
+ }
+
+ req->set_enable_multiplexing(options.EnableMultiplexing);
+ req->set_enable_chunk_preallocation(options.EnableChunkPreallocation);
+ req->set_replica_lag_limit(options.ReplicaLagLimit);
+ // TODO(kiselyovp) profiler is ignored
+
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+
+ return NRpcProxy::CreateJournalWriter(std::move(req));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<ITableReaderPtr> TClientBase::CreateTableReader(
+ const TRichYPath& path,
+ const TTableReaderOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.ReadTable();
+ InitStreamingRequest(*req);
+
+ ToProto(req->mutable_path(), path);
+
+ req->set_unordered(options.Unordered);
+ req->set_omit_inaccessible_columns(options.OmitInaccessibleColumns);
+ req->set_enable_table_index(options.EnableTableIndex);
+ req->set_enable_row_index(options.EnableRowIndex);
+ req->set_enable_range_index(options.EnableRangeIndex);
+ if (options.Config) {
+ req->set_config(ConvertToYsonString(*options.Config).ToString());
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+
+ return NRpcProxy::CreateTableReader(std::move(req));
+}
+
+TFuture<ITableWriterPtr> TClientBase::CreateTableWriter(
+ const TRichYPath& path,
+ const TTableWriterOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.WriteTable();
+ InitStreamingRequest(*req);
+
+ ToProto(req->mutable_path(), path);
+
+ if (options.Config) {
+ req->set_config(ConvertToYsonString(*options.Config).ToString());
+ }
+
+ ToProto(req->mutable_transactional_options(), options);
+
+ return NRpcProxy::CreateTableWriter(std::move(req));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<IUnversionedRowsetPtr> TClientBase::LookupRows(
+ const TYPath& path,
+ TNameTablePtr nameTable,
+ const TSharedRange<TLegacyKey>& keys,
+ const TLookupRowsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.LookupRows();
+ req->SetResponseHeavy(true);
+ req->SetTimeout(options.Timeout.value_or(GetRpcProxyConnection()->GetConfig()->DefaultLookupRowsTimeout));
+
+ req->set_path(path);
+ req->Attachments() = SerializeRowset(nameTable, keys, req->mutable_rowset_descriptor());
+
+ if (!options.ColumnFilter.IsUniversal()) {
+ for (auto id : options.ColumnFilter.GetIndexes()) {
+ req->add_columns(TString(nameTable->GetName(id)));
+ }
+ }
+ req->set_timestamp(options.Timestamp);
+ req->set_retention_timestamp(options.RetentionTimestamp);
+ req->set_keep_missing_rows(options.KeepMissingRows);
+ req->set_enable_partial_result(options.EnablePartialResult);
+ req->set_replica_consistency(static_cast<NProto::EReplicaConsistency>(options.ReplicaConsistency));
+ if (options.UseLookupCache) {
+ req->set_use_lookup_cache(*options.UseLookupCache);
+ }
+
+ req->SetMultiplexingBand(options.MultiplexingBand);
+ req->set_multiplexing_band(static_cast<NProto::EMultiplexingBand>(options.MultiplexingBand));
+
+ ToProto(req->mutable_tablet_read_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspLookupRowsPtr& rsp) {
+ return DeserializeRowset<TUnversionedRow>(
+ rsp->rowset_descriptor(),
+ MergeRefsToRef<TRpcProxyClientBufferTag>(rsp->Attachments()));
+ }));
+}
+
+TFuture<IVersionedRowsetPtr> TClientBase::VersionedLookupRows(
+ const TYPath& path,
+ TNameTablePtr nameTable,
+ const TSharedRange<TLegacyKey>& keys,
+ const TVersionedLookupRowsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.VersionedLookupRows();
+ req->SetResponseHeavy(true);
+ req->SetTimeout(options.Timeout.value_or(GetRpcProxyConnection()->GetConfig()->DefaultLookupRowsTimeout));
+
+ req->set_path(path);
+ req->Attachments() = SerializeRowset(nameTable, keys, req->mutable_rowset_descriptor());
+
+ if (!options.ColumnFilter.IsUniversal()) {
+ for (auto id : options.ColumnFilter.GetIndexes()) {
+ req->add_columns(TString(nameTable->GetName(id)));
+ }
+ }
+ req->set_timestamp(options.Timestamp);
+ req->set_keep_missing_rows(options.KeepMissingRows);
+ req->set_enable_partial_result(options.EnablePartialResult);
+ req->set_replica_consistency(static_cast<NProto::EReplicaConsistency>(options.ReplicaConsistency));
+ if (options.UseLookupCache) {
+ req->set_use_lookup_cache(*options.UseLookupCache);
+ }
+
+ req->SetMultiplexingBand(options.MultiplexingBand);
+ req->set_multiplexing_band(static_cast<NProto::EMultiplexingBand>(options.MultiplexingBand));
+ if (options.RetentionConfig) {
+ ToProto(req->mutable_retention_config(), *options.RetentionConfig);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspVersionedLookupRowsPtr& rsp) {
+ return DeserializeRowset<TVersionedRow>(
+ rsp->rowset_descriptor(),
+ MergeRefsToRef<TRpcProxyClientBufferTag>(rsp->Attachments()));
+ }));
+}
+
+TFuture<std::vector<IUnversionedRowsetPtr>> TClientBase::MultiLookup(
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.MultiLookup();
+ req->SetResponseHeavy(true);
+ req->SetTimeout(options.Timeout.value_or(GetRpcProxyConnection()->GetConfig()->DefaultLookupRowsTimeout));
+ req->SetMultiplexingBand(options.MultiplexingBand);
+
+ for (const auto& subrequest : subrequests) {
+ auto* protoSubrequest = req->add_subrequests();
+
+ protoSubrequest->set_path(subrequest.Path);
+
+ const auto& subrequestOptions = subrequest.Options;
+ if (!subrequestOptions.ColumnFilter.IsUniversal()) {
+ for (auto id : subrequestOptions.ColumnFilter.GetIndexes()) {
+ protoSubrequest->add_columns(TString(subrequest.NameTable->GetName(id)));
+ }
+ }
+ protoSubrequest->set_keep_missing_rows(subrequestOptions.KeepMissingRows);
+ protoSubrequest->set_enable_partial_result(subrequestOptions.EnablePartialResult);
+ if (subrequestOptions.UseLookupCache) {
+ protoSubrequest->set_use_lookup_cache(*subrequestOptions.UseLookupCache);
+ }
+
+ auto rowset = SerializeRowset(
+ subrequest.NameTable,
+ subrequest.Keys,
+ protoSubrequest->mutable_rowset_descriptor());
+ protoSubrequest->set_attachment_count(rowset.size());
+ req->Attachments().insert(req->Attachments().end(), rowset.begin(), rowset.end());
+ }
+
+ req->set_replica_consistency(static_cast<NProto::EReplicaConsistency>(options.ReplicaConsistency));
+ req->set_timestamp(options.Timestamp);
+ req->set_retention_timestamp(options.RetentionTimestamp);
+ req->set_multiplexing_band(static_cast<NProto::EMultiplexingBand>(options.MultiplexingBand));
+ ToProto(req->mutable_tablet_read_options(), options);
+
+ return req->Invoke().Apply(BIND([subrequestCount = std::ssize(subrequests)] (const TApiServiceProxy::TRspMultiLookupPtr& rsp) {
+ YT_VERIFY(subrequestCount == rsp->subresponses_size());
+
+ std::vector<IUnversionedRowsetPtr> result;
+ result.reserve(subrequestCount);
+
+ int beginAttachmentIndex = 0;
+ for (const auto& subresponse : rsp->subresponses()) {
+ int endAttachmentIndex = beginAttachmentIndex + subresponse.attachment_count();
+ YT_VERIFY(endAttachmentIndex <= std::ssize(rsp->Attachments()));
+
+ std::vector<TSharedRef> subresponseAttachments{
+ rsp->Attachments().begin() + beginAttachmentIndex,
+ rsp->Attachments().begin() + endAttachmentIndex};
+ result.push_back(DeserializeRowset<TUnversionedRow>(
+ subresponse.rowset_descriptor(),
+ MergeRefsToRef<TRpcProxyClientBufferTag>(std::move(subresponseAttachments))));
+
+ beginAttachmentIndex = endAttachmentIndex;
+ }
+ YT_VERIFY(beginAttachmentIndex == std::ssize(rsp->Attachments()));
+
+ return result;
+ }));
+}
+
+template<class TRequest>
+void FillRequestBySelectRowsOptionsBase(const TSelectRowsOptionsBase& options, TRequest request)
+{
+ request->set_timestamp(options.Timestamp);
+ if (options.UdfRegistryPath) {
+ request->set_udf_registry_path(*options.UdfRegistryPath);
+ }
+}
+
+TFuture<TSelectRowsResult> TClientBase::SelectRows(
+ const TString& query,
+ const TSelectRowsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.SelectRows();
+ req->SetResponseHeavy(true);
+ req->set_query(query);
+
+ FillRequestBySelectRowsOptionsBase(options, req);
+ // TODO(ifsmirnov): retention timestamp in explain_query.
+ req->set_retention_timestamp(options.RetentionTimestamp);
+ // TODO(lukyan): Move to FillRequestBySelectRowsOptionsBase
+ req->SetTimeout(options.Timeout.value_or(GetRpcProxyConnection()->GetConfig()->DefaultSelectRowsTimeout));
+
+ if (options.InputRowLimit) {
+ req->set_input_row_limit(*options.InputRowLimit);
+ }
+ if (options.OutputRowLimit) {
+ req->set_output_row_limit(*options.OutputRowLimit);
+ }
+ req->set_range_expansion_limit(options.RangeExpansionLimit);
+ req->set_max_subqueries(options.MaxSubqueries);
+ req->set_allow_full_scan(options.AllowFullScan);
+ req->set_allow_join_without_index(options.AllowJoinWithoutIndex);
+
+ if (options.ExecutionPool) {
+ req->set_execution_pool(*options.ExecutionPool);
+ }
+ if (options.PlaceholderValues) {
+ req->set_placeholder_values(options.PlaceholderValues.ToString());
+ }
+ req->set_fail_on_incomplete_result(options.FailOnIncompleteResult);
+ req->set_verbose_logging(options.VerboseLogging);
+ req->set_new_range_inference(options.NewRangeInference);
+ req->set_enable_code_cache(options.EnableCodeCache);
+ req->set_memory_limit_per_node(options.MemoryLimitPerNode);
+ ToProto(req->mutable_suppressable_access_tracking_options(), options);
+ req->set_replica_consistency(static_cast<NProto::EReplicaConsistency>(options.ReplicaConsistency));
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspSelectRowsPtr& rsp) {
+ TSelectRowsResult result;
+ result.Rowset = DeserializeRowset<TUnversionedRow>(
+ rsp->rowset_descriptor(),
+ MergeRefsToRef<TRpcProxyClientBufferTag>(rsp->Attachments()));
+ FromProto(&result.Statistics, rsp->statistics());
+ return result;
+ }));
+}
+
+TFuture<TYsonString> TClientBase::ExplainQuery(
+ const TString& query,
+ const TExplainQueryOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ExplainQuery();
+ req->set_query(query);
+ FillRequestBySelectRowsOptionsBase(options, req);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspExplainQueryPtr& rsp) {
+ return TYsonString(rsp->value());
+ }));
+}
+
+TFuture<TPullRowsResult> TClientBase::PullRows(
+ const TYPath& path,
+ const TPullRowsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.PullRows();
+ req->SetResponseHeavy(true);
+ req->set_path(path);
+ ToProto(req->mutable_upstream_replica_id(), options.UpstreamReplicaId);
+ req->set_order_rows_by_timestamp(options.OrderRowsByTimestamp);
+ req->set_tablet_rows_per_read(options.TabletRowsPerRead);
+ ToProto(req->mutable_replication_progress(), options.ReplicationProgress);
+ if (options.UpperTimestamp != NullTimestamp) {
+ req->set_upper_timestamp(options.UpperTimestamp);
+ }
+ for (auto [tabletId, rowIndex] : options.StartReplicationRowIndexes) {
+ auto *protoReplicationRowIndex = req->add_start_replication_row_indexes();
+ ToProto(protoReplicationRowIndex->mutable_tablet_id(), tabletId);
+ protoReplicationRowIndex->set_row_index(rowIndex);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspPullRowsPtr& rsp) {
+ TPullRowsResult result;
+ result.RowCount = rsp->row_count();
+ result.DataWeight = rsp->data_weight();
+ result.Versioned = rsp->versioned();
+ FromProto(&result.ReplicationProgress, rsp->replication_progress());
+
+ for (auto protoReplicationRowIndex : rsp->end_replication_row_indexes()) {
+ auto tabletId = FromProto<TTabletId>(protoReplicationRowIndex.tablet_id());
+ int rowIndex = protoReplicationRowIndex.row_index();
+ if (result.EndReplicationRowIndexes.contains(tabletId)) {
+ THROW_ERROR_EXCEPTION("Duplicate tablet id in end replication row indexes")
+ << TErrorAttribute("tablet_id", tabletId);
+ }
+ InsertOrCrash(result.EndReplicationRowIndexes, std::make_pair(tabletId, rowIndex));
+ }
+
+ result.Rowset = DeserializeRowset(
+ rsp->rowset_descriptor(),
+ MergeRefsToRef<TRpcProxyClientBufferTag>(rsp->Attachments()),
+ rsp->versioned());
+
+ return result;
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/client_base.h b/yt/yt/client/api/rpc_proxy/client_base.h
new file mode 100644
index 0000000000..f8df2a67a8
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/client_base.h
@@ -0,0 +1,192 @@
+#pragma once
+
+#include "private.h"
+#include "helpers.h"
+#include "connection_impl.h"
+#include "api_service_proxy.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/client/driver/private.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TRpcProxyClientBufferTag
+{ };
+
+class TClientBase
+ : public virtual NApi::IClientBase
+{
+protected:
+ // Returns a bound RPC proxy connection for this interface.
+ virtual TConnectionPtr GetRpcProxyConnection() = 0;
+ // Returns a bound RPC proxy client for this interface.
+ virtual TClientPtr GetRpcProxyClient() = 0;
+ // Returns an RPC channel to use for API calls.
+ virtual NRpc::IChannelPtr GetRetryingChannel() const = 0;
+ // Returns an RPC channel to use for API calls within single transaction.
+ // The channel is non-retrying, so should be wrapped into retrying channel on demand.
+ virtual NRpc::IChannelPtr CreateNonRetryingStickyChannel() const = 0;
+ // Wraps the underlying sticky channel into retrying channel.
+ // Stickiness affects retries policy.
+ virtual NRpc::IChannelPtr WrapStickyChannelIntoRetrying(NRpc::IChannelPtr underlying) const = 0;
+
+ friend class TTransaction;
+
+public:
+ NApi::IConnectionPtr GetConnection() override;
+
+ // Helpers for accessing low-lepel API service proxy.
+ // Prefer using interface methods unless you know exactly what you are doing.
+ TApiServiceProxy CreateApiServiceProxy(NRpc::IChannelPtr channel = {});
+ void InitStreamingRequest(NRpc::TClientRequest& request);
+
+ // Transactions.
+ TFuture<NApi::ITransactionPtr> StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const NApi::TTransactionStartOptions& options) override;
+
+ // Tables.
+ TFuture<NApi::IUnversionedRowsetPtr> LookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const NApi::TLookupRowsOptions& options) override;
+
+ TFuture<NApi::IVersionedRowsetPtr> VersionedLookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const NApi::TVersionedLookupRowsOptions& options) override;
+
+ TFuture<std::vector<IUnversionedRowsetPtr>> MultiLookup(
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options = {}) override;
+
+ TFuture<NApi::TSelectRowsResult> SelectRows(
+ const TString& query,
+ const NApi::TSelectRowsOptions& options) override;
+
+ TFuture<NYson::TYsonString> ExplainQuery(
+ const TString& query,
+ const NApi::TExplainQueryOptions& options) override;
+
+ virtual TFuture<TPullRowsResult> PullRows(
+ const NYPath::TYPath& path,
+ const NApi::TPullRowsOptions& options) override;
+
+ // TODO(babenko): batch read and batch write.
+
+ // Cypress.
+ TFuture<bool> NodeExists(
+ const NYPath::TYPath& path,
+ const NApi::TNodeExistsOptions& options) override;
+
+ TFuture<NYson::TYsonString> GetNode(
+ const NYPath::TYPath& path,
+ const NApi::TGetNodeOptions& options) override;
+
+ TFuture<void> SetNode(
+ const NYPath::TYPath& path,
+ const NYson::TYsonString& value,
+ const NApi::TSetNodeOptions& options) override;
+
+ TFuture<void> MultisetAttributesNode(
+ const NYPath::TYPath& path,
+ const NYTree::IMapNodePtr& attributes,
+ const NApi::TMultisetAttributesNodeOptions& options) override;
+
+ TFuture<void> RemoveNode(
+ const NYPath::TYPath& path,
+ const NApi::TRemoveNodeOptions& options) override;
+
+ TFuture<NYson::TYsonString> ListNode(
+ const NYPath::TYPath& path,
+ const NApi::TListNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> CreateNode(
+ const NYPath::TYPath& path,
+ NObjectClient::EObjectType type,
+ const NApi::TCreateNodeOptions& options) override;
+
+ TFuture<NApi::TLockNodeResult> LockNode(
+ const NYPath::TYPath& path,
+ NCypressClient::ELockMode mode,
+ const NApi::TLockNodeOptions& options) override;
+
+ TFuture<void> UnlockNode(
+ const NYPath::TYPath& path,
+ const NApi::TUnlockNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> CopyNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const NApi::TCopyNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> MoveNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const NApi::TMoveNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> LinkNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const NApi::TLinkNodeOptions& options) override;
+
+ TFuture<void> ConcatenateNodes(
+ const std::vector<NYPath::TRichYPath>& srcPaths,
+ const NYPath::TRichYPath& dstPath,
+ const NApi::TConcatenateNodesOptions& options) override;
+
+ TFuture<void> ExternalizeNode(
+ const NYPath::TYPath& path,
+ NObjectClient::TCellTag cellTag,
+ const TExternalizeNodeOptions& options = {}) override;
+
+ TFuture<void> InternalizeNode(
+ const NYPath::TYPath& path,
+ const TInternalizeNodeOptions& options = {}) override;
+
+ // Objects.
+ TFuture<NObjectClient::TObjectId> CreateObject(
+ NObjectClient::EObjectType type,
+ const NApi::TCreateObjectOptions& options) override;
+
+ //! NB: Readers and writers returned by methods below are NOT thread-safe.
+ // Files.
+ TFuture<NApi::IFileReaderPtr> CreateFileReader(
+ const NYPath::TYPath& path,
+ const NApi::TFileReaderOptions& options) override;
+
+ NApi::IFileWriterPtr CreateFileWriter(
+ const NYPath::TRichYPath& path,
+ const NApi::TFileWriterOptions& options) override;
+
+ // Journals.
+ NApi::IJournalReaderPtr CreateJournalReader(
+ const NYPath::TYPath& path,
+ const NApi::TJournalReaderOptions& options) override;
+
+ NApi::IJournalWriterPtr CreateJournalWriter(
+ const NYPath::TYPath& path,
+ const NApi::TJournalWriterOptions& options) override;
+
+ // Tables.
+ TFuture<NApi::ITableReaderPtr> CreateTableReader(
+ const NYPath::TRichYPath& path,
+ const NApi::TTableReaderOptions& options) override;
+
+ TFuture<NApi::ITableWriterPtr> CreateTableWriter(
+ const NYPath::TRichYPath& path,
+ const NApi::TTableWriterOptions& options) override;
+};
+
+DEFINE_REFCOUNTED_TYPE(TClientBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/client_impl.cpp b/yt/yt/client/api/rpc_proxy/client_impl.cpp
new file mode 100644
index 0000000000..34624f4640
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/client_impl.cpp
@@ -0,0 +1,2034 @@
+#include "client_impl.h"
+
+#include "config.h"
+#include "helpers.h"
+#include "private.h"
+#include "table_mount_cache.h"
+#include "timestamp_provider.h"
+#include "transaction.h"
+
+#include <yt/yt/client/api/helpers.h>
+#include <yt/yt/client/api/rowset.h>
+#include <yt/yt/client/api/transaction.h>
+
+#include <yt/yt/client/chaos_client/replication_card_serialization.h>
+
+#include <yt/yt/client/transaction_client/remote_timestamp_provider.h>
+
+#include <yt/yt/client/scheduler/operation_id_or_alias.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_base.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/wire_protocol.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/library/auth/credentials_injecting_channel.h>
+
+#include <yt/yt/core/net/address.h>
+
+#include <yt/yt/core/rpc/dynamic_channel_pool.h>
+#include <yt/yt/core/rpc/retrying_channel.h>
+#include <yt/yt/core/rpc/stream.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <util/generic/cast.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using NYT::ToProto;
+using NYT::FromProto;
+
+using namespace NAuth;
+using namespace NChaosClient;
+using namespace NChunkClient;
+using namespace NObjectClient;
+using namespace NRpc;
+using namespace NScheduler;
+using namespace NQueueClient;
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NTransactionClient;
+using namespace NYPath;
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TClient::TClient(
+ TConnectionPtr connection,
+ const TClientOptions& clientOptions)
+ : Connection_(std::move(connection))
+ , RetryingChannel_(MaybeCreateRetryingChannel(
+ CreateCredentialsInjectingChannel(
+ Connection_->CreateChannel(false),
+ clientOptions),
+ /*retryProxyBanned*/ true))
+ , ClientOptions_(clientOptions)
+ , TableMountCache_(BIND(
+ &CreateTableMountCache,
+ Connection_->GetConfig()->TableMountCache,
+ RetryingChannel_,
+ RpcProxyClientLogger,
+ Connection_->GetConfig()->RpcTimeout))
+ , TimestampProvider_(BIND(&TClient::CreateTimestampProvider, Unretained(this)))
+{ }
+
+const ITableMountCachePtr& TClient::GetTableMountCache()
+{
+ return TableMountCache_.Value();
+}
+
+const IReplicationCardCachePtr& TClient::GetReplicationCardCache()
+{
+ YT_UNIMPLEMENTED();
+}
+
+const ITimestampProviderPtr& TClient::GetTimestampProvider()
+{
+ return TimestampProvider_.Value();
+}
+
+void TClient::Terminate()
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+IChannelPtr TClient::MaybeCreateRetryingChannel(NRpc::IChannelPtr channel, bool retryProxyBanned) const
+{
+ const auto& config = Connection_->GetConfig();
+ if (config->EnableRetries) {
+ return NRpc::CreateRetryingChannel(
+ config->RetryingChannel,
+ std::move(channel),
+ BIND([=] (const TError& error) {
+ return IsRetriableError(error, retryProxyBanned);
+ }));
+ } else {
+ return channel;
+ }
+}
+
+IChannelPtr TClient::CreateNonRetryingChannelByAddress(const TString& address) const
+{
+ return CreateCredentialsInjectingChannel(
+ Connection_->CreateChannelByAddress(address),
+ ClientOptions_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TConnectionPtr TClient::GetRpcProxyConnection()
+{
+ return Connection_;
+}
+
+TClientPtr TClient::GetRpcProxyClient()
+{
+ return this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IChannelPtr TClient::GetRetryingChannel() const
+{
+ return RetryingChannel_;
+}
+
+IChannelPtr TClient::CreateNonRetryingStickyChannel() const
+{
+ return CreateCredentialsInjectingChannel(
+ Connection_->CreateChannel(true),
+ ClientOptions_);
+}
+
+IChannelPtr TClient::WrapStickyChannelIntoRetrying(IChannelPtr underlying) const
+{
+ return MaybeCreateRetryingChannel(
+ std::move(underlying),
+ /*retryProxyBanned*/ false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ITimestampProviderPtr TClient::CreateTimestampProvider() const
+{
+ return NRpcProxy::CreateTimestampProvider(
+ RetryingChannel_,
+ Connection_->GetConfig()->RpcTimeout,
+ Connection_->GetConfig()->TimestampProviderLatestTimestampUpdatePeriod,
+ Connection_->GetConfig()->ClockClusterTag);
+}
+
+ITransactionPtr TClient::AttachTransaction(
+ TTransactionId transactionId,
+ const TTransactionAttachOptions& options)
+{
+ auto connection = GetRpcProxyConnection();
+ auto client = GetRpcProxyClient();
+
+ auto channel = options.StickyAddress
+ ? WrapStickyChannelIntoRetrying(CreateNonRetryingChannelByAddress(options.StickyAddress))
+ : GetRetryingChannel();
+
+ auto proxy = CreateApiServiceProxy(channel);
+
+ auto req = proxy.AttachTransaction();
+ ToProto(req->mutable_transaction_id(), transactionId);
+ // COMPAT(kiselyovp): remove auto_abort from the protocol
+ req->set_auto_abort(false);
+ if (options.PingPeriod) {
+ req->set_ping_period(options.PingPeriod->GetValue());
+ }
+ req->set_ping(options.Ping);
+ req->set_ping_ancestors(options.PingAncestors);
+
+ auto rsp = NConcurrency::WaitFor(req->Invoke())
+ .ValueOrThrow();
+
+ auto transactionType = static_cast<ETransactionType>(rsp->type());
+ auto startTimestamp = static_cast<TTimestamp>(rsp->start_timestamp());
+ auto atomicity = static_cast<EAtomicity>(rsp->atomicity());
+ auto durability = static_cast<EDurability>(rsp->durability());
+ auto timeout = TDuration::FromValue(NYT::FromProto<i64>(rsp->timeout()));
+
+ if (options.StickyAddress && transactionType != ETransactionType::Tablet) {
+ THROW_ERROR_EXCEPTION("Sticky address is supported for tablet transactions only");
+ }
+
+ std::optional<TStickyTransactionParameters> stickyParameters;
+ if (options.StickyAddress || transactionType == ETransactionType::Tablet) {
+ stickyParameters.emplace();
+ if (options.StickyAddress) {
+ stickyParameters->ProxyAddress = options.StickyAddress;
+ } else {
+ stickyParameters->ProxyAddress = rsp->GetAddress();
+ }
+ }
+
+ return CreateTransaction(
+ std::move(connection),
+ std::move(client),
+ std::move(channel),
+ transactionId,
+ startTimestamp,
+ transactionType,
+ atomicity,
+ durability,
+ timeout,
+ options.PingAncestors,
+ options.PingPeriod,
+ std::move(stickyParameters),
+ rsp->sequence_number_source_id(),
+ "Transaction attached");
+}
+
+TFuture<void> TClient::MountTable(
+ const TYPath& path,
+ const TMountTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.MountTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ NYT::ToProto(req->mutable_cell_id(), options.CellId);
+ if (!options.TargetCellIds.empty()) {
+ NYT::ToProto(req->mutable_target_cell_ids(), options.TargetCellIds);
+ }
+ req->set_freeze(options.Freeze);
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::UnmountTable(
+ const TYPath& path,
+ const TUnmountTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.UnmountTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ req->set_force(options.Force);
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::RemountTable(
+ const TYPath& path,
+ const TRemountTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.RemountTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::FreezeTable(
+ const TYPath& path,
+ const TFreezeTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.FreezeTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::UnfreezeTable(
+ const TYPath& path,
+ const TUnfreezeTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.UnfreezeTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::ReshardTable(
+ const TYPath& path,
+ const std::vector<TLegacyOwningKey>& pivotKeys,
+ const TReshardTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ReshardTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ auto writer = CreateWireProtocolWriter();
+ // XXX(sandello): This is ugly and inefficient.
+ std::vector<TUnversionedRow> keys;
+ for (const auto& pivotKey : pivotKeys) {
+ keys.push_back(pivotKey);
+ }
+ writer->WriteRowset(MakeRange(keys));
+ req->Attachments() = writer->Finish();
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::ReshardTable(
+ const TYPath& path,
+ int tabletCount,
+ const TReshardTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ReshardTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ req->set_tablet_count(tabletCount);
+ if (options.Uniform) {
+ req->set_uniform(*options.Uniform);
+ }
+ if (options.EnableSlicing) {
+ req->set_enable_slicing(*options.EnableSlicing);
+ }
+ if (options.SlicingAccuracy) {
+ req->set_slicing_accuracy(*options.SlicingAccuracy);
+ }
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<std::vector<TTabletActionId>> TClient::ReshardTableAutomatic(
+ const TYPath& path,
+ const TReshardTableAutomaticOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ReshardTableAutomatic();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ req->set_keep_actions(options.KeepActions);
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_tablet_range_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspReshardTableAutomaticPtr>& rspOrError) {
+ const auto& rsp = rspOrError.ValueOrThrow();
+ return FromProto<std::vector<TTabletActionId>>(rsp->tablet_actions());
+ }));
+}
+
+TFuture<void> TClient::TrimTable(
+ const TYPath& path,
+ int tabletIndex,
+ i64 trimmedRowCount,
+ const TTrimTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.TrimTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ req->set_tablet_index(tabletIndex);
+ req->set_trimmed_row_count(trimmedRowCount);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::AlterTable(
+ const TYPath& path,
+ const TAlterTableOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AlterTable();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ if (options.Schema) {
+ req->set_schema(ConvertToYsonString(*options.Schema).ToString());
+ }
+ if (options.SchemaId) {
+ ToProto(req->mutable_schema_id(), *options.SchemaId);
+ }
+ if (options.Dynamic) {
+ req->set_dynamic(*options.Dynamic);
+ }
+ if (options.UpstreamReplicaId) {
+ ToProto(req->mutable_upstream_replica_id(), *options.UpstreamReplicaId);
+ }
+ if (options.SchemaModification) {
+ req->set_schema_modification(static_cast<NProto::ETableSchemaModification>(*options.SchemaModification));
+ }
+ if (options.ReplicationProgress) {
+ ToProto(req->mutable_replication_progress(), *options.ReplicationProgress);
+ }
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_transactional_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::AlterTableReplica(
+ TTableReplicaId replicaId,
+ const TAlterTableReplicaOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AlterTableReplica();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_replica_id(), replicaId);
+
+ if (options.Enabled) {
+ req->set_enabled(*options.Enabled);
+ }
+ if (options.Mode) {
+ req->set_mode(static_cast<NProto::ETableReplicaMode>(*options.Mode));
+ }
+ if (options.PreserveTimestamps) {
+ req->set_preserve_timestamps(*options.PreserveTimestamps);
+ }
+ if (options.Atomicity) {
+ req->set_atomicity(static_cast<NProto::EAtomicity>(*options.Atomicity));
+ }
+ if (options.EnableReplicatedTableTracker) {
+ req->set_enable_replicated_table_tracker(*options.EnableReplicatedTableTracker);
+ }
+
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<TYsonString> TClient::GetTablePivotKeys(
+ const TYPath& path,
+ const TGetTablePivotKeysOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetTablePivotKeys();
+ req->set_represent_key_as_list(options.RepresentKeyAsList);
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetTablePivotKeysPtr& rsp) {
+ return TYsonString(rsp->value());
+ }));
+}
+
+TFuture<void> TClient::CreateTableBackup(
+ const TBackupManifestPtr& /*manifest*/,
+ const TCreateTableBackupOptions& /*options*/)
+{
+ ThrowUnimplemented("CreateTableBackup");
+}
+
+TFuture<void> TClient::RestoreTableBackup(
+ const TBackupManifestPtr& /*manifest*/,
+ const TRestoreTableBackupOptions& /*options*/)
+{
+ ThrowUnimplemented("RestoreTableBackup");
+}
+
+TFuture<std::vector<TTableReplicaId>> TClient::GetInSyncReplicas(
+ const TYPath& path,
+ const TNameTablePtr& nameTable,
+ const TSharedRange<TLegacyKey>& keys,
+ const TGetInSyncReplicasOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetInSyncReplicas();
+ SetTimeoutOptions(*req, options);
+
+ if (options.Timestamp) {
+ req->set_timestamp(options.Timestamp);
+ }
+
+ if (options.CachedSyncReplicasTimeout) {
+ req->set_cached_sync_replicas_timeout(NYT::ToProto<i64>(*options.CachedSyncReplicasTimeout));
+ }
+
+ req->set_path(path);
+ req->Attachments() = SerializeRowset(nameTable, keys, req->mutable_rowset_descriptor());
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspGetInSyncReplicasPtr>& rspOrError) {
+ const auto& rsp = rspOrError.ValueOrThrow();
+ return FromProto<std::vector<TTableReplicaId>>(rsp->replica_ids());
+ }));
+}
+
+TFuture<std::vector<TTableReplicaId>> TClient::GetInSyncReplicas(
+ const TYPath& path,
+ const TGetInSyncReplicasOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetInSyncReplicas();
+ SetTimeoutOptions(*req, options);
+
+ if (options.Timestamp) {
+ req->set_timestamp(options.Timestamp);
+ }
+
+ if (options.CachedSyncReplicasTimeout) {
+ req->set_cached_sync_replicas_timeout(NYT::ToProto<i64>(*options.CachedSyncReplicasTimeout));
+ }
+
+ req->set_path(path);
+ req->RequireServerFeature(ERpcProxyFeature::GetInSyncWithoutKeys);
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspGetInSyncReplicasPtr>& rspOrError) {
+ const auto& rsp = rspOrError.ValueOrThrow();
+ return FromProto<std::vector<TTableReplicaId>>(rsp->replica_ids());
+ }));
+}
+
+TFuture<std::vector<TTabletInfo>> TClient::GetTabletInfos(
+ const TYPath& path,
+ const std::vector<int>& tabletIndexes,
+ const TGetTabletInfosOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetTabletInfos();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ ToProto(req->mutable_tablet_indexes(), tabletIndexes);
+ req->set_request_errors(options.RequestErrors);
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspGetTabletInfosPtr>& rspOrError) {
+ const auto& rsp = rspOrError.ValueOrThrow();
+ std::vector<TTabletInfo> tabletInfos;
+ tabletInfos.reserve(rsp->tablets_size());
+ for (const auto& protoTabletInfo : rsp->tablets()) {
+ auto& tabletInfo = tabletInfos.emplace_back();
+ tabletInfo.TotalRowCount = protoTabletInfo.total_row_count();
+ tabletInfo.TrimmedRowCount = protoTabletInfo.trimmed_row_count();
+ tabletInfo.DelayedLocklessRowCount = protoTabletInfo.delayed_lockless_row_count();
+ tabletInfo.BarrierTimestamp = protoTabletInfo.barrier_timestamp();
+ tabletInfo.LastWriteTimestamp = protoTabletInfo.last_write_timestamp();
+ tabletInfo.TableReplicaInfos = protoTabletInfo.replicas().empty()
+ ? std::nullopt
+ : std::make_optional(std::vector<TTabletInfo::TTableReplicaInfo>());
+ FromProto(&tabletInfo.TabletErrors, protoTabletInfo.tablet_errors());
+
+ for (const auto& protoReplicaInfo : protoTabletInfo.replicas()) {
+ auto& currentReplica = tabletInfo.TableReplicaInfos->emplace_back();
+ currentReplica.ReplicaId = FromProto<TGuid>(protoReplicaInfo.replica_id());
+ currentReplica.LastReplicationTimestamp = protoReplicaInfo.last_replication_timestamp();
+ currentReplica.Mode = CheckedEnumCast<ETableReplicaMode>(protoReplicaInfo.mode());
+ currentReplica.CurrentReplicationRowIndex = protoReplicaInfo.current_replication_row_index();
+ currentReplica.CommittedReplicationRowIndex = protoReplicaInfo.committed_replication_row_index();
+ currentReplica.ReplicationError = FromProto<TError>(protoReplicaInfo.replication_error());
+ }
+ }
+ return tabletInfos;
+ }));
+}
+
+TFuture<TGetTabletErrorsResult> TClient::GetTabletErrors(
+ const TYPath& path,
+ const TGetTabletErrorsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetTabletErrors();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ if (options.Limit) {
+ req->set_limit(*options.Limit);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspGetTabletErrorsPtr>& rspOrError) {
+ const auto& rsp = rspOrError.ValueOrThrow();
+ TGetTabletErrorsResult tabletErrors;
+ if (rsp->has_incomplete() && rsp->incomplete()) {
+ tabletErrors.Incomplete = rsp->incomplete();
+ }
+
+ for (i64 index = 0; index != rsp->tablet_ids_size(); ++index) {
+ std::vector<TError> errors;
+ for (const auto& protoError : rsp->tablet_errors(index).errors()) {
+ errors.push_back(FromProto<TError>(protoError));
+ }
+ tabletErrors.TabletErrors[FromProto<TTabletId>(rsp->tablet_ids(index))] = std::move(errors);
+ }
+
+ for (i64 index = 0; index != rsp->replica_ids_size(); ++index) {
+ std::vector<TError> errors;
+ for (const auto& protoError : rsp->replication_errors(index).errors()) {
+ errors.push_back(FromProto<TError>(protoError));
+ }
+ tabletErrors.ReplicationErrors[FromProto<TTableReplicaId>(rsp->replica_ids(index))] = std::move(errors);
+ }
+ return tabletErrors;
+ }));
+}
+
+TFuture<std::vector<TTabletActionId>> TClient::BalanceTabletCells(
+ const TString& tabletCellBundle,
+ const std::vector<TYPath>& movableTables,
+ const TBalanceTabletCellsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.BalanceTabletCells();
+ SetTimeoutOptions(*req, options);
+
+ req->set_bundle(tabletCellBundle);
+ req->set_keep_actions(options.KeepActions);
+ ToProto(req->mutable_movable_tables(), movableTables);
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspBalanceTabletCellsPtr>& rspOrError) {
+ const auto& rsp = rspOrError.ValueOrThrow();
+ return FromProto<std::vector<TTabletActionId>>(rsp->tablet_actions());
+ }));
+}
+
+TFuture<NChaosClient::TReplicationCardPtr> TClient::GetReplicationCard(
+ NChaosClient::TReplicationCardId /*replicationCardId*/,
+ const TGetReplicationCardOptions& /*options*/)
+{
+ YT_UNIMPLEMENTED();
+}
+
+TFuture<void> TClient::UpdateChaosTableReplicaProgress(
+ NChaosClient::TReplicaId /*replicaId*/,
+ const TUpdateChaosTableReplicaProgressOptions& /*options*/)
+{
+ YT_UNIMPLEMENTED();
+}
+
+TFuture<void> TClient::AlterReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TAlterReplicationCardOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AlterReplicationCard();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_replication_card_id(), replicationCardId);
+
+ if (options.ReplicatedTableOptions) {
+ req->set_replicated_table_options(ConvertToYsonString(options.ReplicatedTableOptions).ToString());
+ }
+ if (options.EnableReplicatedTableTracker) {
+ req->set_enable_replicated_table_tracker(*options.EnableReplicatedTableTracker);
+ }
+ if (options.ReplicationCardCollocationId) {
+ ToProto(req->mutable_replication_card_collocation_id(), *options.ReplicationCardCollocationId);
+ }
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<IQueueRowsetPtr> TClient::PullQueue(
+ const TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullQueueOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.PullQueue();
+ req->SetResponseHeavy(true);
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_queue_path(), queuePath);
+ req->set_offset(offset);
+ req->set_partition_index(partitionIndex);
+ ToProto(req->mutable_row_batch_read_options(), rowBatchReadOptions);
+
+ req->set_use_native_tablet_node_api(options.UseNativeTabletNodeApi);
+ req->set_replica_consistency(static_cast<NProto::EReplicaConsistency>(options.ReplicaConsistency));
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspPullQueuePtr& rsp) -> IQueueRowsetPtr {
+ auto rowset = DeserializeRowset<TUnversionedRow>(
+ rsp->rowset_descriptor(),
+ MergeRefsToRef<TRpcProxyClientBufferTag>(rsp->Attachments()));
+ return CreateQueueRowset(rowset, rsp->start_offset());
+ }));
+}
+
+TFuture<IQueueRowsetPtr> TClient::PullConsumer(
+ const TRichYPath& consumerPath,
+ const TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullConsumerOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.PullConsumer();
+ req->SetResponseHeavy(true);
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_consumer_path(), consumerPath);
+ ToProto(req->mutable_queue_path(), queuePath);
+ req->set_offset(offset);
+ req->set_partition_index(partitionIndex);
+ ToProto(req->mutable_row_batch_read_options(), rowBatchReadOptions);
+
+ req->set_replica_consistency(static_cast<NProto::EReplicaConsistency>(options.ReplicaConsistency));
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspPullConsumerPtr& rsp) -> IQueueRowsetPtr {
+ auto rowset = DeserializeRowset<TUnversionedRow>(
+ rsp->rowset_descriptor(),
+ MergeRefsToRef<TRpcProxyClientBufferTag>(rsp->Attachments()));
+ return CreateQueueRowset(rowset, rsp->start_offset());
+ }));
+}
+
+TFuture<void> TClient::RegisterQueueConsumer(
+ const TRichYPath& queuePath,
+ const TRichYPath& consumerPath,
+ bool vital,
+ const TRegisterQueueConsumerOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.RegisterQueueConsumer();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_queue_path(), queuePath);
+ ToProto(req->mutable_consumer_path(), consumerPath);
+ req->set_vital(vital);
+ if (options.Partitions) {
+ ToProto(req->mutable_partitions()->mutable_items(), *options.Partitions);
+ }
+
+ return req->Invoke().AsVoid();
+}
+
+TFuture<void> TClient::UnregisterQueueConsumer(
+ const TRichYPath& queuePath,
+ const TRichYPath& consumerPath,
+ const TUnregisterQueueConsumerOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.UnregisterQueueConsumer();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_queue_path(), queuePath);
+ ToProto(req->mutable_consumer_path(), consumerPath);
+
+ return req->Invoke().AsVoid();
+}
+
+TFuture<std::vector<TListQueueConsumerRegistrationsResult>> TClient::ListQueueConsumerRegistrations(
+ const std::optional<NYPath::TRichYPath>& queuePath,
+ const std::optional<NYPath::TRichYPath>& consumerPath,
+ const TListQueueConsumerRegistrationsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ListQueueConsumerRegistrations();
+ SetTimeoutOptions(*req, options);
+
+ if (queuePath) {
+ ToProto(req->mutable_queue_path(), *queuePath);
+ }
+ if (consumerPath) {
+ ToProto(req->mutable_consumer_path(), *consumerPath);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspListQueueConsumerRegistrationsPtr& rsp) {
+ std::vector<TListQueueConsumerRegistrationsResult> result;
+ for (const auto& registration : rsp->registrations()) {
+ std::optional<std::vector<int>> partitions;
+ if (registration.has_partitions()) {
+ partitions = FromProto<std::vector<int>>(registration.partitions().items());
+ }
+ result.push_back({
+ .QueuePath = FromProto<TRichYPath>(registration.queue_path()),
+ .ConsumerPath = FromProto<TRichYPath>(registration.consumer_path()),
+ .Vital = registration.vital(),
+ .Partitions = std::move(partitions),
+ });
+ }
+ return result;
+ }));
+}
+
+TFuture<void> TClient::AddMember(
+ const TString& group,
+ const TString& member,
+ const TAddMemberOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AddMember();
+ SetTimeoutOptions(*req, options);
+
+ req->set_group(group);
+ req->set_member(member);
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::RemoveMember(
+ const TString& group,
+ const TString& member,
+ const TRemoveMemberOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.RemoveMember();
+ SetTimeoutOptions(*req, options);
+
+ req->set_group(group);
+ req->set_member(member);
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<TCheckPermissionResponse> TClient::CheckPermission(
+ const TString& user,
+ const TYPath& path,
+ EPermission permission,
+ const TCheckPermissionOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.CheckPermission();
+ SetTimeoutOptions(*req, options);
+
+ req->set_user(user);
+ req->set_path(path);
+ req->set_permission(static_cast<int>(permission));
+ if (options.Columns) {
+ auto* protoColumns = req->mutable_columns();
+ ToProto(protoColumns->mutable_items(), *options.Columns);
+ }
+ if (options.Vital) {
+ req->set_vital(*options.Vital);
+ }
+
+ ToProto(req->mutable_master_read_options(), options);
+ ToProto(req->mutable_transactional_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspCheckPermissionPtr& rsp) {
+ TCheckPermissionResponse response;
+ static_cast<TCheckPermissionResult&>(response) = FromProto<TCheckPermissionResult>(rsp->result());
+ if (rsp->has_columns()) {
+ response.Columns = FromProto<std::vector<TCheckPermissionResult>>(rsp->columns().items());
+ }
+ return response;
+ }));
+}
+
+TFuture<TCheckPermissionByAclResult> TClient::CheckPermissionByAcl(
+ const std::optional<TString>& user,
+ EPermission permission,
+ INodePtr acl,
+ const TCheckPermissionByAclOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.CheckPermissionByAcl();
+ SetTimeoutOptions(*req, options);
+
+ if (user) {
+ req->set_user(*user);
+ }
+ req->set_permission(static_cast<int>(permission));
+ req->set_acl(ConvertToYsonString(acl).ToString());
+ req->set_ignore_missing_subjects(options.IgnoreMissingSubjects);
+
+ ToProto(req->mutable_master_read_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspCheckPermissionByAclPtr& rsp) {
+ return FromProto<TCheckPermissionByAclResult>(rsp->result());
+ }));
+}
+
+TFuture<void> TClient::TransferAccountResources(
+ const TString& srcAccount,
+ const TString& dstAccount,
+ NYTree::INodePtr resourceDelta,
+ const TTransferAccountResourcesOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.TransferAccountResources();
+ SetTimeoutOptions(*req, options);
+
+ req->set_src_account(srcAccount);
+ req->set_dst_account(dstAccount);
+ req->set_resource_delta(ConvertToYsonString(resourceDelta).ToString());
+
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::TransferPoolResources(
+ const TString& srcPool,
+ const TString& dstPool,
+ const TString& poolTree,
+ NYTree::INodePtr resourceDelta,
+ const TTransferPoolResourcesOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.TransferPoolResources();
+ SetTimeoutOptions(*req, options);
+
+ req->set_src_pool(srcPool);
+ req->set_dst_pool(dstPool);
+ req->set_pool_tree(poolTree);
+ req->set_resource_delta(ConvertToYsonString(resourceDelta).ToString());
+
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<NScheduler::TOperationId> TClient::StartOperation(
+ NScheduler::EOperationType type,
+ const TYsonString& spec,
+ const TStartOperationOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.StartOperation();
+ SetTimeoutOptions(*req, options);
+
+ req->set_type(NProto::ConvertOperationTypeToProto(type));
+ req->set_spec(spec.ToString());
+
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_transactional_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspStartOperationPtr& rsp) {
+ return FromProto<TOperationId>(rsp->operation_id());
+ }));
+}
+
+TFuture<void> TClient::AbortOperation(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ const TAbortOperationOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AbortOperation();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+
+ if (options.AbortMessage) {
+ req->set_abort_message(*options.AbortMessage);
+ }
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::SuspendOperation(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ const TSuspendOperationOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.SuspendOperation();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+ req->set_abort_running_jobs(options.AbortRunningJobs);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::ResumeOperation(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ const TResumeOperationOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ResumeOperation();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::CompleteOperation(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ const TCompleteOperationOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.CompleteOperation();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::UpdateOperationParameters(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ const TYsonString& parameters,
+ const TUpdateOperationParametersOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.UpdateOperationParameters();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+
+ req->set_parameters(parameters.ToString());
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<TOperation> TClient::GetOperation(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ const TGetOperationOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetOperation();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+
+ ToProto(req->mutable_master_read_options(), options);
+ // COMPAT(max42): after 22.3 is everywhere, drop legacy field.
+ if (options.Attributes) {
+ ToProto(req->mutable_legacy_attributes(), *options.Attributes);
+ ToProto(req->mutable_attributes()->mutable_keys(), *options.Attributes);
+ }
+
+ req->set_include_runtime(options.IncludeRuntime);
+ req->set_maximum_cypress_progress_age(ToProto<i64>(options.MaximumCypressProgressAge));
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetOperationPtr& rsp) {
+ auto attributes = ConvertToAttributes(TYsonStringBuf(rsp->meta()));
+ TOperation operation;
+ Deserialize(operation, std::move(attributes), /*clone*/ false);
+ return operation;
+ }));
+}
+
+TFuture<void> TClient::DumpJobContext(
+ NJobTrackerClient::TJobId jobId,
+ const TYPath& path,
+ const TDumpJobContextOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.DumpJobContext();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_job_id(), jobId);
+ req->set_path(path);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr> TClient::GetJobInput(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+ auto req = proxy.GetJobInput();
+ if (options.Timeout) {
+ SetTimeoutOptions(*req, options);
+ } else {
+ InitStreamingRequest(*req);
+ }
+
+ ToProto(req->mutable_job_id(), jobId);
+ req->set_job_spec_source(static_cast<NProto::EJobSpecSource>(options.JobSpecSource));
+
+ return CreateRpcClientInputStream(std::move(req));
+}
+
+TFuture<TYsonString> TClient::GetJobInputPaths(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputPathsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetJobInputPaths();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_job_id(), jobId);
+ req->set_job_spec_source(static_cast<NProto::EJobSpecSource>(options.JobSpecSource));
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetJobInputPathsPtr& rsp) {
+ return TYsonString(rsp->paths());
+ }));
+}
+
+TFuture<TYsonString> TClient::GetJobSpec(
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobSpecOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetJobSpec();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_job_id(), jobId);
+ req->set_omit_node_directory(options.OmitNodeDirectory);
+ req->set_omit_input_table_specs(options.OmitInputTableSpecs);
+ req->set_omit_output_table_specs(options.OmitOutputTableSpecs);
+ req->set_job_spec_source(static_cast<NProto::EJobSpecSource>(options.JobSpecSource));
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetJobSpecPtr& rsp) {
+ return TYsonString(rsp->job_spec());
+ }));
+}
+
+TFuture<TSharedRef> TClient::GetJobStderr(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobStderrOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetJobStderr();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+ ToProto(req->mutable_job_id(), jobId);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetJobStderrPtr& rsp) {
+ YT_VERIFY(rsp->Attachments().size() == 1);
+ return rsp->Attachments().front();
+ }));
+}
+
+TFuture<TSharedRef> TClient::GetJobFailContext(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobFailContextOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetJobFailContext();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+ ToProto(req->mutable_job_id(), jobId);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetJobFailContextPtr& rsp) {
+ YT_VERIFY(rsp->Attachments().size() == 1);
+ return rsp->Attachments().front();
+ }));
+}
+
+TFuture<TListOperationsResult> TClient::ListOperations(
+ const TListOperationsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ListOperations();
+ SetTimeoutOptions(*req, options);
+
+ if (options.FromTime) {
+ req->set_from_time(NYT::ToProto<i64>(*options.FromTime));
+ }
+ if (options.ToTime) {
+ req->set_to_time(NYT::ToProto<i64>(*options.ToTime));
+ }
+ if (options.CursorTime) {
+ req->set_cursor_time(NYT::ToProto<i64>(*options.CursorTime));
+ }
+ req->set_cursor_direction(static_cast<NProto::EOperationSortDirection>(options.CursorDirection));
+ if (options.UserFilter) {
+ req->set_user_filter(*options.UserFilter);
+ }
+
+ if (options.AccessFilter) {
+ req->set_access_filter(ConvertToYsonString(options.AccessFilter).ToString());
+ }
+
+ if (options.StateFilter) {
+ req->set_state_filter(NProto::ConvertOperationStateToProto(*options.StateFilter));
+ }
+ if (options.TypeFilter) {
+ req->set_type_filter(NProto::ConvertOperationTypeToProto(*options.TypeFilter));
+ }
+ if (options.SubstrFilter) {
+ req->set_substr_filter(*options.SubstrFilter);
+ }
+ if (options.Pool) {
+ req->set_pool(*options.Pool);
+ }
+ if (options.PoolTree) {
+ req->set_pool_tree(*options.PoolTree);
+ }
+ if (options.WithFailedJobs) {
+ req->set_with_failed_jobs(*options.WithFailedJobs);
+ }
+ if (options.ArchiveFetchingTimeout) {
+ req->set_archive_fetching_timeout(NYT::ToProto<i64>(options.ArchiveFetchingTimeout));
+ }
+ req->set_include_archive(options.IncludeArchive);
+ req->set_include_counters(options.IncludeCounters);
+ req->set_limit(options.Limit);
+
+ // COMPAT(max42): after 22.3 is everywhere, drop legacy field.
+ if (options.Attributes) {
+ ToProto(req->mutable_legacy_attributes()->mutable_keys(), *options.Attributes);
+ ToProto(req->mutable_attributes()->mutable_keys(), *options.Attributes);
+ } else {
+ req->mutable_legacy_attributes()->set_all(true);
+ }
+
+ req->set_enable_ui_mode(options.EnableUIMode);
+
+ ToProto(req->mutable_master_read_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspListOperationsPtr& rsp) {
+ return FromProto<TListOperationsResult>(rsp->result());
+ }));
+}
+
+TFuture<TListJobsResult> TClient::ListJobs(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ const TListJobsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ListJobs();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+
+ if (options.Type) {
+ req->set_type(NProto::ConvertJobTypeToProto(*options.Type));
+ }
+ if (options.State) {
+ req->set_state(NProto::ConvertJobStateToProto(*options.State));
+ }
+ if (options.Address) {
+ req->set_address(*options.Address);
+ }
+ if (options.WithStderr) {
+ req->set_with_stderr(*options.WithStderr);
+ }
+ if (options.WithFailContext) {
+ req->set_with_fail_context(*options.WithFailContext);
+ }
+ if (options.WithSpec) {
+ req->set_with_spec(*options.WithSpec);
+ }
+ if (options.JobCompetitionId) {
+ ToProto(req->mutable_job_competition_id(), options.JobCompetitionId);
+ }
+ if (options.WithCompetitors) {
+ req->set_with_competitors(*options.WithCompetitors);
+ }
+ if (options.TaskName) {
+ req->set_task_name(*options.TaskName);
+ }
+
+ req->set_sort_field(static_cast<NProto::EJobSortField>(options.SortField));
+ req->set_sort_order(static_cast<NProto::EJobSortDirection>(options.SortOrder));
+
+ req->set_limit(options.Limit);
+ req->set_offset(options.Offset);
+
+ req->set_include_cypress(options.IncludeCypress);
+ req->set_include_controller_agent(options.IncludeControllerAgent);
+ req->set_include_archive(options.IncludeArchive);
+
+ req->set_data_source(static_cast<NProto::EDataSource>(options.DataSource));
+ req->set_running_jobs_lookbehind_period(NYT::ToProto<i64>(options.RunningJobsLookbehindPeriod));
+
+ ToProto(req->mutable_master_read_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspListJobsPtr& rsp) {
+ return FromProto<TListJobsResult>(rsp->result());
+ }));
+}
+
+TFuture<TYsonString> TClient::GetJob(
+ const TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetJob();
+ SetTimeoutOptions(*req, options);
+
+ NScheduler::ToProto(req, operationIdOrAlias);
+ ToProto(req->mutable_job_id(), jobId);
+
+ // COMPAT(max42): after 22.3 is everywhere, drop legacy field.
+ if (options.Attributes) {
+ ToProto(req->mutable_legacy_attributes()->mutable_keys(), *options.Attributes);
+ ToProto(req->mutable_attributes()->mutable_keys(), *options.Attributes);
+ } else {
+ req->mutable_legacy_attributes()->set_all(true);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetJobPtr& rsp) {
+ return TYsonString(rsp->info());
+ }));
+}
+
+TFuture<void> TClient::AbandonJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbandonJobOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AbandonJob();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_job_id(), jobId);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<TPollJobShellResponse> TClient::PollJobShell(
+ NJobTrackerClient::TJobId jobId,
+ const std::optional<TString>& shellName,
+ const TYsonString& parameters,
+ const TPollJobShellOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.PollJobShell();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_job_id(), jobId);
+ req->set_parameters(parameters.ToString());
+ if (shellName) {
+ req->set_shell_name(*shellName);
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspPollJobShellPtr& rsp) {
+ return TPollJobShellResponse {
+ .Result = TYsonString(rsp->result()),
+ };
+ }));
+}
+
+TFuture<void> TClient::AbortJob(
+ NJobTrackerClient::TJobId jobId,
+ const TAbortJobOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AbortJob();
+ SetTimeoutOptions(*req, options);
+
+ ToProto(req->mutable_job_id(), jobId);
+ if (options.InterruptTimeout) {
+ req->set_interrupt_timeout(NYT::ToProto<i64>(*options.InterruptTimeout));
+ }
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<TGetFileFromCacheResult> TClient::GetFileFromCache(
+ const TString& md5,
+ const TGetFileFromCacheOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetFileFromCache();
+ SetTimeoutOptions(*req, options);
+ ToProto(req->mutable_transactional_options(), options);
+
+ req->set_md5(md5);
+ req->set_cache_path(options.CachePath);
+
+ ToProto(req->mutable_master_read_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetFileFromCachePtr& rsp) {
+ return FromProto<TGetFileFromCacheResult>(rsp->result());
+ }));
+}
+
+TFuture<TPutFileToCacheResult> TClient::PutFileToCache(
+ const TYPath& path,
+ const TString& expectedMD5,
+ const TPutFileToCacheOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.PutFileToCache();
+ SetTimeoutOptions(*req, options);
+ ToProto(req->mutable_transactional_options(), options);
+
+ req->set_path(path);
+ req->set_md5(expectedMD5);
+ req->set_cache_path(options.CachePath);
+ req->set_preserve_expiration_timeout(options.PreserveExpirationTimeout);
+
+ ToProto(req->mutable_prerequisite_options(), options);
+ ToProto(req->mutable_master_read_options(), options);
+ ToProto(req->mutable_mutating_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspPutFileToCachePtr& rsp) {
+ return FromProto<TPutFileToCacheResult>(rsp->result());
+ }));
+}
+
+TFuture<TClusterMeta> TClient::GetClusterMeta(
+ const TGetClusterMetaOptions& /*options*/)
+{
+ ThrowUnimplemented("GetClusterMeta");
+}
+
+TFuture<void> TClient::CheckClusterLiveness(
+ const TCheckClusterLivenessOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.CheckClusterLiveness();
+ SetTimeoutOptions(*req, options);
+
+ req->set_check_cypress_root(options.CheckCypressRoot);
+ req->set_check_secondary_master_cells(options.CheckSecondaryMasterCells);
+ if (auto bundleName = options.CheckTabletCellBundle) {
+ req->set_check_tablet_cell_bundle(*bundleName);
+ }
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<TSkynetSharePartsLocationsPtr> TClient::LocateSkynetShare(
+ const TRichYPath&,
+ const TLocateSkynetShareOptions& /*options*/)
+{
+ ThrowUnimplemented("LocateSkynetShare");
+}
+
+TFuture<std::vector<TColumnarStatistics>> TClient::GetColumnarStatistics(
+ const std::vector<TRichYPath>& path,
+ const TGetColumnarStatisticsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GetColumnarStatistics();
+ SetTimeoutOptions(*req, options);
+
+ for (const auto& subPath: path) {
+ req->add_paths(ConvertToYsonString(subPath).ToString());
+ }
+
+ req->set_fetcher_mode(static_cast<NProto::EColumnarStatisticsFetcherMode>(options.FetcherMode));
+
+ ToProto(req->mutable_fetch_chunk_spec_config(), options.FetchChunkSpecConfig);
+
+ ToProto(req->mutable_fetcher_config(), options.FetcherConfig);
+
+ req->set_enable_early_finish(options.EnableEarlyFinish);
+
+ ToProto(req->mutable_transactional_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGetColumnarStatisticsPtr& rsp) {
+ return NYT::FromProto<std::vector<TColumnarStatistics>>(rsp->statistics());
+ }));
+}
+
+TFuture<NApi::TMultiTablePartitions> TClient::PartitionTables(
+ const std::vector<TRichYPath>& paths,
+ const TPartitionTablesOptions& options)
+{
+ if (options.PartitionMode == NTableClient::ETablePartitionMode::Sorted) {
+ THROW_ERROR_EXCEPTION("Sorted partitioning is not supported yet");
+ }
+
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.PartitionTables();
+ SetTimeoutOptions(*req, options);
+
+ for (const auto& path : paths) {
+ req->add_paths(ToString(path));
+ }
+
+ ToProto(req->mutable_fetch_chunk_spec_config(), options.FetchChunkSpecConfig);
+
+ ToProto(req->mutable_fetcher_config(), options.FetcherConfig);
+
+ req->mutable_chunk_slice_fetcher_config()->set_max_slices_per_fetch(
+ ToProto<i64>(options.ChunkSliceFetcherConfig->MaxSlicesPerFetch));
+
+ req->set_partition_mode(static_cast<NProto::EPartitionTablesMode>(options.PartitionMode));
+
+ req->set_data_weight_per_partition(options.DataWeightPerPartition);
+
+ if (options.MaxPartitionCount) {
+ req->set_max_partition_count(*options.MaxPartitionCount);
+ }
+
+ req->set_adjust_data_weight_per_partition(options.AdjustDataWeightPerPartition);
+
+ req->set_enable_key_guarantee(options.EnableKeyGuarantee);
+
+ ToProto(req->mutable_transactional_options(), options);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspPartitionTablesPtr& rsp) {
+ return FromProto<TMultiTablePartitions>(*rsp);
+ }));
+}
+
+TFuture<void> TClient::TruncateJournal(
+ const TYPath& path,
+ i64 rowCount,
+ const TTruncateJournalOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.TruncateJournal();
+ SetTimeoutOptions(*req, options);
+
+ req->set_path(path);
+ req->set_row_count(rowCount);
+ ToProto(req->mutable_mutating_options(), options);
+ ToProto(req->mutable_prerequisite_options(), options);
+
+ return req->Invoke().As<void>();
+}
+
+
+TFuture<int> TClient::BuildSnapshot(const TBuildSnapshotOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.BuildSnapshot();
+ if (options.CellId) {
+ ToProto(req->mutable_cell_id(), options.CellId);
+ }
+ req->set_set_read_only(options.SetReadOnly);
+ req->set_wait_for_snapshot_completion(options.WaitForSnapshotCompletion);
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspBuildSnapshotPtr>& rspOrError) -> int {
+ const auto& rsp = rspOrError.ValueOrThrow();
+ return rsp->snapshot_id();
+ }));
+}
+
+TFuture<TCellIdToSnapshotIdMap> TClient::BuildMasterSnapshots(const TBuildMasterSnapshotsOptions& /*options*/)
+{
+ ThrowUnimplemented("BuildMasterSnapshots");
+}
+
+TFuture<void> TClient::SwitchLeader(
+ NHydra::TCellId /*cellId*/,
+ const TString& /*newLeaderAddress*/,
+ const TSwitchLeaderOptions& /*options*/)
+{
+ ThrowUnimplemented("SwitchLeader");
+}
+
+TFuture<void> TClient::ResetStateHash(
+ NHydra::TCellId /*cellId*/,
+ const TResetStateHashOptions& /*options*/)
+{
+ ThrowUnimplemented("ResetStateHash");
+}
+
+TFuture<void> TClient::GCCollect(const TGCCollectOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.GCCollect();
+ if (options.CellId) {
+ ToProto(req->mutable_cell_id(), options.CellId);
+ }
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::KillProcess(
+ const TString& /*address*/,
+ const TKillProcessOptions& /*options*/)
+{
+ ThrowUnimplemented("KillProcess");
+}
+
+TFuture<TString> TClient::WriteCoreDump(
+ const TString& /*address*/,
+ const TWriteCoreDumpOptions& /*options*/)
+{
+ ThrowUnimplemented("WriteCoreDump");
+}
+
+TFuture<TGuid> TClient::WriteLogBarrier(
+ const TString& /*address*/,
+ const TWriteLogBarrierOptions& /*options*/)
+{
+ ThrowUnimplemented("WriteLogBarrier");
+}
+
+TFuture<TString> TClient::WriteOperationControllerCoreDump(
+ TOperationId /*operationId*/,
+ const TWriteOperationControllerCoreDumpOptions& /*options*/)
+{
+ ThrowUnimplemented("WriteOperationControllerCoreDump");
+}
+
+TFuture<void> TClient::HealExecNode(
+ const TString& /*address*/,
+ const THealExecNodeOptions& /*options*/)
+{
+ ThrowUnimplemented("HealExecNode");
+}
+
+TFuture<void> TClient::SuspendCoordinator(
+ TCellId coordinatorCellId,
+ const TSuspendCoordinatorOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.SuspendCoordinator();
+ ToProto(req->mutable_coordinator_cell_id(), coordinatorCellId);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::ResumeCoordinator(
+ TCellId coordinatorCellId,
+ const TResumeCoordinatorOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ResumeCoordinator();
+ ToProto(req->mutable_coordinator_cell_id(), coordinatorCellId);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::MigrateReplicationCards(
+ TCellId chaosCellId,
+ const TMigrateReplicationCardsOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.MigrateReplicationCards();
+ ToProto(req->mutable_chaos_cell_id(), chaosCellId);
+ ToProto(req->mutable_replication_card_ids(), options.ReplicationCardIds);
+ if (options.DestinationCellId) {
+ ToProto(req->mutable_destination_cell_id(), options.DestinationCellId);
+ }
+
+ return req->Invoke().As<void>();
+}
+
+namespace {
+
+NProto::EMaintenanceComponent ConvertMaintenanceComponentToProto(EMaintenanceComponent component)
+{
+ switch (component) {
+ case EMaintenanceComponent::ClusterNode:
+ return NProto::EMaintenanceComponent::MC_CLUSTER_NODE;
+ case EMaintenanceComponent::HttpProxy:
+ return NProto::EMaintenanceComponent::MC_HTTP_PROXY;
+ case EMaintenanceComponent::RpcProxy:
+ return NProto::EMaintenanceComponent::MC_RPC_PROXY;
+ case EMaintenanceComponent::Host:
+ return NProto::EMaintenanceComponent::MC_HOST;
+ default:
+ THROW_ERROR_EXCEPTION("Invalid maintenance component %Qv", component);
+ }
+}
+
+NProto::EMaintenanceType ConvertMaintenanceTypeToProto(EMaintenanceType type)
+{
+ switch (type) {
+ case EMaintenanceType::Ban:
+ return NProto::EMaintenanceType::MT_BAN;
+ case EMaintenanceType::Decommission:
+ return NProto::EMaintenanceType::MT_DECOMMISSION;
+ case EMaintenanceType::DisableSchedulerJobs:
+ return NProto::EMaintenanceType::MT_DISABLE_SCHEDULER_JOBS;
+ case EMaintenanceType::DisableWriteSessions:
+ return NProto::EMaintenanceType::MT_DISABLE_WRITE_SESSIONS;
+ case EMaintenanceType::DisableTabletCells:
+ return NProto::EMaintenanceType::MT_DISABLE_TABLET_CELLS;
+ case EMaintenanceType::PendingRestart:
+ return NProto::EMaintenanceType::MT_PENDING_RESTART;
+ default:
+ THROW_ERROR_EXCEPTION("Invalid maintenance type %Qv", type);
+ }
+}
+
+} // namespace
+
+TFuture<TMaintenanceId> TClient::AddMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ EMaintenanceType type,
+ const TString& comment,
+ const TAddMaintenanceOptions& options)
+{
+ ValidateMaintenanceComment(comment);
+
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.AddMaintenance();
+ req->set_component(ConvertMaintenanceComponentToProto(component));
+ req->set_address(address);
+ req->set_type(ConvertMaintenanceTypeToProto(type));
+ req->set_comment(comment);
+ SetTimeoutOptions(*req, options);
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspAddMaintenancePtr>& rsp) {
+ return FromProto<TMaintenanceId>(rsp.ValueOrThrow()->id());
+ }));
+}
+
+TFuture<TMaintenanceCounts> TClient::RemoveMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ const TMaintenanceFilter& filter,
+ const TRemoveMaintenanceOptions& options)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.RemoveMaintenance();
+ req->set_component(ConvertMaintenanceComponentToProto(component));
+ req->set_address(address);
+
+ ToProto(req->mutable_ids(), filter.Ids);
+
+ if (filter.Type) {
+ req->set_type(ConvertMaintenanceTypeToProto(*filter.Type));
+ }
+
+ using TByUser = TMaintenanceFilter::TByUser;
+ Visit(filter.User,
+ [] (TByUser::TAll) {},
+ [&] (TByUser::TMine) {
+ req->set_mine(true);
+ },
+ [&] (const TString& user) {
+ req->set_user(user);
+ });
+
+ SetTimeoutOptions(*req, options);
+
+ return req->Invoke().Apply(BIND([] (const TErrorOr<TApiServiceProxy::TRspRemoveMaintenancePtr>& rsp) {
+ auto rspValue = rsp.ValueOrThrow();
+
+ const auto& protoCounts = rspValue->removed_maintenance_counts();
+ TMaintenanceCounts counts;
+
+ if (!rspValue->use_map_instead_of_fields()) {
+ counts[EMaintenanceType::Ban] = rspValue->ban();
+ counts[EMaintenanceType::Decommission] = rspValue->decommission();
+ counts[EMaintenanceType::DisableSchedulerJobs] = rspValue->disable_scheduler_jobs();
+ counts[EMaintenanceType::DisableWriteSessions] = rspValue->disable_write_sessions();
+ counts[EMaintenanceType::DisableTabletCells] = rspValue->disable_tablet_cells();
+ counts[EMaintenanceType::PendingRestart] = rspValue->pending_restart();
+ } else {
+ for (auto type : TEnumTraits<EMaintenanceType>::GetDomainValues()) {
+ auto it = protoCounts.find(ConvertMaintenanceTypeToProto(type));
+ counts[type] = it == protoCounts.end() ? 0 : it->second;
+ }
+ }
+
+ return counts;
+ }));
+}
+
+TFuture<void> TClient::SuspendChaosCells(
+ const std::vector<TCellId>& cellIds,
+ const TSuspendChaosCellsOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.SuspendChaosCells();
+ ToProto(req->mutable_cell_ids(), cellIds);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::ResumeChaosCells(
+ const std::vector<TCellId>& cellIds,
+ const TResumeChaosCellsOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ResumeChaosCells();
+ ToProto(req->mutable_cell_ids(), cellIds);
+
+ return req->Invoke().As<void>();
+}
+
+TFuture<void> TClient::SuspendTabletCells(
+ const std::vector<TCellId>& /*cellIds*/,
+ const TSuspendTabletCellsOptions& /*options*/)
+{
+ ThrowUnimplemented("SuspendTabletCells");
+}
+
+TFuture<void> TClient::ResumeTabletCells(
+ const std::vector<TCellId>& /*cellIds*/,
+ const TResumeTabletCellsOptions& /*options*/)
+{
+ ThrowUnimplemented("ResumeTabletCells");
+}
+
+TFuture<TDisableChunkLocationsResult> TClient::DisableChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDisableChunkLocationsOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.DisableChunkLocations();
+ ToProto(req->mutable_node_address(), nodeAddress);
+ ToProto(req->mutable_location_uuids(), locationUuids);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspDisableChunkLocationsPtr& rsp) {
+ auto locationUuids = FromProto<std::vector<TGuid>>(rsp->location_uuids());
+ return TDisableChunkLocationsResult{
+ .LocationUuids = locationUuids
+ };
+ }));
+}
+
+TFuture<TDestroyChunkLocationsResult> TClient::DestroyChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDestroyChunkLocationsOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.DestroyChunkLocations();
+ ToProto(req->mutable_node_address(), nodeAddress);
+ ToProto(req->mutable_location_uuids(), locationUuids);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspDestroyChunkLocationsPtr& rsp) {
+ auto locationUuids = FromProto<std::vector<TGuid>>(rsp->location_uuids());
+ return TDestroyChunkLocationsResult{
+ .LocationUuids = locationUuids
+ };
+ }));
+}
+
+TFuture<TResurrectChunkLocationsResult> TClient::ResurrectChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TResurrectChunkLocationsOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.ResurrectChunkLocations();
+ ToProto(req->mutable_node_address(), nodeAddress);
+ ToProto(req->mutable_location_uuids(), locationUuids);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspResurrectChunkLocationsPtr& rsp) {
+ auto locationUuids = FromProto<std::vector<TGuid>>(rsp->location_uuids());
+ return TResurrectChunkLocationsResult{
+ .LocationUuids = locationUuids
+ };
+ }));
+}
+
+TFuture<TRequestRebootResult> TClient::RequestReboot(
+ const TString& nodeAddress,
+ const TRequestRebootOptions& /*options*/)
+{
+ auto proxy = CreateApiServiceProxy();
+
+ auto req = proxy.RequestReboot();
+ ToProto(req->mutable_node_address(), nodeAddress);
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspRequestRebootPtr& /*rsp*/) {
+ return TRequestRebootResult();
+ }));
+}
+
+TFuture<NQueryTrackerClient::TQueryId> TClient::StartQuery(
+ NQueryTrackerClient::EQueryEngine /*engine*/,
+ const TString& /*query*/,
+ const TStartQueryOptions& /*options*/)
+{
+ ThrowUnimplemented("StartQuery");
+}
+
+TFuture<void> TClient::AbortQuery(
+ NQueryTrackerClient::TQueryId /*queryId*/,
+ const TAbortQueryOptions& /*options*/)
+{
+ ThrowUnimplemented("AbortQuery");
+}
+
+TFuture<TQueryResult> TClient::GetQueryResult(
+ NQueryTrackerClient::TQueryId /*queryId*/,
+ i64 /*resultIndex*/,
+ const TGetQueryResultOptions& /*options*/)
+{
+ ThrowUnimplemented("GetQueryResult");
+}
+
+TFuture<IUnversionedRowsetPtr> TClient::ReadQueryResult(
+ NQueryTrackerClient::TQueryId /*queryId*/,
+ i64 /*resultIndex*/,
+ const TReadQueryResultOptions& /*options*/)
+{
+ ThrowUnimplemented("AbortQuery");
+}
+
+TFuture<TQuery> TClient::GetQuery(
+ NQueryTrackerClient::TQueryId /*queryId*/,
+ const TGetQueryOptions& /*options*/)
+{
+ ThrowUnimplemented("GetQuery");
+}
+
+TFuture<TListQueriesResult> TClient::ListQueries(
+ const TListQueriesOptions& /*options*/)
+{
+ ThrowUnimplemented("ListQueries");
+}
+
+TFuture<void> TClient::AlterQuery(
+ NQueryTrackerClient::TQueryId /*queryId*/,
+ const TAlterQueryOptions& /*options*/)
+{
+ ThrowUnimplemented("AlterQuery");
+}
+
+TFuture<void> TClient::SetUserPassword(
+ const TString& /*user*/,
+ const TString& /*currentPasswordSha256*/,
+ const TString& /*newPasswordSha256*/,
+ const TSetUserPasswordOptions& /*options*/)
+{
+ ThrowUnimplemented("SetUserPassword");
+}
+
+TFuture<TIssueTokenResult> TClient::IssueToken(
+ const TString& /*user*/,
+ const TString& /*passwordSha256*/,
+ const TIssueTokenOptions& /*options*/)
+{
+ ThrowUnimplemented("IssueToken");
+}
+
+TFuture<void> TClient::RevokeToken(
+ const TString& /*user*/,
+ const TString& /*passwordSha256*/,
+ const TString& /*tokenSha256*/,
+ const TRevokeTokenOptions& /*options*/)
+{
+ ThrowUnimplemented("RevokeToken");
+}
+
+TFuture<TListUserTokensResult> TClient::ListUserTokens(
+ const TString& /*user*/,
+ const TString& /*passwordSha256*/,
+ const TListUserTokensOptions& /*options*/)
+{
+ ThrowUnimplemented("ListUserTokens");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/client_impl.h b/yt/yt/client/api/rpc_proxy/client_impl.h
new file mode 100644
index 0000000000..3f9724fcb0
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/client_impl.h
@@ -0,0 +1,503 @@
+#pragma once
+
+#include "client_base.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/core/misc/lazy_ptr.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TClient
+ : public virtual NApi::IClient
+ , public TClientBase
+ , public NApi::TClusterAwareClientBase
+{
+public:
+ TClient(
+ TConnectionPtr connection,
+ const TClientOptions& options);
+
+ void Terminate() override;
+ const NTabletClient::ITableMountCachePtr& GetTableMountCache() override;
+ const NChaosClient::IReplicationCardCachePtr& GetReplicationCardCache() override;
+ const NTransactionClient::ITimestampProviderPtr& GetTimestampProvider() override;
+
+ // Transactions.
+ NApi::ITransactionPtr AttachTransaction(
+ NTransactionClient::TTransactionId transactionId,
+ const NApi::TTransactionAttachOptions& options) override;
+
+ // Tables.
+ TFuture<void> MountTable(
+ const NYPath::TYPath& path,
+ const NApi::TMountTableOptions& options) override;
+
+ TFuture<void> UnmountTable(
+ const NYPath::TYPath& path,
+ const NApi::TUnmountTableOptions& options) override;
+
+ TFuture<void> RemountTable(
+ const NYPath::TYPath& path,
+ const NApi::TRemountTableOptions& options) override;
+
+ TFuture<void> FreezeTable(
+ const NYPath::TYPath& path,
+ const NApi::TFreezeTableOptions& options) override;
+
+ TFuture<void> UnfreezeTable(
+ const NYPath::TYPath& path,
+ const NApi::TUnfreezeTableOptions& options) override;
+
+ TFuture<void> ReshardTable(
+ const NYPath::TYPath& path,
+ const std::vector<NTableClient::TLegacyOwningKey>& pivotKeys,
+ const NApi::TReshardTableOptions& options) override;
+
+ TFuture<void> ReshardTable(
+ const NYPath::TYPath& path,
+ int tabletCount,
+ const NApi::TReshardTableOptions& options) override;
+
+ TFuture<std::vector<NTabletClient::TTabletActionId>> ReshardTableAutomatic(
+ const NYPath::TYPath& path,
+ const NApi::TReshardTableAutomaticOptions& options) override;
+
+ TFuture<void> TrimTable(
+ const NYPath::TYPath& path,
+ int tabletIndex,
+ i64 trimmedRowCount,
+ const NApi::TTrimTableOptions& options) override;
+
+ TFuture<void> AlterTable(
+ const NYPath::TYPath& path,
+ const NApi::TAlterTableOptions& options) override;
+
+ TFuture<void> AlterTableReplica(
+ NTabletClient::TTableReplicaId replicaId,
+ const NApi::TAlterTableReplicaOptions& options) override;
+
+ TFuture<NYson::TYsonString> GetTablePivotKeys(
+ const NYPath::TYPath& path,
+ const TGetTablePivotKeysOptions& options) override;
+
+ TFuture<void> CreateTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TCreateTableBackupOptions& options) override;
+
+ TFuture<void> RestoreTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TRestoreTableBackupOptions& options) override;
+
+ TFuture<std::vector<NTabletClient::TTableReplicaId>> GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const NTableClient::TNameTablePtr& nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const NApi::TGetInSyncReplicasOptions& options) override;
+
+ TFuture<std::vector<NTabletClient::TTableReplicaId>> GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const NApi::TGetInSyncReplicasOptions& options) override;
+
+ TFuture<std::vector<NApi::TTabletInfo>> GetTabletInfos(
+ const NYPath::TYPath& path,
+ const std::vector<int>& tabletIndexes,
+ const NApi::TGetTabletInfosOptions& options) override;
+
+ TFuture<TGetTabletErrorsResult> GetTabletErrors(
+ const NYPath::TYPath& path,
+ const NApi::TGetTabletErrorsOptions& options) override;
+
+ TFuture<std::vector<NTabletClient::TTabletActionId>> BalanceTabletCells(
+ const TString& tabletCellBundle,
+ const std::vector<NYPath::TYPath>& movableTables,
+ const NApi::TBalanceTabletCellsOptions& options) override;
+
+ TFuture<NChaosClient::TReplicationCardPtr> GetReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TGetReplicationCardOptions& options = {}) override;
+
+ TFuture<void> UpdateChaosTableReplicaProgress(
+ NChaosClient::TReplicaId replicaId,
+ const TUpdateChaosTableReplicaProgressOptions& options = {}) override;
+
+ TFuture<void> AlterReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TAlterReplicationCardOptions& options = {}) override;
+
+ // Queues.
+ TFuture<NQueueClient::IQueueRowsetPtr> PullQueue(
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullQueueOptions& options = {}) override;
+
+ TFuture<NQueueClient::IQueueRowsetPtr> PullConsumer(
+ const NYPath::TRichYPath& consumerPath,
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullConsumerOptions& options = {}) override;
+
+ TFuture<void> RegisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ bool vital,
+ const TRegisterQueueConsumerOptions& options = {}) override;
+
+ TFuture<void> UnregisterQueueConsumer(
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ const TUnregisterQueueConsumerOptions& options = {}) override;
+
+ TFuture<std::vector<TListQueueConsumerRegistrationsResult>> ListQueueConsumerRegistrations(
+ const std::optional<NYPath::TRichYPath>& queuePath,
+ const std::optional<NYPath::TRichYPath>& consumerPath,
+ const TListQueueConsumerRegistrationsOptions& options = {}) override;
+
+ // Files.
+ TFuture<NApi::TGetFileFromCacheResult> GetFileFromCache(
+ const TString& md5,
+ const NApi::TGetFileFromCacheOptions& options) override;
+
+ TFuture<NApi::TPutFileToCacheResult> PutFileToCache(
+ const NYPath::TYPath& path,
+ const TString& expectedMD5,
+ const NApi::TPutFileToCacheOptions& options) override;
+
+ // Security.
+ TFuture<void> AddMember(
+ const TString& group,
+ const TString& member,
+ const NApi::TAddMemberOptions& options) override;
+
+ TFuture<void> RemoveMember(
+ const TString& group,
+ const TString& member,
+ const NApi::TRemoveMemberOptions& options) override;
+
+ TFuture<TCheckPermissionResponse> CheckPermission(
+ const TString& user,
+ const NYPath::TYPath& path,
+ NYTree::EPermission permission,
+ const NApi::TCheckPermissionOptions& options) override;
+
+ TFuture<TCheckPermissionByAclResult> CheckPermissionByAcl(
+ const std::optional<TString>& user,
+ NYTree::EPermission permission,
+ NYTree::INodePtr acl,
+ const NApi::TCheckPermissionByAclOptions& options) override;
+
+ TFuture<void> TransferAccountResources(
+ const TString& srcAccount,
+ const TString& dstAccount,
+ NYTree::INodePtr resourceDelta,
+ const TTransferAccountResourcesOptions& options) override;
+
+ // Scheduler pools.
+ virtual TFuture<void> TransferPoolResources(
+ const TString& srcPool,
+ const TString& dstPool,
+ const TString& poolTree,
+ NYTree::INodePtr resourceDelta,
+ const TTransferPoolResourcesOptions& options) override;
+
+ // Scheduler.
+ TFuture<NScheduler::TOperationId> StartOperation(
+ NScheduler::EOperationType type,
+ const NYson::TYsonString& spec,
+ const NApi::TStartOperationOptions& options) override;
+
+ TFuture<void> AbortOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NApi::TAbortOperationOptions& options) override;
+
+ TFuture<void> SuspendOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NApi::TSuspendOperationOptions& options) override;
+
+ TFuture<void> ResumeOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NApi::TResumeOperationOptions& options) override;
+
+ TFuture<void> CompleteOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NApi::TCompleteOperationOptions& options) override;
+
+ TFuture<void> UpdateOperationParameters(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NYson::TYsonString& parameters,
+ const NApi::TUpdateOperationParametersOptions& options) override;
+
+ TFuture<TOperation> GetOperation(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NApi::TGetOperationOptions& options) override;
+
+ TFuture<void> DumpJobContext(
+ NJobTrackerClient::TJobId jobId,
+ const NYPath::TYPath& path,
+ const NApi::TDumpJobContextOptions& options) override;
+
+ TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr> GetJobInput(
+ NJobTrackerClient::TJobId jobId,
+ const NApi::TGetJobInputOptions& options) override;
+
+ TFuture<NYson::TYsonString> GetJobInputPaths(
+ NJobTrackerClient::TJobId jobId,
+ const NApi::TGetJobInputPathsOptions& options) override;
+
+ TFuture<NYson::TYsonString> GetJobSpec(
+ NJobTrackerClient::TJobId jobId,
+ const NApi::TGetJobSpecOptions& options) override;
+
+ TFuture<TSharedRef> GetJobStderr(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const NApi::TGetJobStderrOptions& options) override;
+
+ TFuture<TSharedRef> GetJobFailContext(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const NApi::TGetJobFailContextOptions& options) override;
+
+ TFuture<NApi::TListOperationsResult> ListOperations(
+ const NApi::TListOperationsOptions& options) override;
+
+ TFuture<NApi::TListJobsResult> ListJobs(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NApi::TListJobsOptions&) override;
+
+ TFuture<NYson::TYsonString> GetJob(
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const NApi::TGetJobOptions& options) override;
+
+ TFuture<void> AbandonJob(
+ NJobTrackerClient::TJobId job_id,
+ const NApi::TAbandonJobOptions& options) override;
+
+ TFuture<TPollJobShellResponse> PollJobShell(
+ NJobTrackerClient::TJobId jobId,
+ const std::optional<TString>& shellName,
+ const NYson::TYsonString& parameters,
+ const NApi::TPollJobShellOptions& options) override;
+
+ TFuture<void> AbortJob(
+ NJobTrackerClient::TJobId jobId,
+ const NApi::TAbortJobOptions& options) override;
+
+ // Metadata.
+ TFuture<NApi::TClusterMeta> GetClusterMeta(
+ const NApi::TGetClusterMetaOptions&) override;
+
+ TFuture<void> CheckClusterLiveness(
+ const TCheckClusterLivenessOptions&) override;
+
+ TFuture<NApi::TSkynetSharePartsLocationsPtr> LocateSkynetShare(
+ const NYPath::TRichYPath&,
+ const NApi::TLocateSkynetShareOptions&) override;
+
+ TFuture<std::vector<NTableClient::TColumnarStatistics>> GetColumnarStatistics(
+ const std::vector<NYPath::TRichYPath>& path,
+ const NApi::TGetColumnarStatisticsOptions& options) override;
+
+ TFuture<TMultiTablePartitions> PartitionTables(
+ const std::vector<NYPath::TRichYPath>& paths,
+ const NApi::TPartitionTablesOptions& options) override;
+
+ TFuture<void> TruncateJournal(
+ const NYPath::TYPath& path,
+ i64 rowCount,
+ const NApi::TTruncateJournalOptions& options) override;
+
+ // Administration.
+ TFuture<int> BuildSnapshot(
+ const NApi::TBuildSnapshotOptions& options) override;
+
+ TFuture<TCellIdToSnapshotIdMap> BuildMasterSnapshots(
+ const TBuildMasterSnapshotsOptions& options) override;
+
+ TFuture<void> SwitchLeader(
+ NHydra::TCellId cellId,
+ const TString& newLeaderAddress,
+ const TSwitchLeaderOptions& options) override;
+
+ TFuture<void> ResetStateHash(
+ NHydra::TCellId cellId,
+ const TResetStateHashOptions& options) override;
+
+ TFuture<void> GCCollect(
+ const NApi::TGCCollectOptions& options) override;
+
+ TFuture<void> KillProcess(
+ const TString& address,
+ const NApi::TKillProcessOptions& options) override;
+
+ TFuture<TString> WriteCoreDump(
+ const TString& address,
+ const NApi::TWriteCoreDumpOptions& options) override;
+
+ TFuture<TGuid> WriteLogBarrier(
+ const TString& address,
+ const TWriteLogBarrierOptions& options) override;
+
+ TFuture<TString> WriteOperationControllerCoreDump(
+ NJobTrackerClient::TOperationId operationId,
+ const NApi::TWriteOperationControllerCoreDumpOptions& options) override;
+
+ TFuture<void> HealExecNode(
+ const TString& address,
+ const THealExecNodeOptions& options) override;
+
+ TFuture<void> SuspendCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TSuspendCoordinatorOptions& options) override;
+
+ TFuture<void> ResumeCoordinator(
+ NObjectClient::TCellId coordinatorCellId,
+ const TResumeCoordinatorOptions& options) override;
+
+ TFuture<void> MigrateReplicationCards(
+ NObjectClient::TCellId chaosCellId,
+ const TMigrateReplicationCardsOptions& options) override;
+
+ TFuture<void> SuspendChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendChaosCellsOptions& options) override;
+
+ TFuture<void> ResumeChaosCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeChaosCellsOptions& options) override;
+
+ TFuture<void> SuspendTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendTabletCellsOptions& options) override;
+
+ TFuture<void> ResumeTabletCells(
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeTabletCellsOptions& options) override;
+
+ TFuture<TMaintenanceId> AddMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ EMaintenanceType type,
+ const TString& comment,
+ const TAddMaintenanceOptions& options) override;
+
+ TFuture<TMaintenanceCounts> RemoveMaintenance(
+ EMaintenanceComponent component,
+ const TString& address,
+ const TMaintenanceFilter& filter,
+ const TRemoveMaintenanceOptions& options) override;
+
+ TFuture<TDisableChunkLocationsResult> DisableChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDisableChunkLocationsOptions& options) override;
+
+ TFuture<TDestroyChunkLocationsResult> DestroyChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDestroyChunkLocationsOptions& options) override;
+
+ TFuture<TResurrectChunkLocationsResult> ResurrectChunkLocations(
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TResurrectChunkLocationsOptions& options) override;
+
+ TFuture<TRequestRebootResult> RequestReboot(
+ const TString& nodeAddress,
+ const TRequestRebootOptions& options) override;
+
+ // Query tracker
+
+ TFuture<NQueryTrackerClient::TQueryId> StartQuery(
+ NQueryTrackerClient::EQueryEngine engine,
+ const TString& query,
+ const TStartQueryOptions& options) override;
+
+ TFuture<void> AbortQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAbortQueryOptions& options) override;
+
+ TFuture<TQueryResult> GetQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex = 0,
+ const TGetQueryResultOptions& options = {}) override;
+
+ TFuture<IUnversionedRowsetPtr> ReadQueryResult(
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex = 0,
+ const TReadQueryResultOptions& options = {}) override;
+
+ TFuture<TQuery> GetQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TGetQueryOptions& options = {}) override;
+
+ TFuture<TListQueriesResult> ListQueries(
+ const TListQueriesOptions& options = {}) override;
+
+ TFuture<void> AlterQuery(
+ NQueryTrackerClient::TQueryId queryId,
+ const TAlterQueryOptions& options = {}) override;
+
+ // Authentication
+
+ virtual TFuture<void> SetUserPassword(
+ const TString& user,
+ const TString& currentPasswordSha256,
+ const TString& newPasswordSha256,
+ const TSetUserPasswordOptions& options) override;
+
+ TFuture<TIssueTokenResult> IssueToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TIssueTokenOptions& options) override;
+
+ TFuture<void> RevokeToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TString& tokenSha256,
+ const TRevokeTokenOptions& options) override;
+
+ TFuture<TListUserTokensResult> ListUserTokens(
+ const TString& user,
+ const TString& passwordSha256,
+ const TListUserTokensOptions& options) override;
+
+private:
+ const TConnectionPtr Connection_;
+ const NRpc::TDynamicChannelPoolPtr ChannelPool_;
+ const NRpc::IChannelPtr RetryingChannel_;
+ const TClientOptions ClientOptions_;
+
+ TLazyIntrusivePtr<NTabletClient::ITableMountCache> TableMountCache_;
+
+ TLazyIntrusivePtr<NTransactionClient::ITimestampProvider> TimestampProvider_;
+
+ NTransactionClient::ITimestampProviderPtr CreateTimestampProvider() const;
+
+ NRpc::IChannelPtr MaybeCreateRetryingChannel(NRpc::IChannelPtr channel, bool retryProxyBanned) const;
+ // Returns an RPC channel to use for API calls to the particular address (e.g.: AttachTransaction).
+ // The channel is non-retrying, so should be wrapped into retrying channel on demand.
+ NRpc::IChannelPtr CreateNonRetryingChannelByAddress(const TString& address) const;
+
+ TConnectionPtr GetRpcProxyConnection() override;
+ TClientPtr GetRpcProxyClient() override;
+
+ NRpc::IChannelPtr GetRetryingChannel() const override;
+ NRpc::IChannelPtr CreateNonRetryingStickyChannel() const override;
+ NRpc::IChannelPtr WrapStickyChannelIntoRetrying(NRpc::IChannelPtr underlying) const override;
+};
+
+DEFINE_REFCOUNTED_TYPE(TClient)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/config.cpp b/yt/yt/client/api/rpc_proxy/config.cpp
new file mode 100644
index 0000000000..2ce120929c
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/config.cpp
@@ -0,0 +1,125 @@
+#include "config.h"
+
+#include "address_helpers.h"
+
+#include <yt/yt/core/net/address.h>
+
+#include <yt/yt/core/bus/tcp/config.h>
+
+#include <yt/yt/core/http/config.h>
+
+#include <yt/yt/core/rpc/config.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NNet;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TConnectionConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("cluster_url", &TThis::ClusterUrl)
+ .Default();
+ registrar.Parameter("cluster_tag", &TThis::ClusterTag)
+ .Optional();
+ registrar.Parameter("proxy_role", &TThis::ProxyRole)
+ .Optional();
+ registrar.Parameter("proxy_address_type", &TThis::ProxyAddressType)
+ .Optional();
+ registrar.Parameter("proxy_network_name", &TThis::ProxyNetworkName)
+ .Optional();
+ registrar.Parameter("proxy_addresses", &TThis::ProxyAddresses)
+ .Alias("addresses")
+ .Optional();
+ registrar.Parameter("proxy_endpoints", &TThis::ProxyEndpoints)
+ .Optional();
+ registrar.Parameter("proxy_unix_domain_socket", &TThis::ProxyUnixDomainSocket)
+ .Optional();
+
+ registrar.Parameter("dynamic_channel_pool", &TThis::DynamicChannelPool)
+ .DefaultNew();
+
+ registrar.Parameter("ping_period", &TThis::PingPeriod)
+ .Default(TDuration::Seconds(3));
+
+ registrar.Parameter("proxy_list_update_period", &TThis::ProxyListUpdatePeriod)
+ .Default(TDuration::Minutes(5));
+ registrar.Parameter("proxy_list_retry_period", &TThis::ProxyListRetryPeriod)
+ .Default(TDuration::Seconds(1));
+ registrar.Parameter("max_proxy_list_retry_period", &TThis::MaxProxyListRetryPeriod)
+ .Default(TDuration::Seconds(30));
+ registrar.Parameter("max_proxy_list_update_attempts", &TThis::MaxProxyListUpdateAttempts)
+ .Default(3);
+
+ registrar.Parameter("rpc_timeout", &TThis::RpcTimeout)
+ .Default(TDuration::Seconds(30));
+ registrar.Parameter("rpc_acknowledgement_timeout", &TThis::RpcAcknowledgementTimeout)
+ .Default(TDuration::Seconds(15));
+ registrar.Parameter("timestamp_provider_latest_timestamp_update_period", &TThis::TimestampProviderLatestTimestampUpdatePeriod)
+ .Default(TDuration::Seconds(3));
+ registrar.Parameter("default_transaction_timeout", &TThis::DefaultTransactionTimeout)
+ .Default(TDuration::Seconds(30));
+ registrar.Parameter("default_lookup_rows_timeout", &TThis::DefaultLookupRowsTimeout)
+ .Default(TDuration::Seconds(30));
+ registrar.Parameter("default_select_rows_timeout", &TThis::DefaultSelectRowsTimeout)
+ .Default(TDuration::Seconds(30));
+ registrar.Parameter("default_total_streaming_timeout", &TThis::DefaultTotalStreamingTimeout)
+ .Default(TDuration::Minutes(15));
+ registrar.Parameter("default_streaming_stall_timeout", &TThis::DefaultStreamingStallTimeout)
+ .Default(TDuration::Minutes(1));
+
+ registrar.Parameter("default_ping_period", &TThis::DefaultPingPeriod)
+ .Default(TDuration::Seconds(5));
+
+ registrar.Parameter("bus_client", &TThis::BusClient)
+ .DefaultNew();
+ registrar.Parameter("idle_channel_ttl", &TThis::IdleChannelTtl)
+ .Default(TDuration::Minutes(5));
+
+ registrar.Parameter("http_client", &TThis::HttpClient)
+ .DefaultNew();
+
+ registrar.Parameter("request_codec", &TThis::RequestCodec)
+ .Default(NCompression::ECodec::None);
+ registrar.Parameter("response_codec", &TThis::ResponseCodec)
+ .Default(NCompression::ECodec::None);
+ // COMPAT(kiselyovp): legacy RPC codecs
+ registrar.Parameter("enable_legacy_rpc_codecs", &TThis::EnableLegacyRpcCodecs)
+ .Default(true);
+
+ registrar.Parameter("enable_retries", &TThis::EnableRetries)
+ .Default(false);
+ registrar.Parameter("retrying_channel", &TThis::RetryingChannel)
+ .DefaultNew();
+
+ registrar.Parameter("modify_rows_batch_capacity", &TThis::ModifyRowsBatchCapacity)
+ .GreaterThanOrEqual(0)
+ .Default(0);
+
+ registrar.Preprocessor([] (TThis* config) {
+ config->DynamicChannelPool->MaxPeerCount = 100;
+ });
+
+ registrar.Parameter("clock_cluster_tag", &TThis::ClockClusterTag)
+ .Default(NObjectClient::InvalidCellTag);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (!config->ProxyEndpoints && !config->ClusterUrl && !config->ProxyAddresses && !config->ProxyUnixDomainSocket) {
+ THROW_ERROR_EXCEPTION("Either \"endpoints\" or \"cluster_url\" or \"proxy_addresses\" or \"proxy_unix_domain_socket\" must be specified");
+ }
+ if (config->ProxyEndpoints && config->ProxyRole) {
+ THROW_ERROR_EXCEPTION("\"proxy_role\" is not supported by Service Discovery");
+ }
+ if (config->ProxyAddresses && config->ProxyAddresses->empty()) {
+ THROW_ERROR_EXCEPTION("\"proxy_addresses\" must not be empty");
+ }
+
+ if (!config->ClusterName && config->ClusterUrl) {
+ config->ClusterName = InferYTClusterFromClusterUrl(*config->ClusterUrl);
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/config.h b/yt/yt/client/api/rpc_proxy/config.h
new file mode 100644
index 0000000000..4f4c7e548d
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/config.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/bus/tcp/config.h>
+
+#include <yt/yt/core/http/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+#include <yt/yt/library/re2/public.h>
+
+#include <yt/yt/client/api/client.h>
+#include <yt/yt/client/api/config.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TConnectionConfig
+ : public NApi::TConnectionConfig
+{
+public:
+ std::optional<TString> ClusterUrl;
+ std::optional<TClusterTag> ClusterTag;
+ std::optional<TString> ProxyRole;
+ std::optional<EAddressType> ProxyAddressType;
+ std::optional<TString> ProxyNetworkName;
+ std::optional<std::vector<TString>> ProxyAddresses;
+ NRpc::TServiceDiscoveryEndpointsConfigPtr ProxyEndpoints;
+ std::optional<TString> ProxyUnixDomainSocket;
+
+ NRpc::TDynamicChannelPoolConfigPtr DynamicChannelPool;
+
+ TDuration PingPeriod;
+ TDuration ProxyListUpdatePeriod;
+ TDuration ProxyListRetryPeriod;
+ TDuration MaxProxyListRetryPeriod;
+ int MaxProxyListUpdateAttempts;
+
+ TDuration RpcTimeout;
+ std::optional<TDuration> RpcAcknowledgementTimeout;
+
+ TDuration TimestampProviderLatestTimestampUpdatePeriod;
+
+ TDuration DefaultTransactionTimeout;
+ TDuration DefaultLookupRowsTimeout;
+ TDuration DefaultSelectRowsTimeout;
+ TDuration DefaultTotalStreamingTimeout;
+ TDuration DefaultStreamingStallTimeout;
+ TDuration DefaultPingPeriod;
+
+ NBus::TBusConfigPtr BusClient;
+ TDuration IdleChannelTtl;
+
+ NHttp::TClientConfigPtr HttpClient;
+
+ NCompression::ECodec RequestCodec;
+ NCompression::ECodec ResponseCodec;
+
+ bool EnableLegacyRpcCodecs;
+
+ bool EnableRetries;
+ NRpc::TRetryingChannelConfigPtr RetryingChannel;
+
+ i64 ModifyRowsBatchCapacity;
+
+ NObjectClient::TCellTag ClockClusterTag;
+
+ REGISTER_YSON_STRUCT(TConnectionConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TConnectionConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/connection.cpp b/yt/yt/client/api/rpc_proxy/connection.cpp
new file mode 100644
index 0000000000..678840e77c
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/connection.cpp
@@ -0,0 +1,18 @@
+#include "connection.h"
+#include "connection_impl.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::IConnectionPtr CreateConnection(
+ TConnectionConfigPtr config,
+ TConnectionOptions options)
+{
+ return New<TConnection>(std::move(config), std::move(options));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/connection.h b/yt/yt/client/api/rpc_proxy/connection.h
new file mode 100644
index 0000000000..b89ec650c4
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/connection.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/connection.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TConnectionOptions
+ : public NApi::TConnectionOptions
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::IConnectionPtr CreateConnection(
+ TConnectionConfigPtr config,
+ TConnectionOptions options = {});
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/connection_impl.cpp b/yt/yt/client/api/rpc_proxy/connection_impl.cpp
new file mode 100644
index 0000000000..65303372fe
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/connection_impl.cpp
@@ -0,0 +1,512 @@
+#include "connection_impl.h"
+#include "discovery_service_proxy.h"
+#include "connection_impl.h"
+#include "client_impl.h"
+#include "config.h"
+#include "helpers.h"
+#include "private.h"
+
+#include <yt/yt/core/net/local_address.h>
+#include <yt/yt/core/net/address.h>
+
+#include <yt/yt/core/concurrency/action_queue.h>
+#include <yt/yt/core/concurrency/periodic_executor.h>
+
+#include <yt/yt/core/bus/tcp/dispatcher.h>
+
+#include <yt/yt/core/http/client.h>
+#include <yt/yt/core/http/http.h>
+#include <yt/yt/core/http/helpers.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <yt/yt/core/rpc/bus/channel.h>
+#include <yt/yt/core/rpc/roaming_channel.h>
+#include <yt/yt/core/rpc/caching_channel_factory.h>
+#include <yt/yt/core/rpc/dynamic_channel_pool.h>
+#include <yt/yt/core/rpc/dispatcher.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/core/service_discovery/service_discovery.h>
+
+#include <yt/yt/build/ya_version.h>
+
+#include <util/system/env.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NBus;
+using namespace NRpc;
+using namespace NNet;
+using namespace NHttp;
+using namespace NYson;
+using namespace NYTree;
+using namespace NConcurrency;
+using namespace NServiceDiscovery;
+
+////////////////////////////////////////////////////////////////////////////////
+
+THashMap<TString, TString> ParseProxyUrlAliasingRules(TString envConfig)
+{
+ if (envConfig.empty()) {
+ return {};
+ }
+ return ConvertTo<THashMap<TString, TString>>(TYsonString(envConfig));
+}
+
+void ApplyProxyUrlAliasingRules(TString& url, const std::optional<THashMap<TString, TString>>& proxyUrlAliasingRules)
+{
+ static const auto rulesFromEnv = ParseProxyUrlAliasingRules(GetEnv("YT_PROXY_URL_ALIASING_CONFIG"));
+
+ const THashMap<TString, TString>& rules =
+ proxyUrlAliasingRules
+ ? proxyUrlAliasingRules.value()
+ : rulesFromEnv;
+
+ if (auto ruleIt = rules.find(url); ruleIt != rules.end()) {
+ url = ruleIt->second;
+ }
+}
+
+TString NormalizeHttpProxyUrl(TString url, const std::optional<THashMap<TString, TString>>& proxyUrlAliasingRules)
+{
+ const TStringBuf CanonicalPrefix = "http://";
+ const TStringBuf CanonicalSuffix = ".yt.yandex.net";
+
+ ApplyProxyUrlAliasingRules(url, proxyUrlAliasingRules);
+
+ if (url.find('.') == TString::npos &&
+ url.find(':') == TString::npos &&
+ url.find("localhost") == TString::npos)
+ {
+ url.append(CanonicalSuffix);
+ }
+
+ if (!url.StartsWith(CanonicalPrefix)) {
+ url.prepend(CanonicalPrefix);
+ }
+
+ return url;
+}
+
+namespace {
+
+TString MakeConnectionLoggingTag(const TConnectionConfigPtr& config, TGuid connectionId)
+{
+ TStringBuilder builder;
+ TDelimitedStringBuilderWrapper delimitedBuilder(&builder);
+ if (config->ClusterUrl) {
+ delimitedBuilder->AppendFormat("ClusterUrl: %v", *config->ClusterUrl);
+ }
+ if (config->ProxyRole) {
+ delimitedBuilder->AppendFormat("ProxyRole: %v", *config->ProxyRole);
+ }
+ delimitedBuilder->AppendFormat("ConnectionId: %v", connectionId);
+ return builder.Flush();
+}
+
+TString MakeEndpointDescription(const TConnectionConfigPtr& config, TGuid connectionId)
+{
+ return Format("Rpc{%v}", MakeConnectionLoggingTag(config, connectionId));
+}
+
+IAttributeDictionaryPtr MakeEndpointAttributes(const TConnectionConfigPtr& config, TGuid connectionId)
+{
+ return ConvertToAttributes(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("rpc_proxy").Value(true)
+ .DoIf(config->ClusterUrl.has_value(), [&] (auto fluent) {
+ fluent
+ .Item("cluster_url").Value(*config->ClusterUrl);
+ })
+ .DoIf(config->ProxyRole.has_value(), [&] (auto fluent) {
+ fluent
+ .Item("proxy_role").Value(*config->ProxyRole);
+ })
+ .Item("connection_id").Value(connectionId)
+ .EndMap());
+}
+
+TString MakeConnectionClusterId(const TConnectionConfigPtr& config)
+{
+ if (config->ClusterName) {
+ return Format("Rpc(Name=%v)", *config->ClusterName);
+ } else if (config->ClusterUrl) {
+ return Format("Rpc(Url=%v)", *config->ClusterUrl);
+ } else {
+ return Format("Rpc(ProxyAddresses=%v)", config->ProxyAddresses);
+ }
+}
+
+class TProxyChannelProvider
+ : public IRoamingChannelProvider
+{
+public:
+ TProxyChannelProvider(
+ TConnectionConfigPtr config,
+ TGuid connectionId,
+ TDynamicChannelPoolPtr pool,
+ bool sticky)
+ : Pool_(std::move(pool))
+ , Sticky_(sticky)
+ , EndpointDescription_(MakeEndpointDescription(config, connectionId))
+ , EndpointAttributes_(MakeEndpointAttributes(config, connectionId))
+ { }
+
+ const TString& GetEndpointDescription() const override
+ {
+ return EndpointDescription_;
+ }
+
+ const NYTree::IAttributeDictionary& GetEndpointAttributes() const override
+ {
+ return *EndpointAttributes_;
+ }
+
+ TFuture<IChannelPtr> GetChannel() override
+ {
+ if (Sticky_) {
+ auto guard = Guard(SpinLock_);
+ if (!Channel_) {
+ Channel_ = Pool_->GetRandomChannel();
+ }
+ return Channel_;
+ } else {
+ return Pool_->GetRandomChannel();
+ }
+ }
+
+ void Terminate(const TError& /*error*/) override
+ { }
+
+ TFuture<IChannelPtr> GetChannel(std::string /*serviceName*/) override
+ {
+ return GetChannel();
+ }
+
+ TFuture<IChannelPtr> GetChannel(const IClientRequestPtr& /*request*/) override
+ {
+ return GetChannel();
+ }
+
+private:
+ const TDynamicChannelPoolPtr Pool_;
+ const bool Sticky_;
+ const TGuid ConnectionId_;
+
+ const TString EndpointDescription_;
+ const IAttributeDictionaryPtr EndpointAttributes_;
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock_);
+ TFuture<IChannelPtr> Channel_;
+};
+
+TConnectionConfigPtr GetPostprocessedConfig(TConnectionConfigPtr config)
+{
+ config->Postprocess();
+ return config;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TConnection::TConnection(TConnectionConfigPtr config, TConnectionOptions options)
+ : Config_(GetPostprocessedConfig(std::move(config)))
+ , ConnectionId_(TGuid::Create())
+ , LoggingTag_(MakeConnectionLoggingTag(Config_, ConnectionId_))
+ , ClusterId_(MakeConnectionClusterId(Config_))
+ , Logger(RpcProxyClientLogger.WithRawTag(LoggingTag_))
+ , ChannelFactory_(Config_->ProxyUnixDomainSocket
+ ? NRpc::NBus::CreateUdsBusChannelFactory(Config_->BusClient)
+ : NRpc::NBus::CreateTcpBusChannelFactory(Config_->BusClient))
+ , CachingChannelFactory_(CreateCachingChannelFactory(
+ ChannelFactory_,
+ Config_->IdleChannelTtl))
+ , ChannelPool_(New<TDynamicChannelPool>(
+ Config_->DynamicChannelPool,
+ ChannelFactory_,
+ MakeEndpointDescription(Config_, ConnectionId_),
+ MakeEndpointAttributes(Config_, ConnectionId_),
+ TApiServiceProxy::GetDescriptor().ServiceName,
+ TDiscoverRequestHook()))
+{
+ if (options.ConnectionInvoker) {
+ ConnectionInvoker_ = options.ConnectionInvoker;
+ } else {
+ ActionQueue_ = New<TActionQueue>("RpcProxyConn");
+ ConnectionInvoker_ = ActionQueue_->GetInvoker();
+ }
+
+ UpdateProxyListExecutor_ = New<TPeriodicExecutor>(
+ GetInvoker(),
+ BIND(&TConnection::OnProxyListUpdate, MakeWeak(this)),
+ TPeriodicExecutorOptions::WithJitter(Config_->ProxyListUpdatePeriod));
+
+ if (Config_->ProxyEndpoints) {
+ ServiceDiscovery_ = NRpc::TDispatcher::Get()->GetServiceDiscovery();
+ if (!ServiceDiscovery_) {
+ ChannelPool_->SetPeerDiscoveryError(TError("No Service Discovery is configured"));
+ return;
+ }
+ }
+
+ if (Config_->ProxyAddresses) {
+ ChannelPool_->SetPeers(*Config_->ProxyAddresses);
+ }
+
+ if (Config_->ProxyUnixDomainSocket) {
+ ChannelPool_->SetPeers({*Config_->ProxyUnixDomainSocket});
+ }
+}
+
+TConnection::~TConnection()
+{
+ RunNoExcept([&] {
+ Terminate();
+ });
+}
+
+IChannelPtr TConnection::CreateChannel(bool sticky)
+{
+ auto provider = New<TProxyChannelProvider>(
+ Config_,
+ ConnectionId_,
+ ChannelPool_,
+ sticky);
+ return CreateRoamingChannel(std::move(provider));
+}
+
+IChannelPtr TConnection::CreateChannelByAddress(const TString& address)
+{
+ return CachingChannelFactory_->CreateChannel(address);
+}
+
+TClusterTag TConnection::GetClusterTag() const
+{
+ THROW_ERROR_EXCEPTION_UNLESS(Config_->ClusterTag,
+ "Cluster tag is not specified in connection config; please set \"cluster_tag\"");
+ return *Config_->ClusterTag;
+}
+
+const TString& TConnection::GetLoggingTag() const
+{
+ return LoggingTag_;
+}
+
+const TString& TConnection::GetClusterId() const
+{
+ return ClusterId_;
+}
+
+const std::optional<TString>& TConnection::GetClusterName() const
+{
+ return Config_->ClusterName;
+}
+
+bool TConnection::IsSameCluster(const IConnectionPtr& other) const
+{
+ // NB: Cluster tag is not defined for RPC proxy connection
+ // so we use some best-effort logic here.
+ return GetClusterId() == other->GetClusterId();
+}
+
+IInvokerPtr TConnection::GetInvoker()
+{
+ return ConnectionInvoker_;
+}
+
+NApi::IClientPtr TConnection::CreateClient(const TClientOptions& options)
+{
+ if (options.Token) {
+ DiscoveryToken_.Store(*options.Token);
+ }
+
+ if (Config_->ClusterUrl || Config_->ProxyEndpoints) {
+ UpdateProxyListExecutor_->Start();
+ }
+
+ return New<TClient>(this, options);
+}
+
+NHiveClient::ITransactionParticipantPtr TConnection::CreateTransactionParticipant(
+ NHiveClient::TCellId /*cellId*/,
+ const TTransactionParticipantOptions& /*options*/)
+{
+ YT_UNIMPLEMENTED();
+}
+
+void TConnection::ClearMetadataCaches()
+{ }
+
+void TConnection::Terminate()
+{
+ YT_LOG_DEBUG("Terminating connection");
+ ChannelPool_->Terminate(TError("Connection terminated"));
+ YT_UNUSED_FUTURE(UpdateProxyListExecutor_->Stop());
+}
+
+const TConnectionConfigPtr& TConnection::GetConfig()
+{
+ return Config_;
+}
+
+std::vector<TString> TConnection::DiscoverProxiesViaHttp()
+{
+ auto correlationId = TGuid::Create();
+
+ try {
+ YT_LOG_DEBUG("Updating proxy list via HTTP (CorrelationId: %v)", correlationId);
+
+ auto poller = TTcpDispatcher::Get()->GetXferPoller();
+ auto client = NHttp::CreateClient(Config_->HttpClient, std::move(poller));
+ auto headers = New<THeaders>();
+ SetUserAgent(headers, GetRpcUserAgent());
+ if (auto token = DiscoveryToken_.Load()) {
+ headers->Add("Authorization", "OAuth " + token);
+ }
+ headers->Add("X-YT-Correlation-Id", ToString(correlationId));
+ headers->Add("X-YT-Header-Format", "<format=text>yson");
+ headers->Add(
+ "X-YT-Parameters",
+ BuildYsonStringFluently(EYsonFormat::Text)
+ .BeginMap()
+ .Item("output_format")
+ .BeginAttributes()
+ .Item("format").Value("text")
+ .EndAttributes()
+ .Value("yson")
+ .OptionalItem("role", Config_->ProxyRole)
+ .OptionalItem("address_type", Config_->ProxyAddressType)
+ .OptionalItem("network_name", Config_->ProxyNetworkName)
+ .EndMap().ToString());
+
+ auto url = NormalizeHttpProxyUrl(*Config_->ClusterUrl) + "/api/v4/discover_proxies";
+ auto rsp = WaitFor(client->Get(url, headers))
+ .ValueOrThrow();
+
+ if (rsp->GetStatusCode() != EStatusCode::OK) {
+ THROW_ERROR_EXCEPTION("HTTP proxy discovery request returned an error")
+ << TErrorAttribute("correlation_id", correlationId)
+ << TErrorAttribute("status_code", rsp->GetStatusCode())
+ << ParseYTError(rsp);
+ }
+
+ auto body = rsp->ReadAll();
+ YT_LOG_DEBUG("Received proxy list via HTTP (CorrelationId: %v)", correlationId);
+
+ auto node = ConvertTo<INodePtr>(TYsonString(ToString(body)));
+ node = node->AsMap()->FindChild("proxies");
+ return ConvertTo<std::vector<TString>>(node);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error discovering RPC proxies via HTTP")
+ << TErrorAttribute("correlation_id", correlationId)
+ << ex;
+ }
+}
+
+std::vector<TString> TConnection::DiscoverProxiesViaServiceDiscovery()
+{
+ YT_LOG_DEBUG("Updating proxy list via Service Discovery");
+
+ if (!ServiceDiscovery_) {
+ THROW_ERROR_EXCEPTION("No service discovery configured");
+ }
+
+ std::vector<TFuture<TEndpointSet>> asyncEndpointSets;
+ for (const auto& cluster : Config_->ProxyEndpoints->Clusters) {
+ asyncEndpointSets.push_back(ServiceDiscovery_->ResolveEndpoints(
+ cluster,
+ Config_->ProxyEndpoints->EndpointSetId));
+ }
+
+ auto endpointSets = WaitFor(AllSet(asyncEndpointSets))
+ .ValueOrThrow();
+
+ std::vector<TString> allAddresses;
+ std::vector<TError> errors;
+ for (int i = 0; i < std::ssize(endpointSets); ++i) {
+ if (!endpointSets[i].IsOK()) {
+ errors.push_back(endpointSets[i]);
+ YT_LOG_WARNING(
+ endpointSets[i],
+ "Could not resolve endpoints from cluster (Cluster: %v, EndpointSetId: %v)",
+ Config_->ProxyEndpoints->Clusters[i],
+ Config_->ProxyEndpoints->EndpointSetId);
+ continue;
+ }
+
+ auto addresses = AddressesFromEndpointSet(endpointSets[i].Value());
+ allAddresses.insert(allAddresses.end(), addresses.begin(), addresses.end());
+ }
+
+ if (errors.size() == endpointSets.size()) {
+ THROW_ERROR_EXCEPTION("Error discovering RPC proxies via Service Discovery") << errors;
+ }
+
+ return allAddresses;
+}
+
+void TConnection::OnProxyListUpdate()
+{
+ auto attributes = CreateEphemeralAttributes();
+ if (Config_->ProxyEndpoints) {
+ attributes->Set("endpoint_set_cluster", Config_->ProxyEndpoints->Cluster);
+ attributes->Set("endpoint_set_id", Config_->ProxyEndpoints->EndpointSetId);
+ } else if (Config_->ClusterUrl) {
+ attributes->Set("cluster_url", Config_->ClusterUrl);
+ } else {
+ YT_ABORT();
+ }
+ attributes->Set("proxy_role", Config_->ProxyRole.value_or(DefaultRpcProxyRole));
+
+ auto backoff = Config_->ProxyListRetryPeriod;
+ for (int attempt = 0;; ++attempt) {
+ try {
+ std::vector<TString> proxies;
+ if (Config_->ProxyEndpoints) {
+ proxies = DiscoverProxiesViaServiceDiscovery();
+ } else if (Config_->ClusterUrl) {
+ proxies = DiscoverProxiesViaHttp();
+ } else {
+ YT_ABORT();
+ }
+
+ if (proxies.empty()) {
+ THROW_ERROR_EXCEPTION("Proxy list is empty");
+ }
+
+ ChannelPool_->SetPeers(proxies);
+
+ break;
+ } catch (const std::exception& ex) {
+ if (attempt > Config_->MaxProxyListUpdateAttempts) {
+ ChannelPool_->SetPeerDiscoveryError(TError(ex) << *attributes);
+ }
+
+ YT_LOG_WARNING(ex, "Error updating proxy list (Attempt: %v, Backoff: %v)",
+ attempt,
+ backoff);
+
+ TDelayedExecutor::WaitForDuration(backoff);
+
+ if (backoff < Config_->MaxProxyListRetryPeriod) {
+ backoff *= 1.2;
+ }
+
+ if (attempt > Config_->MaxProxyListUpdateAttempts) {
+ attempt = 0;
+ }
+ }
+ }
+}
+
+NYson::TYsonString TConnection::GetConfigYson() const
+{
+ return ConvertToYsonString(Config_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/connection_impl.h b/yt/yt/client/api/rpc_proxy/connection_impl.h
new file mode 100644
index 0000000000..5f2c618cfe
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/connection_impl.h
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "connection.h"
+
+#include <yt/yt/client/api/sticky_transaction_pool.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+// TODO(prime@): Create HTTP endpoint for discovery that works without authentication.
+#include <yt/yt/core/misc/atomic_object.h>
+
+#include <yt/yt/core/service_discovery/public.h>
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TConnection
+ : public NApi::IConnection
+{
+public:
+ TConnection(TConnectionConfigPtr config, TConnectionOptions options);
+ ~TConnection();
+
+ NRpc::IChannelPtr CreateChannel(bool sticky);
+ NRpc::IChannelPtr CreateChannelByAddress(const TString& address);
+
+ const TConnectionConfigPtr& GetConfig();
+
+ // IConnection implementation.
+ TClusterTag GetClusterTag() const override;
+ const TString& GetLoggingTag() const override;
+ const TString& GetClusterId() const override;
+ const std::optional<TString>& GetClusterName() const override;
+
+ bool IsSameCluster(const IConnectionPtr& other) const override;
+
+ IInvokerPtr GetInvoker() override;
+
+ NApi::IClientPtr CreateClient(const NApi::TClientOptions& options) override;
+ NHiveClient::ITransactionParticipantPtr CreateTransactionParticipant(
+ NHiveClient::TCellId cellId,
+ const NApi::TTransactionParticipantOptions& options) override;
+
+ void ClearMetadataCaches() override;
+
+ void Terminate() override;
+
+ NYson::TYsonString GetConfigYson() const override;
+
+private:
+ friend class TClient;
+ friend class TTransaction;
+ friend class TTimestampProvider;
+
+ const TConnectionConfigPtr Config_;
+
+ const TGuid ConnectionId_;
+ const TString LoggingTag_;
+ const TString ClusterId_;
+ const NLogging::TLogger Logger;
+ const NRpc::IChannelFactoryPtr ChannelFactory_;
+ const NRpc::IChannelFactoryPtr CachingChannelFactory_;
+ const NRpc::TDynamicChannelPoolPtr ChannelPool_;
+
+ NConcurrency::TActionQueuePtr ActionQueue_;
+ IInvokerPtr ConnectionInvoker_;
+
+ NConcurrency::TPeriodicExecutorPtr UpdateProxyListExecutor_;
+
+ // TODO(prime@): Create HTTP endpoint for discovery that works without authentication.
+ TAtomicObject<TString> DiscoveryToken_;
+
+ NServiceDiscovery::IServiceDiscoveryPtr ServiceDiscovery_;
+
+ std::vector<TString> DiscoverProxiesViaHttp();
+ std::vector<TString> DiscoverProxiesViaServiceDiscovery();
+
+ void OnProxyListUpdate();
+};
+
+DEFINE_REFCOUNTED_TYPE(TConnection)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/discovery_service_proxy.h b/yt/yt/client/api/rpc_proxy/discovery_service_proxy.h
new file mode 100644
index 0000000000..8a524be756
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/discovery_service_proxy.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "public.h"
+
+#include "protocol_version.h"
+
+#include <yt/yt_proto/yt/client/api/rpc_proxy/proto/discovery_service.pb.h>
+
+#include <yt/yt/core/rpc/client.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDiscoveryServiceProxy
+ : public NRpc::TProxyBase
+{
+public:
+ DEFINE_RPC_PROXY(TDiscoveryServiceProxy, DiscoveryService,
+ .SetProtocolVersion(NRpc::TProtocolVersion{0, 0}));
+
+ DEFINE_RPC_PROXY_METHOD(NRpcProxy::NProto, DiscoverProxies,
+ .SetMultiplexingBand(NRpc::EMultiplexingBand::Control));
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/file_reader.cpp b/yt/yt/client/api/rpc_proxy/file_reader.cpp
new file mode 100644
index 0000000000..24c55ff521
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/file_reader.cpp
@@ -0,0 +1,71 @@
+#include "file_reader.h"
+
+#include <yt/yt/client/api/file_reader.h>
+
+#include <yt/yt/client/hydra/public.h>
+
+#include <yt/yt/core/rpc/stream.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NConcurrency;
+using namespace NObjectClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFileReader
+ : public IFileReader
+{
+public:
+ TFileReader(
+ IAsyncZeroCopyInputStreamPtr underlying,
+ TObjectId id,
+ NHydra::TRevision revision)
+ : Underlying_(std::move(underlying))
+ , Id_(id)
+ , Revision_(revision)
+ {
+ YT_VERIFY(Underlying_);
+ }
+
+ TFuture<TSharedRef> Read() override
+ {
+ return Underlying_->Read();
+ }
+
+ NObjectClient::TObjectId GetId() const override
+ {
+ return Id_;
+ }
+
+ NHydra::TRevision GetRevision() const override
+ {
+ return Revision_;
+ }
+
+private:
+ const IAsyncZeroCopyInputStreamPtr Underlying_;
+ const TObjectId Id_;
+ const NHydra::TRevision Revision_;
+};
+
+TFuture<IFileReaderPtr> CreateFileReader(
+ TApiServiceProxy::TReqReadFilePtr request)
+{
+ return NRpc::CreateRpcClientInputStream(std::move(request))
+ .Apply(BIND([=] (const IAsyncZeroCopyInputStreamPtr& inputStream) {
+ return inputStream->Read().Apply(BIND([=] (const TSharedRef& metaRef) {
+ NApi::NRpcProxy::NProto::TReadFileMeta meta;
+ if (!TryDeserializeProto(&meta, metaRef)) {
+ THROW_ERROR_EXCEPTION("Failed to deserialize file stream header");
+ }
+
+ return New<TFileReader>(inputStream, FromProto<TObjectId>(meta.id()), meta.revision());
+ })).As<IFileReaderPtr>();
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/file_reader.h b/yt/yt/client/api/rpc_proxy/file_reader.h
new file mode 100644
index 0000000000..f91af5e27d
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/file_reader.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "api_service_proxy.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<IFileReaderPtr> CreateFileReader(
+ TApiServiceProxy::TReqReadFilePtr request);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/file_writer.cpp b/yt/yt/client/api/rpc_proxy/file_writer.cpp
new file mode 100644
index 0000000000..33318dff3a
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/file_writer.cpp
@@ -0,0 +1,94 @@
+#include "file_writer.h"
+
+#include <yt/yt/client/api/file_writer.h>
+
+#include <yt/yt/core/rpc/stream.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFileWriter
+ : public IFileWriter
+{
+public:
+ TFileWriter(
+ TApiServiceProxy::TReqWriteFilePtr request)
+ : Request_(std::move(request))
+ {
+ YT_VERIFY(Request_);
+ }
+
+ TFuture<void> Open() override
+ {
+ ValidateNotClosed();
+
+ if (!OpenResult_) {
+ OpenResult_ = NRpc::CreateRpcClientOutputStream(Request_)
+ .Apply(BIND([=, this, this_ = MakeStrong(this)] (const IAsyncZeroCopyOutputStreamPtr& outputStream) {
+ Underlying_ = outputStream;
+ })).As<void>();
+ }
+
+ return OpenResult_;
+ }
+
+ TFuture<void> Write(const TSharedRef& data) override
+ {
+ ValidateOpened();
+ ValidateNotClosed();
+
+ if (!data) {
+ return VoidFuture;
+ }
+
+ // Returned future might be set instantly, and the user can modify #data right after that.
+ struct TTag { };
+ auto dataCopy = TSharedMutableRef::MakeCopy<TTag>(data);
+ return Underlying_->Write(dataCopy);
+ }
+
+ TFuture<void> Close() override
+ {
+ ValidateOpened();
+ ValidateNotClosed();
+
+ Closed_ = true;
+ return Underlying_->Close();
+ }
+
+private:
+ const TApiServiceProxy::TReqWriteFilePtr Request_;
+
+ IAsyncZeroCopyOutputStreamPtr Underlying_;
+ TFuture<void> OpenResult_;
+ bool Closed_ = false;
+
+ void ValidateOpened()
+ {
+ if (!OpenResult_ || !OpenResult_.IsSet()) {
+ THROW_ERROR_EXCEPTION("Cannot write into an unopened file writer");
+ }
+ OpenResult_.Get().ThrowOnError();
+ }
+
+ void ValidateNotClosed()
+ {
+ if (Closed_) {
+ THROW_ERROR_EXCEPTION("File writer is closed");
+ }
+ }
+};
+
+IFileWriterPtr CreateFileWriter(
+ TApiServiceProxy::TReqWriteFilePtr request)
+{
+ return New<TFileWriter>(std::move(request));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/file_writer.h b/yt/yt/client/api/rpc_proxy/file_writer.h
new file mode 100644
index 0000000000..19d9b67739
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/file_writer.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "api_service_proxy.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IFileWriterPtr CreateFileWriter(
+ TApiServiceProxy::TReqWriteFilePtr request);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/helpers.cpp b/yt/yt/client/api/rpc_proxy/helpers.cpp
new file mode 100644
index 0000000000..ca0ebfeab4
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/helpers.cpp
@@ -0,0 +1,1871 @@
+#include "helpers.h"
+
+#include <yt/yt/client/api/rowset.h>
+
+#include <yt/yt/client/table_client/columnar_statistics.h>
+#include <yt/yt/client/table_client/column_sort_schema.h>
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_base.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+#include <yt/yt/client/table_client/wire_protocol.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ThrowUnimplemented(const TString& method)
+{
+ THROW_ERROR_EXCEPTION("%Qv method is not implemented in RPC proxy",
+ method);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+////////////////////////////////////////////////////////////////////////////////
+// OPTIONS
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(
+ NProto::TTransactionalOptions* proto,
+ const NApi::TTransactionalOptions& options)
+{
+ if (options.TransactionId) {
+ ToProto(proto->mutable_transaction_id(), options.TransactionId);
+ }
+ proto->set_ping(options.Ping);
+ proto->set_ping_ancestors(options.PingAncestors);
+ proto->set_suppress_transaction_coordinator_sync(options.SuppressTransactionCoordinatorSync);
+ proto->set_suppress_upstream_sync(options.SuppressUpstreamSync);
+}
+
+void ToProto(
+ NProto::TPrerequisiteOptions* proto,
+ const NApi::TPrerequisiteOptions& options)
+{
+ for (const auto& item : options.PrerequisiteTransactionIds) {
+ auto* protoItem = proto->add_transactions();
+ ToProto(protoItem->mutable_transaction_id(), item);
+ }
+ for (const auto& item : options.PrerequisiteRevisions) {
+ auto* protoItem = proto->add_revisions();
+ protoItem->set_path(item->Path);
+ protoItem->set_revision(item->Revision);
+ }
+}
+
+void ToProto(
+ NProto::TMasterReadOptions* proto,
+ const NApi::TMasterReadOptions& options)
+{
+ proto->set_read_from(static_cast<NProto::EMasterReadKind>(options.ReadFrom));
+ proto->set_disable_per_user_cache(options.DisablePerUserCache);
+ proto->set_expire_after_successful_update_time(NYT::ToProto<i64>(options.ExpireAfterSuccessfulUpdateTime));
+ proto->set_expire_after_failed_update_time(NYT::ToProto<i64>(options.ExpireAfterFailedUpdateTime));
+ proto->set_success_staleness_bound(NYT::ToProto<i64>(options.SuccessStalenessBound));
+ if (options.CacheStickyGroupSize) {
+ proto->set_cache_sticky_group_size(*options.CacheStickyGroupSize);
+ }
+}
+
+void ToProto(
+ NProto::TMutatingOptions* proto,
+ const NApi::TMutatingOptions& options)
+{
+ ToProto(proto->mutable_mutation_id(), options.GetOrGenerateMutationId());
+ proto->set_retry(options.Retry);
+}
+
+void ToProto(
+ NProto::TSuppressableAccessTrackingOptions* proto,
+ const NApi::TSuppressableAccessTrackingOptions& options)
+{
+ proto->set_suppress_access_tracking(options.SuppressAccessTracking);
+ proto->set_suppress_modification_tracking(options.SuppressModificationTracking);
+ proto->set_suppress_expiration_timeout_renewal(options.SuppressExpirationTimeoutRenewal);
+}
+
+void ToProto(
+ NProto::TTabletRangeOptions* proto,
+ const NApi::TTabletRangeOptions& options)
+{
+ if (options.FirstTabletIndex) {
+ proto->set_first_tablet_index(*options.FirstTabletIndex);
+ }
+ if (options.LastTabletIndex) {
+ proto->set_last_tablet_index(*options.LastTabletIndex);
+ }
+}
+
+void ToProto(
+ NProto::TTabletReadOptions* protoOptions,
+ const NApi::TTabletReadOptionsBase& options)
+{
+ protoOptions->set_read_from(static_cast<NProto::ETabletReadKind>(options.ReadFrom));
+ if (options.CachedSyncReplicasTimeout) {
+ protoOptions->set_cached_sync_replicas_timeout(NYT::ToProto<i64>(*options.CachedSyncReplicasTimeout));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CONFIGS
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(
+ NProto::TRetentionConfig* protoConfig,
+ const NTableClient::TRetentionConfig& config)
+{
+ protoConfig->set_min_data_versions(config.MinDataVersions);
+ protoConfig->set_max_data_versions(config.MaxDataVersions);
+ protoConfig->set_min_data_ttl(config.MinDataTtl.GetValue());
+ protoConfig->set_max_data_ttl(config.MaxDataTtl.GetValue());
+ protoConfig->set_ignore_major_timestamp(config.IgnoreMajorTimestamp);
+}
+
+void FromProto(
+ NTableClient::TRetentionConfig* config,
+ const NProto::TRetentionConfig& protoConfig)
+{
+ config->MinDataVersions = protoConfig.min_data_versions();
+ config->MaxDataVersions = protoConfig.max_data_versions();
+ config->MinDataTtl = TDuration::FromValue(protoConfig.min_data_ttl());
+ config->MaxDataTtl = TDuration::FromValue(protoConfig.max_data_ttl());
+ config->IgnoreMajorTimestamp = protoConfig.ignore_major_timestamp();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RESULTS
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(
+ NProto::TGetFileFromCacheResult* proto,
+ const NApi::TGetFileFromCacheResult& result)
+{
+ proto->set_path(result.Path);
+}
+
+void FromProto(
+ NApi::TGetFileFromCacheResult* result,
+ const NProto::TGetFileFromCacheResult& proto)
+{
+ result->Path = proto.path();
+}
+
+void ToProto(
+ NProto::TPutFileToCacheResult* proto,
+ const NApi::TPutFileToCacheResult& result)
+{
+ proto->set_path(result.Path);
+}
+
+void FromProto(
+ NApi::TPutFileToCacheResult* result,
+ const NProto::TPutFileToCacheResult& proto)
+{
+ result->Path = proto.path();
+}
+
+void ToProto(
+ NProto::TCheckPermissionResult* proto,
+ const NApi::TCheckPermissionResult& result)
+{
+ proto->Clear();
+
+ proto->set_action(static_cast<NProto::ESecurityAction>(result.Action));
+
+ ToProto(proto->mutable_object_id(), result.ObjectId);
+ if (result.ObjectName) {
+ proto->set_object_name(*result.ObjectName);
+ }
+
+ ToProto(proto->mutable_subject_id(), result.SubjectId);
+ if (result.SubjectName) {
+ proto->set_subject_name(*result.SubjectName);
+ }
+}
+
+void FromProto(
+ NApi::TCheckPermissionResult* result,
+ const NProto::TCheckPermissionResult& proto)
+{
+ result->Action = static_cast<NSecurityClient::ESecurityAction>(proto.action());
+
+ FromProto(&result->ObjectId, proto.object_id());
+ if (proto.has_object_name()) {
+ result->ObjectName = proto.object_name();
+ } else {
+ result->ObjectName.reset();
+ }
+
+ FromProto(&result->SubjectId, proto.subject_id());
+ if (proto.has_subject_name()) {
+ result->SubjectName = proto.subject_name();
+ } else {
+ result->SubjectName.reset();
+ }
+}
+
+void ToProto(
+ NProto::TCheckPermissionByAclResult* proto,
+ const NApi::TCheckPermissionByAclResult& result)
+{
+ proto->Clear();
+
+ proto->set_action(static_cast<NProto::ESecurityAction>(result.Action));
+
+ ToProto(proto->mutable_subject_id(), result.SubjectId);
+ if (result.SubjectName) {
+ proto->set_subject_name(*result.SubjectName);
+ }
+
+ NYT::ToProto(proto->mutable_missing_subjects(), result.MissingSubjects);
+}
+
+void FromProto(
+ NApi::TCheckPermissionByAclResult* result,
+ const NProto::TCheckPermissionByAclResult& proto)
+{
+ result->Action = static_cast<NSecurityClient::ESecurityAction>(proto.action());
+
+ FromProto(&result->SubjectId, proto.subject_id());
+ if (proto.has_subject_name()) {
+ result->SubjectName = proto.subject_name();
+ } else {
+ result->SubjectName.reset();
+ }
+
+ NYT::FromProto(&result->MissingSubjects, proto.missing_subjects());
+}
+
+void ToProto(
+ NProto::TListOperationsResult* proto,
+ const NApi::TListOperationsResult& result)
+{
+ proto->Clear();
+ NYT::ToProto(proto->mutable_operations(), result.Operations);
+
+ if (result.PoolTreeCounts) {
+ auto* poolTreeCounts = proto->mutable_pool_tree_counts()->mutable_entries();
+ for (const auto& entry: *result.PoolTreeCounts) {
+ (*poolTreeCounts)[entry.first] = entry.second;
+ }
+ }
+ if (result.PoolCounts) {
+ for (const auto& entry: *result.PoolCounts) {
+ auto* newPoolCount = proto->mutable_pool_counts()->add_entries();
+ newPoolCount->set_pool(entry.first);
+ newPoolCount->set_count(entry.second);
+ }
+ }
+ if (result.UserCounts) {
+ for (const auto& entry: *result.UserCounts) {
+ auto* newUserCount = proto->mutable_user_counts()->add_entries();
+ newUserCount->set_user(entry.first);
+ newUserCount->set_count(entry.second);
+ }
+ }
+
+ if (result.StateCounts) {
+ for (const auto& state: TEnumTraits<NScheduler::EOperationState>::GetDomainValues()) {
+ if ((*result.StateCounts)[state] != 0) {
+ auto* newStateCount = proto->mutable_state_counts()->add_entries();
+ newStateCount->set_state(ConvertOperationStateToProto(state));
+ newStateCount->set_count((*result.StateCounts)[state]);
+ }
+ }
+ }
+ if (result.TypeCounts) {
+ for (const auto& type: TEnumTraits<NScheduler::EOperationType>::GetDomainValues()) {
+ if ((*result.TypeCounts)[type] != 0) {
+ auto* newTypeCount = proto->mutable_type_counts()->add_entries();
+ newTypeCount->set_type(ConvertOperationTypeToProto(type));
+ newTypeCount->set_count((*result.TypeCounts)[type]);
+ }
+ }
+ }
+
+ if (result.FailedJobsCount) {
+ proto->set_failed_jobs_count(*result.FailedJobsCount);
+ }
+ proto->set_incomplete(result.Incomplete);
+}
+
+void FromProto(
+ NApi::TListOperationsResult* result,
+ const NProto::TListOperationsResult& proto)
+{
+ NYT::FromProto(&result->Operations, proto.operations());
+
+ if (proto.has_pool_tree_counts()) {
+ result->PoolTreeCounts.emplace();
+ for (const auto& [poolTree, count]: proto.pool_tree_counts().entries()) {
+ YT_VERIFY((*result->PoolTreeCounts)[poolTree] == 0);
+ (*result->PoolTreeCounts)[poolTree] = count;
+ }
+ } else {
+ result->PoolTreeCounts.reset();
+ }
+
+ if (proto.has_pool_counts()) {
+ result->PoolCounts.emplace();
+ for (const auto& poolCount: proto.pool_counts().entries()) {
+ auto pool = poolCount.pool();
+ YT_VERIFY((*result->PoolCounts)[pool] == 0);
+ (*result->PoolCounts)[pool] = poolCount.count();
+ }
+ } else {
+ result->PoolCounts.reset();
+ }
+ if (proto.has_user_counts()) {
+ result->UserCounts.emplace();
+ for (const auto& userCount: proto.user_counts().entries()) {
+ auto user = userCount.user();
+ YT_VERIFY((*result->UserCounts)[user] == 0);
+ (*result->UserCounts)[user] = userCount.count();
+ }
+ } else {
+ result->UserCounts.reset();
+ }
+
+ if (proto.has_state_counts()) {
+ result->StateCounts.emplace();
+ std::fill(result->StateCounts->begin(), result->StateCounts->end(), 0);
+ for (const auto& stateCount: proto.state_counts().entries()) {
+ auto state = ConvertOperationStateFromProto(stateCount.state());
+ YT_VERIFY(result->StateCounts->IsDomainValue(state));
+ YT_VERIFY((*result->StateCounts)[state] == 0);
+ (*result->StateCounts)[state] = stateCount.count();
+ }
+ } else {
+ result->StateCounts.reset();
+ }
+ if (proto.has_type_counts()) {
+ result->TypeCounts.emplace();
+ std::fill(result->TypeCounts->begin(), result->TypeCounts->end(), 0);
+ for (const auto& typeCount: proto.type_counts().entries()) {
+ auto type = ConvertOperationTypeFromProto(typeCount.type());
+ YT_VERIFY(result->TypeCounts->IsDomainValue(type));
+ YT_VERIFY((*result->TypeCounts)[type] == 0);
+ (*result->TypeCounts)[type] = typeCount.count();
+ }
+ } else {
+ result->TypeCounts.reset();
+ }
+
+ if (proto.has_failed_jobs_count()) {
+ result->FailedJobsCount = proto.failed_jobs_count();
+ } else {
+ result->FailedJobsCount.reset();
+ }
+ result->Incomplete = proto.incomplete();
+}
+
+void ToProto(
+ NProto::TListJobsResult* proto,
+ const NApi::TListJobsResult& result)
+{
+ proto->Clear();
+ NYT::ToProto(proto->mutable_jobs(), result.Jobs);
+
+ if (result.CypressJobCount) {
+ proto->set_cypress_job_count(*result.CypressJobCount);
+ }
+ if (result.ControllerAgentJobCount) {
+ proto->set_controller_agent_job_count(*result.ControllerAgentJobCount);
+ }
+ if (result.ArchiveJobCount) {
+ proto->set_archive_job_count(*result.ArchiveJobCount);
+ }
+
+ ToProto(proto->mutable_statistics(), result.Statistics);
+ NYT::ToProto(proto->mutable_errors(), result.Errors);
+}
+
+void FromProto(
+ NApi::TListJobsResult* result,
+ const NProto::TListJobsResult& proto)
+{
+ NYT::FromProto(&result->Jobs, proto.jobs());
+
+ if (proto.has_cypress_job_count()) {
+ result->CypressJobCount = proto.cypress_job_count();
+ } else {
+ result->CypressJobCount.reset();
+ }
+ if (proto.has_controller_agent_job_count()) {
+ result->ControllerAgentJobCount = proto.controller_agent_job_count();
+ } else {
+ result->ControllerAgentJobCount.reset();
+ }
+ if (proto.has_archive_job_count()) {
+ result->ArchiveJobCount = proto.archive_job_count();
+ } else {
+ result->ArchiveJobCount.reset();
+ }
+
+ FromProto(&result->Statistics, proto.statistics());
+ NYT::FromProto(&result->Errors, proto.errors());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MISC
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(NProto::TColumnSchema* protoSchema, const NTableClient::TColumnSchema& schema)
+{
+ protoSchema->set_stable_name(schema.StableName().Get());
+ protoSchema->set_name(schema.Name());
+ protoSchema->set_type(NYT::ToProto<int>(GetPhysicalType(schema.CastToV1Type())));
+ auto typeV3Yson = ConvertToYsonString(TTypeV3LogicalTypeWrapper{schema.LogicalType()});
+ protoSchema->set_type_v3(typeV3Yson.ToString());
+ if (schema.Lock()) {
+ protoSchema->set_lock(*schema.Lock());
+ } else {
+ protoSchema->clear_lock();
+ }
+ if (schema.Expression()) {
+ protoSchema->set_expression(*schema.Expression());
+ } else {
+ protoSchema->clear_expression();
+ }
+ if (schema.Aggregate()) {
+ protoSchema->set_aggregate(*schema.Aggregate());
+ } else {
+ protoSchema->clear_aggregate();
+ }
+ if (schema.SortOrder()) {
+ protoSchema->set_sort_order(NYT::ToProto<int>(*schema.SortOrder()));
+ } else {
+ protoSchema->clear_sort_order();
+ }
+ if (schema.Group()) {
+ protoSchema->set_group(*schema.Group());
+ } else {
+ protoSchema->clear_group();
+ }
+ if (schema.Required()) {
+ protoSchema->set_required(schema.Required());
+ } else {
+ protoSchema->clear_required();
+ }
+ if (schema.MaxInlineHunkSize()) {
+ protoSchema->set_max_inline_hunk_size(*schema.MaxInlineHunkSize());
+ } else {
+ protoSchema->clear_max_inline_hunk_size();
+ }
+}
+
+void FromProto(NTableClient::TColumnSchema* schema, const NProto::TColumnSchema& protoSchema)
+{
+ schema->SetName(protoSchema.name());
+ schema->SetStableName(
+ protoSchema.has_stable_name()
+ ? TStableName(protoSchema.stable_name())
+ : TStableName(protoSchema.name()));
+
+ auto physicalType = CheckedEnumCast<EValueType>(protoSchema.type());
+
+ TLogicalTypePtr columnType;
+ if (protoSchema.has_type_v3()) {
+ columnType = ConvertTo<TTypeV3LogicalTypeWrapper>(TYsonStringBuf(protoSchema.type_v3())).LogicalType;
+ auto [v1Type, v1Required] = CastToV1Type(columnType);
+ if (protoSchema.has_required() && protoSchema.required() != v1Required) {
+ THROW_ERROR_EXCEPTION("Fields \"type_v3\" and \"required\" do not match")
+ << TErrorAttribute("type_v3", ToString(*columnType))
+ << TErrorAttribute("required", protoSchema.required());
+ }
+ if (protoSchema.has_logical_type() && v1Type != NYT::FromProto<ESimpleLogicalValueType>(protoSchema.logical_type())) {
+ THROW_ERROR_EXCEPTION("Fields \"type_v3\" and \"logical_type\" do not match")
+ << TErrorAttribute("type_v3", ToString(*columnType))
+ << TErrorAttribute("logical_type", NYT::FromProto<ESimpleLogicalValueType>(protoSchema.logical_type()));
+ }
+ if (protoSchema.has_type() && GetPhysicalType(v1Type) != physicalType) {
+ THROW_ERROR_EXCEPTION("Fields \"type_v3\" and \"logical_type\" do not match")
+ << TErrorAttribute("type_v3", ToString(*columnType))
+ << TErrorAttribute("type", protoSchema.type());
+ }
+ } else if (protoSchema.has_logical_type()) {
+ auto logicalType = CheckedEnumCast<ESimpleLogicalValueType>(protoSchema.logical_type());
+ columnType = MakeLogicalType(logicalType, protoSchema.required());
+ if (protoSchema.has_type() && GetPhysicalType(logicalType) != physicalType) {
+ THROW_ERROR_EXCEPTION("Fields \"logical_type\" and \"type\" do not match")
+ << TErrorAttribute("logical_type", ToString(*columnType))
+ << TErrorAttribute("type", protoSchema.type());
+ }
+ } else if (protoSchema.has_type()) {
+ columnType = MakeLogicalType(GetLogicalType(physicalType), protoSchema.required());
+ }
+
+ if (!columnType) {
+ THROW_ERROR_EXCEPTION("Type is not specified");
+ }
+
+ schema->SetLogicalType(std::move(columnType));
+ schema->SetLock(protoSchema.has_lock() ? std::make_optional(protoSchema.lock()) : std::nullopt);
+ schema->SetExpression(protoSchema.has_expression() ? std::make_optional(protoSchema.expression()) : std::nullopt);
+ schema->SetAggregate(protoSchema.has_aggregate() ? std::make_optional(protoSchema.aggregate()) : std::nullopt);
+ schema->SetSortOrder(protoSchema.has_sort_order() ? std::make_optional(ESortOrder(protoSchema.sort_order())) : std::nullopt);
+ schema->SetGroup(protoSchema.has_group() ? std::make_optional(protoSchema.group()) : std::nullopt);
+ schema->SetMaxInlineHunkSize(protoSchema.has_max_inline_hunk_size() ? std::make_optional(protoSchema.max_inline_hunk_size()) : std::nullopt);
+}
+
+void ToProto(NProto::TTableSchema* protoSchema, const NTableClient::TTableSchema& schema)
+{
+ using NYT::ToProto;
+
+ ToProto(protoSchema->mutable_columns(), schema.Columns());
+ protoSchema->set_strict(schema.GetStrict());
+ protoSchema->set_unique_keys(schema.GetUniqueKeys());
+}
+
+void FromProto(NTableClient::TTableSchema* schema, const NProto::TTableSchema& protoSchema)
+{
+ using NYT::FromProto;
+
+ *schema = NTableClient::TTableSchema(
+ FromProto<std::vector<NTableClient::TColumnSchema>>(protoSchema.columns()),
+ protoSchema.strict(),
+ protoSchema.unique_keys());
+}
+
+void ToProto(NProto::TTableSchema* protoSchema, const NTableClient::TTableSchemaPtr& schema)
+{
+ ToProto(protoSchema, *schema);
+}
+
+void FromProto(NTableClient::TTableSchemaPtr* schema, const NProto::TTableSchema& protoSchema)
+{
+ *schema = New<NTableClient::TTableSchema>();
+ FromProto(schema->Get(), protoSchema);
+}
+
+void ToProto(NProto::TTabletInfo* protoTabletInfo, const NTabletClient::TTabletInfo& tabletInfo)
+{
+ ToProto(protoTabletInfo->mutable_tablet_id(), tabletInfo.TabletId);
+ protoTabletInfo->set_mount_revision(tabletInfo.MountRevision);
+ protoTabletInfo->set_state(static_cast<i32>(tabletInfo.State));
+ ToProto(protoTabletInfo->mutable_pivot_key(), tabletInfo.PivotKey);
+ if (tabletInfo.CellId) {
+ ToProto(protoTabletInfo->mutable_cell_id(), tabletInfo.CellId);
+ }
+}
+
+void FromProto(NTabletClient::TTabletInfo* tabletInfo, const NProto::TTabletInfo& protoTabletInfo)
+{
+ using NYT::FromProto;
+
+ tabletInfo->TabletId =
+ FromProto<TTabletId>(protoTabletInfo.tablet_id());
+ tabletInfo->MountRevision = protoTabletInfo.mount_revision();
+ tabletInfo->State = CheckedEnumCast<ETabletState>(protoTabletInfo.state());
+ tabletInfo->PivotKey = FromProto<NTableClient::TLegacyOwningKey>(protoTabletInfo.pivot_key());
+ if (protoTabletInfo.has_cell_id()) {
+ tabletInfo->CellId = FromProto<TTabletCellId>(protoTabletInfo.cell_id());
+ }
+}
+
+void ToProto(
+ NProto::TQueryStatistics* protoStatistics,
+ const NQueryClient::TQueryStatistics& statistics)
+{
+ protoStatistics->set_rows_read(statistics.RowsRead);
+ protoStatistics->set_data_weight_read(statistics.DataWeightRead);
+ protoStatistics->set_rows_written(statistics.RowsWritten);
+ protoStatistics->set_sync_time(statistics.SyncTime.GetValue());
+ protoStatistics->set_async_time(statistics.AsyncTime.GetValue());
+ protoStatistics->set_execute_time(statistics.ExecuteTime.GetValue());
+ protoStatistics->set_read_time(statistics.ReadTime.GetValue());
+ protoStatistics->set_write_time(statistics.WriteTime.GetValue());
+ protoStatistics->set_codegen_time(statistics.CodegenTime.GetValue());
+ protoStatistics->set_wait_on_ready_event_time(statistics.WaitOnReadyEventTime.GetValue());
+ protoStatistics->set_incomplete_input(statistics.IncompleteInput);
+ protoStatistics->set_incomplete_output(statistics.IncompleteOutput);
+ protoStatistics->set_memory_usage(statistics.MemoryUsage);
+
+ NYT::ToProto(protoStatistics->mutable_inner_statistics(), statistics.InnerStatistics);
+}
+
+void FromProto(
+ NQueryClient::TQueryStatistics* statistics,
+ const NProto::TQueryStatistics& protoStatistics)
+{
+ statistics->RowsRead = protoStatistics.rows_read();
+ statistics->DataWeightRead = protoStatistics.data_weight_read();
+ statistics->RowsWritten = protoStatistics.rows_written();
+ statistics->SyncTime = TDuration::FromValue(protoStatistics.sync_time());
+ statistics->AsyncTime = TDuration::FromValue(protoStatistics.async_time());
+ statistics->ExecuteTime = TDuration::FromValue(protoStatistics.execute_time());
+ statistics->ReadTime = TDuration::FromValue(protoStatistics.read_time());
+ statistics->WriteTime = TDuration::FromValue(protoStatistics.write_time());
+ statistics->CodegenTime = TDuration::FromValue(protoStatistics.codegen_time());
+ statistics->WaitOnReadyEventTime = TDuration::FromValue(protoStatistics.wait_on_ready_event_time());
+ statistics->IncompleteInput = protoStatistics.incomplete_input();
+ statistics->IncompleteOutput = protoStatistics.incomplete_output();
+ statistics->MemoryUsage = protoStatistics.memory_usage();
+
+ NYT::FromProto(&statistics->InnerStatistics, protoStatistics.inner_statistics());
+}
+
+void ToProto(NProto::TOperation* protoOperation, const NApi::TOperation& operation)
+{
+ protoOperation->Clear();
+
+ if (operation.Id) {
+ ToProto(protoOperation->mutable_id(), *operation.Id);
+ }
+ if (operation.Type) {
+ protoOperation->set_type(ConvertOperationTypeToProto(*operation.Type));
+ }
+ if (operation.State) {
+ protoOperation->set_state(ConvertOperationStateToProto(*operation.State));
+ }
+
+ if (operation.StartTime) {
+ protoOperation->set_start_time(NYT::ToProto<i64>(*operation.StartTime));
+ }
+ if (operation.FinishTime) {
+ protoOperation->set_finish_time(NYT::ToProto<i64>(*operation.FinishTime));
+ }
+
+ if (operation.AuthenticatedUser) {
+ protoOperation->set_authenticated_user(*operation.AuthenticatedUser);
+ }
+
+ if (operation.BriefSpec) {
+ protoOperation->set_brief_spec(operation.BriefSpec.ToString());
+ }
+ if (operation.Spec) {
+ protoOperation->set_spec(operation.Spec.ToString());
+ }
+ if (operation.ProvidedSpec) {
+ protoOperation->set_provided_spec(operation.ProvidedSpec.ToString());
+ }
+ if (operation.ExperimentAssignments) {
+ protoOperation->set_experiment_assignments(operation.ExperimentAssignments.ToString());
+ }
+ if (operation.ExperimentAssignmentNames) {
+ protoOperation->set_experiment_assignment_names(operation.ExperimentAssignmentNames.ToString());
+ }
+ if (operation.FullSpec) {
+ protoOperation->set_full_spec(operation.FullSpec.ToString());
+ }
+ if (operation.UnrecognizedSpec) {
+ protoOperation->set_unrecognized_spec(operation.UnrecognizedSpec.ToString());
+ }
+
+ if (operation.BriefProgress) {
+ protoOperation->set_brief_progress(operation.BriefProgress.ToString());
+ }
+ if (operation.Progress) {
+ protoOperation->set_progress(operation.Progress.ToString());
+ }
+
+ if (operation.RuntimeParameters) {
+ protoOperation->set_runtime_parameters(operation.RuntimeParameters.ToString());
+ }
+
+ if (operation.Suspended) {
+ protoOperation->set_suspended(*operation.Suspended);
+ }
+
+ if (operation.Events) {
+ protoOperation->set_events(operation.Events.ToString());
+ }
+ if (operation.Result) {
+ protoOperation->set_result(operation.Result.ToString());
+ }
+
+ if (operation.SlotIndexPerPoolTree) {
+ protoOperation->set_slot_index_per_pool_tree(operation.SlotIndexPerPoolTree.ToString());
+ }
+
+ if (operation.TaskNames) {
+ protoOperation->set_task_names(operation.TaskNames.ToString());
+ }
+
+ if (operation.Alerts) {
+ protoOperation->set_alerts(operation.Alerts.ToString());
+ }
+ if (operation.AlertEvents) {
+ protoOperation->set_alert_events(operation.AlertEvents.ToString());
+ }
+
+ if (operation.ControllerFeatures) {
+ protoOperation->set_controller_features(operation.ControllerFeatures.ToString());
+ }
+
+ if (operation.OtherAttributes) {
+ protoOperation->set_other_attributes(ConvertToYsonString(operation.OtherAttributes).ToString());
+ }
+}
+
+void FromProto(NApi::TOperation* operation, const NProto::TOperation& protoOperation)
+{
+ if (protoOperation.has_id()) {
+ operation->Id = NYT::FromProto<NScheduler::TOperationId>(protoOperation.id());
+ } else {
+ operation->Id.reset();
+ }
+ if (protoOperation.has_type()) {
+ operation->Type = ConvertOperationTypeFromProto(protoOperation.type());
+ } else {
+ operation->Type.reset();
+ }
+ if (protoOperation.has_state()) {
+ operation->State = ConvertOperationStateFromProto(protoOperation.state());
+ } else {
+ operation->State.reset();
+ }
+
+ if (protoOperation.has_start_time()) {
+ operation->StartTime = TInstant::FromValue(protoOperation.start_time());
+ } else {
+ operation->StartTime.reset();
+ }
+ if (protoOperation.has_finish_time()) {
+ operation->FinishTime = TInstant::FromValue(protoOperation.finish_time());
+ } else {
+ operation->FinishTime.reset();
+ }
+
+ if (protoOperation.has_authenticated_user()) {
+ operation->AuthenticatedUser = protoOperation.authenticated_user();
+ } else {
+ operation->AuthenticatedUser.reset();
+ }
+
+ if (protoOperation.has_brief_spec()) {
+ operation->BriefSpec = TYsonString(protoOperation.brief_spec());
+ } else {
+ operation->BriefSpec = TYsonString();
+ }
+ if (protoOperation.has_spec()) {
+ operation->Spec = TYsonString(protoOperation.spec());
+ } else {
+ operation->Spec = TYsonString();
+ }
+ if (protoOperation.has_provided_spec()) {
+ operation->ProvidedSpec = TYsonString(protoOperation.provided_spec());
+ } else {
+ operation->ProvidedSpec = TYsonString();
+ }
+ if (protoOperation.has_full_spec()) {
+ operation->FullSpec = TYsonString(protoOperation.full_spec());
+ } else {
+ operation->FullSpec = TYsonString();
+ }
+ if (protoOperation.has_unrecognized_spec()) {
+ operation->UnrecognizedSpec = TYsonString(protoOperation.unrecognized_spec());
+ } else {
+ operation->UnrecognizedSpec = TYsonString();
+ }
+
+ if (protoOperation.has_experiment_assignments()) {
+ operation->ExperimentAssignments = TYsonString(protoOperation.experiment_assignments());
+ } else {
+ operation->ExperimentAssignments = TYsonString();
+ }
+ if (protoOperation.has_experiment_assignment_names()) {
+ operation->ExperimentAssignmentNames = TYsonString(protoOperation.experiment_assignment_names());
+ } else {
+ operation->ExperimentAssignmentNames = TYsonString();
+ }
+
+ if (protoOperation.has_brief_progress()) {
+ operation->BriefProgress = TYsonString(protoOperation.brief_progress());
+ } else {
+ operation->BriefProgress = TYsonString();
+ }
+ if (protoOperation.has_progress()) {
+ operation->Progress = TYsonString(protoOperation.progress());
+ } else {
+ operation->Progress = TYsonString();
+ }
+
+ if (protoOperation.has_runtime_parameters()) {
+ operation->RuntimeParameters = TYsonString(protoOperation.runtime_parameters());
+ } else {
+ operation->RuntimeParameters = TYsonString();
+ }
+
+ if (protoOperation.has_suspended()) {
+ operation->Suspended = protoOperation.suspended();
+ } else {
+ operation->Suspended.reset();
+ }
+
+ if (protoOperation.has_events()) {
+ operation->Events = TYsonString(protoOperation.events());
+ } else {
+ operation->Events = TYsonString();
+ }
+ if (protoOperation.has_result()) {
+ operation->Result = TYsonString(protoOperation.result());
+ } else {
+ operation->Result = TYsonString();
+ }
+
+ if (protoOperation.has_slot_index_per_pool_tree()) {
+ operation->SlotIndexPerPoolTree = TYsonString(protoOperation.slot_index_per_pool_tree());
+ } else {
+ operation->SlotIndexPerPoolTree = TYsonString();
+ }
+
+ if (protoOperation.has_task_names()) {
+ operation->TaskNames = TYsonString(protoOperation.task_names());
+ } else {
+ operation->TaskNames = TYsonString();
+ }
+
+ if (protoOperation.has_alerts()) {
+ operation->Alerts = TYsonString(protoOperation.alerts());
+ } else {
+ operation->Alerts = TYsonString();
+ }
+ if (protoOperation.has_alert_events()) {
+ operation->AlertEvents = TYsonString(protoOperation.alert_events());
+ } else {
+ operation->AlertEvents = TYsonString();
+ }
+
+ if (protoOperation.has_controller_features()) {
+ operation->ControllerFeatures = TYsonString(protoOperation.controller_features());
+ } else {
+ operation->ControllerFeatures = TYsonString();
+ }
+
+ if (protoOperation.has_other_attributes()) {
+ operation->OtherAttributes = ConvertToAttributes(TYsonStringBuf(protoOperation.other_attributes()));
+ } else {
+ operation->OtherAttributes = {};
+ }
+}
+
+void ToProto(NProto::TJob* protoJob, const NApi::TJob& job)
+{
+ protoJob->Clear();
+
+ if (job.Id) {
+ ToProto(protoJob->mutable_id(), job.Id);
+ }
+ if (job.OperationId) {
+ ToProto(protoJob->mutable_operation_id(), job.OperationId);
+ }
+ if (job.Type) {
+ protoJob->set_type(ConvertJobTypeToProto(*job.Type));
+ }
+ if (auto state = job.GetState()) {
+ protoJob->set_state(ConvertJobStateToProto(*state));
+ }
+ if (job.ControllerState) {
+ protoJob->set_controller_state(
+ ConvertJobStateToProto(*job.ControllerState));
+ }
+ if (job.ArchiveState) {
+ protoJob->set_archive_state(ConvertJobStateToProto(*job.ArchiveState));
+ }
+
+ if (job.StartTime) {
+ protoJob->set_start_time(NYT::ToProto<i64>(*job.StartTime));
+ }
+ if (job.FinishTime) {
+ protoJob->set_finish_time(NYT::ToProto<i64>(*job.FinishTime));
+ }
+
+ if (job.Address) {
+ protoJob->set_address(*job.Address);
+ }
+ if (job.Progress) {
+ protoJob->set_progress(*job.Progress);
+ }
+ if (job.StderrSize) {
+ protoJob->set_stderr_size(*job.StderrSize);
+ }
+ if (job.FailContextSize) {
+ protoJob->set_fail_context_size(*job.FailContextSize);
+ }
+ if (job.HasSpec) {
+ protoJob->set_has_spec(*job.HasSpec);
+ }
+
+ if (job.Error) {
+ protoJob->set_error(job.Error.ToString());
+ }
+ if (job.BriefStatistics) {
+ protoJob->set_brief_statistics(job.BriefStatistics.ToString());
+ }
+ if (job.InputPaths) {
+ protoJob->set_input_paths(job.InputPaths.ToString());
+ }
+ if (job.CoreInfos) {
+ protoJob->set_core_infos(job.CoreInfos.ToString());
+ }
+ if (job.JobCompetitionId) {
+ ToProto(protoJob->mutable_job_competition_id(), job.JobCompetitionId);
+ }
+ if (job.ProbingJobCompetitionId) {
+ ToProto(protoJob->mutable_probing_job_competition_id(), job.ProbingJobCompetitionId);
+ }
+ if (job.HasCompetitors) {
+ protoJob->set_has_competitors(*job.HasCompetitors);
+ }
+ if (job.HasProbingCompetitors) {
+ protoJob->set_has_probing_competitors(*job.HasProbingCompetitors);
+ }
+ if (job.IsStale) {
+ protoJob->set_is_stale(*job.IsStale);
+ }
+ if (job.ExecAttributes) {
+ protoJob->set_exec_attributes(job.ExecAttributes.ToString());
+ }
+ if (job.TaskName) {
+ protoJob->set_task_name(*job.TaskName);
+ }
+ if (job.PoolTree) {
+ protoJob->set_pool_tree(*job.PoolTree);
+ }
+ if (job.Pool) {
+ protoJob->set_pool(*job.Pool);
+ }
+ if (job.JobCookie) {
+ protoJob->set_job_cookie(*job.JobCookie);
+ }
+}
+
+void FromProto(NApi::TJob* job, const NProto::TJob& protoJob)
+{
+ if (protoJob.has_id()) {
+ FromProto(&job->Id, protoJob.id());
+ } else {
+ job->Id = {};
+ }
+ if (protoJob.has_operation_id()) {
+ FromProto(&job->OperationId, protoJob.operation_id());
+ } else {
+ job->OperationId = {};
+ }
+ if (protoJob.has_type()) {
+ job->Type = ConvertJobTypeFromProto(protoJob.type());
+ } else {
+ job->Type.reset();
+ }
+ if (protoJob.has_controller_state()) {
+ job->ControllerState = ConvertJobStateFromProto(protoJob.controller_state());
+ } else {
+ job->ControllerState.reset();
+ }
+ if (protoJob.has_archive_state()) {
+ job->ArchiveState = ConvertJobStateFromProto(protoJob.archive_state());
+ } else {
+ job->ArchiveState.reset();
+ }
+ if (protoJob.has_start_time()) {
+ job->StartTime = TInstant::FromValue(protoJob.start_time());
+ } else {
+ job->StartTime.reset();
+ }
+ if (protoJob.has_finish_time()) {
+ job->FinishTime = TInstant::FromValue(protoJob.finish_time());
+ } else {
+ job->FinishTime.reset();
+ }
+ if (protoJob.has_address()) {
+ job->Address = protoJob.address();
+ } else {
+ job->Address.reset();
+ }
+ if (protoJob.has_progress()) {
+ job->Progress = protoJob.progress();
+ } else {
+ job->Progress.reset();
+ }
+ if (protoJob.has_stderr_size()) {
+ job->StderrSize = protoJob.stderr_size();
+ } else {
+ job->StderrSize.reset();
+ }
+ if (protoJob.has_fail_context_size()) {
+ job->FailContextSize = protoJob.fail_context_size();
+ } else {
+ job->FailContextSize.reset();
+ }
+ if (protoJob.has_has_spec()) {
+ job->HasSpec = protoJob.has_spec();
+ } else {
+ job->HasSpec = false;
+ }
+ if (protoJob.has_error()) {
+ job->Error = TYsonString(protoJob.error());
+ } else {
+ job->Error = TYsonString();
+ }
+ if (protoJob.has_brief_statistics()) {
+ job->BriefStatistics = TYsonString(protoJob.brief_statistics());
+ } else {
+ job->BriefStatistics = TYsonString();
+ }
+ if (protoJob.has_input_paths()) {
+ job->InputPaths = TYsonString(protoJob.input_paths());
+ } else {
+ job->InputPaths = TYsonString();
+ }
+ if (protoJob.has_core_infos()) {
+ job->CoreInfos = TYsonString(protoJob.core_infos());
+ } else {
+ job->CoreInfos = TYsonString();
+ }
+ if (protoJob.has_job_competition_id()) {
+ FromProto(&job->JobCompetitionId, protoJob.job_competition_id());
+ } else {
+ job->JobCompetitionId = {};
+ }
+ if (protoJob.has_probing_job_competition_id()) {
+ FromProto(&job->ProbingJobCompetitionId, protoJob.probing_job_competition_id());
+ } else {
+ job->ProbingJobCompetitionId = {};
+ }
+ if (protoJob.has_has_competitors()) {
+ job->HasCompetitors = protoJob.has_competitors();
+ } else {
+ job->HasCompetitors = false;
+ }
+ if (protoJob.has_has_probing_competitors()) {
+ job->HasProbingCompetitors = protoJob.has_probing_competitors();
+ } else {
+ job->HasProbingCompetitors = false;
+ }
+ if (protoJob.has_is_stale()) {
+ job->IsStale = protoJob.is_stale();
+ } else {
+ job->IsStale.reset();
+ }
+ if (protoJob.has_exec_attributes()) {
+ job->ExecAttributes = TYsonString(protoJob.exec_attributes());
+ } else {
+ job->ExecAttributes = TYsonString();
+ }
+ if (protoJob.has_task_name()) {
+ job->TaskName = protoJob.task_name();
+ } else {
+ job->TaskName.reset();
+ }
+ if (protoJob.has_pool_tree()) {
+ job->PoolTree = protoJob.pool_tree();
+ } else {
+ job->PoolTree.reset();
+ }
+ if (protoJob.has_pool()) {
+ job->Pool = protoJob.pool();
+ } else {
+ job->Pool.reset();
+ }
+ if (protoJob.has_job_cookie()) {
+ job->JobCookie = protoJob.job_cookie();
+ } else {
+ job->JobCookie.reset();
+ }
+}
+
+void ToProto(
+ NProto::TListJobsStatistics* protoStatistics,
+ const NApi::TListJobsStatistics& statistics)
+{
+ protoStatistics->mutable_state_counts()->clear_entries();
+ for (const auto& state: TEnumTraits<NJobTrackerClient::EJobState>::GetDomainValues()) {
+ if (statistics.StateCounts[state] != 0) {
+ auto* newStateCount = protoStatistics->mutable_state_counts()->add_entries();
+ newStateCount->set_state(ConvertJobStateToProto(state));
+ newStateCount->set_count(statistics.StateCounts[state]);
+ }
+ }
+
+ protoStatistics->mutable_type_counts()->clear_entries();
+ for (const auto& type: TEnumTraits<NJobTrackerClient::EJobType>::GetDomainValues()) {
+ if (statistics.TypeCounts[type] != 0) {
+ auto* newTypeCount = protoStatistics->mutable_type_counts()->add_entries();
+ newTypeCount->set_type(ConvertJobTypeToProto(type));
+ newTypeCount->set_count(statistics.TypeCounts[type]);
+ }
+ }
+}
+
+void FromProto(
+ NApi::TListJobsStatistics* statistics,
+ const NProto::TListJobsStatistics& protoStatistics)
+{
+ std::fill(statistics->StateCounts.begin(), statistics->StateCounts.end(), 0);
+ for (const auto& stateCount: protoStatistics.state_counts().entries()) {
+ auto state = ConvertJobStateFromProto(stateCount.state());
+ YT_VERIFY(statistics->StateCounts.IsDomainValue(state));
+ YT_VERIFY(statistics->StateCounts[state] == 0);
+ statistics->StateCounts[state] = stateCount.count();
+ }
+
+ std::fill(statistics->TypeCounts.begin(), statistics->TypeCounts.end(), 0);
+ for (const auto& typeCount: protoStatistics.type_counts().entries()) {
+ auto type = ConvertJobTypeFromProto(typeCount.type());
+ YT_VERIFY(statistics->TypeCounts.IsDomainValue(type));
+ YT_VERIFY(statistics->TypeCounts[type] == 0);
+ statistics->TypeCounts[type] = typeCount.count();
+ }
+}
+
+void ToProto(
+ NProto::TFetchChunkSpecConfig* protoFetchChunkSpecConfig,
+ const NChunkClient::TFetchChunkSpecConfigPtr& fetchChunkSpecConfig)
+{
+ protoFetchChunkSpecConfig->set_max_chunk_per_fetch(
+ fetchChunkSpecConfig->MaxChunksPerFetch);
+ protoFetchChunkSpecConfig->set_max_chunk_per_locate_request(
+ fetchChunkSpecConfig->MaxChunksPerLocateRequest);
+}
+
+void FromProto(
+ const NChunkClient::TFetchChunkSpecConfigPtr& fetchChunkSpecConfig,
+ const NProto::TFetchChunkSpecConfig& protoFetchChunkSpecConfig)
+{
+ NYT::FromProto(
+ &fetchChunkSpecConfig->MaxChunksPerFetch,
+ protoFetchChunkSpecConfig.max_chunk_per_fetch());
+ NYT::FromProto(
+ &fetchChunkSpecConfig->MaxChunksPerLocateRequest,
+ protoFetchChunkSpecConfig.max_chunk_per_locate_request());
+}
+
+void ToProto(
+ NProto::TFetcherConfig* protoFetcherConfig,
+ const NChunkClient::TFetcherConfigPtr& fetcherConfig)
+{
+ protoFetcherConfig->set_node_rpc_timeout(
+ NYT::ToProto<i64>(fetcherConfig->NodeRpcTimeout));
+}
+
+void FromProto(
+ const NChunkClient::TFetcherConfigPtr& fetcherConfig,
+ const NProto::TFetcherConfig& protoFetcherConfig)
+{
+ fetcherConfig->NodeRpcTimeout = TDuration::FromValue(protoFetcherConfig.node_rpc_timeout());
+}
+
+void ToProto(
+ NProto::TColumnarStatistics* protoStatistics,
+ const NTableClient::TColumnarStatistics& statistics)
+{
+ protoStatistics->Clear();
+
+ NYT::ToProto(protoStatistics->mutable_column_data_weights(), statistics.ColumnDataWeights);
+ if (statistics.TimestampTotalWeight) {
+ protoStatistics->set_timestamp_total_weight(*statistics.TimestampTotalWeight);
+ }
+ protoStatistics->set_legacy_chunk_data_weight(statistics.LegacyChunkDataWeight);
+
+ NYT::NTableClient::ToProto(protoStatistics->mutable_column_min_values(), statistics.ColumnMinValues);
+ NYT::NTableClient::ToProto(protoStatistics->mutable_column_max_values(), statistics.ColumnMaxValues);
+ NYT::ToProto(protoStatistics->mutable_column_non_null_value_counts(), statistics.ColumnNonNullValueCounts);
+
+ if (statistics.ChunkRowCount) {
+ protoStatistics->set_chunk_row_count(*statistics.ChunkRowCount);
+ }
+ if (statistics.LegacyChunkRowCount) {
+ protoStatistics->set_legacy_chunk_row_count(*statistics.LegacyChunkRowCount);
+ }
+}
+
+void FromProto(
+ NTableClient::TColumnarStatistics* statistics,
+ const NProto::TColumnarStatistics& protoStatistics)
+{
+ NYT::FromProto(&statistics->ColumnDataWeights, protoStatistics.column_data_weights());
+ if (protoStatistics.has_timestamp_total_weight()) {
+ statistics->TimestampTotalWeight = protoStatistics.timestamp_total_weight();
+ } else {
+ statistics->TimestampTotalWeight.reset();
+ }
+ statistics->LegacyChunkDataWeight = protoStatistics.legacy_chunk_data_weight();
+
+ NYT::NTableClient::FromProto(&statistics->ColumnMinValues, protoStatistics.column_min_values());
+ NYT::NTableClient::FromProto(&statistics->ColumnMaxValues, protoStatistics.column_max_values());
+ NYT::FromProto(&statistics->ColumnNonNullValueCounts, protoStatistics.column_non_null_value_counts());
+
+ if (protoStatistics.has_chunk_row_count()) {
+ statistics->ChunkRowCount = protoStatistics.chunk_row_count();
+ } else {
+ statistics->ChunkRowCount.reset();
+ }
+ if (protoStatistics.has_legacy_chunk_row_count()) {
+ statistics->LegacyChunkRowCount = protoStatistics.legacy_chunk_row_count();
+ } else {
+ statistics->LegacyChunkRowCount.reset();
+ }
+}
+
+void ToProto(
+ NProto::TMultiTablePartition* protoMultiTablePartition,
+ const NApi::TMultiTablePartition& multiTablePartition)
+{
+ protoMultiTablePartition->Clear();
+
+ for (const auto& range : multiTablePartition.TableRanges) {
+ protoMultiTablePartition->add_table_ranges(ToString(range));
+ }
+
+ auto aggregateStatistics = protoMultiTablePartition->mutable_aggregate_statistics();
+ aggregateStatistics->set_chunk_count(multiTablePartition.AggregateStatistics.ChunkCount);
+ aggregateStatistics->set_data_weight(multiTablePartition.AggregateStatistics.DataWeight);
+ aggregateStatistics->set_row_count(multiTablePartition.AggregateStatistics.RowCount);
+}
+
+void FromProto(
+ NApi::TMultiTablePartition* multiTablePartition,
+ const NProto::TMultiTablePartition& protoMultiTablePartition)
+{
+ for (const auto& range : protoMultiTablePartition.table_ranges()) {
+ multiTablePartition->TableRanges.emplace_back(NYPath::TRichYPath::Parse(range));
+ }
+
+ if (protoMultiTablePartition.has_aggregate_statistics()) {
+ const auto& aggregateStatistics = protoMultiTablePartition.aggregate_statistics();
+ multiTablePartition->AggregateStatistics.ChunkCount = aggregateStatistics.chunk_count();
+ multiTablePartition->AggregateStatistics.DataWeight = aggregateStatistics.data_weight();
+ multiTablePartition->AggregateStatistics.RowCount = aggregateStatistics.row_count();
+ }
+}
+
+void FromProto(
+ NApi::TMultiTablePartitions* multiTablePartitions,
+ const NProto::TRspPartitionTables& protoRspPartitionTables)
+{
+ NYT::FromProto(
+ &multiTablePartitions->Partitions,
+ protoRspPartitionTables.partitions());
+}
+
+void ToProto(
+ NProto::TRowBatchReadOptions* proto,
+ const NQueueClient::TQueueRowBatchReadOptions& result)
+{
+ proto->set_max_row_count(result.MaxRowCount);
+ proto->set_max_data_weight(result.MaxDataWeight);
+ if (result.DataWeightPerRowHint) {
+ proto->set_data_weight_per_row_hint(*result.DataWeightPerRowHint);
+ }
+}
+
+void FromProto(
+ NQueueClient::TQueueRowBatchReadOptions* result,
+ const NProto::TRowBatchReadOptions& proto)
+{
+ result->MaxRowCount = proto.max_row_count();
+ result->MaxDataWeight = proto.max_data_weight();
+ if (proto.has_data_weight_per_row_hint()) {
+ result->DataWeightPerRowHint = proto.data_weight_per_row_hint();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ENUMS
+////////////////////////////////////////////////////////////////////////////////
+
+NProto::EOperationType ConvertOperationTypeToProto(
+ NScheduler::EOperationType operationType)
+{
+ switch (operationType) {
+ case NScheduler::EOperationType::Map:
+ return NProto::EOperationType::OT_MAP;
+ case NScheduler::EOperationType::Merge:
+ return NProto::EOperationType::OT_MERGE;
+ case NScheduler::EOperationType::Erase:
+ return NProto::EOperationType::OT_ERASE;
+ case NScheduler::EOperationType::Sort:
+ return NProto::EOperationType::OT_SORT;
+ case NScheduler::EOperationType::Reduce:
+ return NProto::EOperationType::OT_REDUCE;
+ case NScheduler::EOperationType::MapReduce:
+ return NProto::EOperationType::OT_MAP_REDUCE;
+ case NScheduler::EOperationType::RemoteCopy:
+ return NProto::EOperationType::OT_REMOTE_COPY;
+ case NScheduler::EOperationType::JoinReduce:
+ return NProto::EOperationType::OT_JOIN_REDUCE;
+ case NScheduler::EOperationType::Vanilla:
+ return NProto::EOperationType::OT_VANILLA;
+ }
+ YT_ABORT();
+}
+
+NScheduler::EOperationType ConvertOperationTypeFromProto(
+ NProto::EOperationType proto)
+{
+ switch (proto) {
+ case NProto::EOperationType::OT_MAP:
+ return NScheduler::EOperationType::Map;
+ case NProto::EOperationType::OT_MERGE:
+ return NScheduler::EOperationType::Merge;
+ case NProto::EOperationType::OT_ERASE:
+ return NScheduler::EOperationType::Erase;
+ case NProto::EOperationType::OT_SORT:
+ return NScheduler::EOperationType::Sort;
+ case NProto::EOperationType::OT_REDUCE:
+ return NScheduler::EOperationType::Reduce;
+ case NProto::EOperationType::OT_MAP_REDUCE:
+ return NScheduler::EOperationType::MapReduce;
+ case NProto::EOperationType::OT_REMOTE_COPY:
+ return NScheduler::EOperationType::RemoteCopy;
+ case NProto::EOperationType::OT_JOIN_REDUCE:
+ return NScheduler::EOperationType::JoinReduce;
+ case NProto::EOperationType::OT_VANILLA:
+ return NScheduler::EOperationType::Vanilla;
+ case NProto::EOperationType::OT_UNKNOWN:
+ THROW_ERROR_EXCEPTION("Protobuf contains unknown value for operation type");
+ }
+ YT_ABORT();
+}
+
+NProto::EOperationState ConvertOperationStateToProto(
+ NScheduler::EOperationState operationState)
+{
+ switch (operationState) {
+ case NScheduler::EOperationState::None:
+ return NProto::EOperationState::OS_NONE;
+ case NScheduler::EOperationState::Starting:
+ return NProto::EOperationState::OS_STARTING;
+ case NScheduler::EOperationState::Orphaned:
+ return NProto::EOperationState::OS_ORPHANED;
+ case NScheduler::EOperationState::WaitingForAgent:
+ return NProto::EOperationState::OS_WAITING_FOR_AGENT;
+ case NScheduler::EOperationState::Initializing:
+ return NProto::EOperationState::OS_INITIALIZING;
+ case NScheduler::EOperationState::Preparing:
+ return NProto::EOperationState::OS_PREPARING;
+ case NScheduler::EOperationState::Materializing:
+ return NProto::EOperationState::OS_MATERIALIZING;
+ case NScheduler::EOperationState::Reviving:
+ return NProto::EOperationState::OS_REVIVING;
+ case NScheduler::EOperationState::RevivingJobs:
+ return NProto::EOperationState::OS_REVIVING_JOBS;
+ case NScheduler::EOperationState::Pending:
+ return NProto::EOperationState::OS_PENDING;
+ case NScheduler::EOperationState::Running:
+ return NProto::EOperationState::OS_RUNNING;
+ case NScheduler::EOperationState::Completing:
+ return NProto::EOperationState::OS_COMPLETING;
+ case NScheduler::EOperationState::Completed:
+ return NProto::EOperationState::OS_COMPLETED;
+ case NScheduler::EOperationState::Aborting:
+ return NProto::EOperationState::OS_ABORTING;
+ case NScheduler::EOperationState::Aborted:
+ return NProto::EOperationState::OS_ABORTED;
+ case NScheduler::EOperationState::Failing:
+ return NProto::EOperationState::OS_FAILING;
+ case NScheduler::EOperationState::Failed:
+ return NProto::EOperationState::OS_FAILED;
+ case NScheduler::EOperationState::ReviveInitializing:
+ return NProto::EOperationState::OS_REVIVE_INITIALIZING;
+ }
+ YT_ABORT();
+}
+
+NScheduler::EOperationState ConvertOperationStateFromProto(
+ NProto::EOperationState proto)
+{
+ switch (proto) {
+ case NProto::EOperationState::OS_NONE:
+ return NScheduler::EOperationState::None;
+ case NProto::EOperationState::OS_STARTING:
+ return NScheduler::EOperationState::Starting;
+ case NProto::EOperationState::OS_ORPHANED:
+ return NScheduler::EOperationState::Orphaned;
+ case NProto::EOperationState::OS_WAITING_FOR_AGENT:
+ return NScheduler::EOperationState::WaitingForAgent;
+ case NProto::EOperationState::OS_INITIALIZING:
+ return NScheduler::EOperationState::Initializing;
+ case NProto::EOperationState::OS_PREPARING:
+ return NScheduler::EOperationState::Preparing;
+ case NProto::EOperationState::OS_MATERIALIZING:
+ return NScheduler::EOperationState::Materializing;
+ case NProto::EOperationState::OS_REVIVING:
+ return NScheduler::EOperationState::Reviving;
+ case NProto::EOperationState::OS_REVIVING_JOBS:
+ return NScheduler::EOperationState::RevivingJobs;
+ case NProto::EOperationState::OS_PENDING:
+ return NScheduler::EOperationState::Pending;
+ case NProto::EOperationState::OS_RUNNING:
+ return NScheduler::EOperationState::Running;
+ case NProto::EOperationState::OS_COMPLETING:
+ return NScheduler::EOperationState::Completing;
+ case NProto::EOperationState::OS_COMPLETED:
+ return NScheduler::EOperationState::Completed;
+ case NProto::EOperationState::OS_ABORTING:
+ return NScheduler::EOperationState::Aborting;
+ case NProto::EOperationState::OS_ABORTED:
+ return NScheduler::EOperationState::Aborted;
+ case NProto::EOperationState::OS_FAILING:
+ return NScheduler::EOperationState::Failing;
+ case NProto::EOperationState::OS_FAILED:
+ return NScheduler::EOperationState::Failed;
+ case NProto::EOperationState::OS_REVIVE_INITIALIZING:
+ return NScheduler::EOperationState::ReviveInitializing;
+ case NProto::EOperationState::OS_UNKNOWN:
+ THROW_ERROR_EXCEPTION("Protobuf contains unknown value for operation state");
+ }
+ YT_ABORT();
+}
+
+NProto::EJobType ConvertJobTypeToProto(
+ NJobTrackerClient::EJobType jobType)
+{
+ switch (jobType) {
+ case NJobTrackerClient::EJobType::Map:
+ return NProto::EJobType::JT_MAP;
+ case NJobTrackerClient::EJobType::PartitionMap:
+ return NProto::EJobType::JT_PARTITION_MAP;
+ case NJobTrackerClient::EJobType::SortedMerge:
+ return NProto::EJobType::JT_SORTED_MERGE;
+ case NJobTrackerClient::EJobType::OrderedMerge:
+ return NProto::EJobType::JT_ORDERED_MERGE;
+ case NJobTrackerClient::EJobType::UnorderedMerge:
+ return NProto::EJobType::JT_UNORDERED_MERGE;
+ case NJobTrackerClient::EJobType::Partition:
+ return NProto::EJobType::JT_PARTITION;
+ case NJobTrackerClient::EJobType::SimpleSort:
+ return NProto::EJobType::JT_SIMPLE_SORT;
+ case NJobTrackerClient::EJobType::FinalSort:
+ return NProto::EJobType::JT_FINAL_SORT;
+ case NJobTrackerClient::EJobType::SortedReduce:
+ return NProto::EJobType::JT_SORTED_REDUCE;
+ case NJobTrackerClient::EJobType::PartitionReduce:
+ return NProto::EJobType::JT_PARTITION_REDUCE;
+ case NJobTrackerClient::EJobType::ReduceCombiner:
+ return NProto::EJobType::JT_REDUCE_COMBINER;
+ case NJobTrackerClient::EJobType::RemoteCopy:
+ return NProto::EJobType::JT_REMOTE_COPY;
+ case NJobTrackerClient::EJobType::IntermediateSort:
+ return NProto::EJobType::JT_INTERMEDIATE_SORT;
+ case NJobTrackerClient::EJobType::OrderedMap:
+ return NProto::EJobType::JT_ORDERED_MAP;
+ case NJobTrackerClient::EJobType::JoinReduce:
+ return NProto::EJobType::JT_JOIN_REDUCE;
+ case NJobTrackerClient::EJobType::Vanilla:
+ return NProto::EJobType::JT_VANILLA;
+ case NJobTrackerClient::EJobType::SchedulerUnknown:
+ return NProto::EJobType::JT_SCHEDULER_UNKNOWN;
+ case NJobTrackerClient::EJobType::ReplicateChunk:
+ return NProto::EJobType::JT_REPLICATE_CHUNK;
+ case NJobTrackerClient::EJobType::RemoveChunk:
+ return NProto::EJobType::JT_REMOVE_CHUNK;
+ case NJobTrackerClient::EJobType::RepairChunk:
+ return NProto::EJobType::JT_REPAIR_CHUNK;
+ case NJobTrackerClient::EJobType::SealChunk:
+ return NProto::EJobType::JT_SEAL_CHUNK;
+ case NJobTrackerClient::EJobType::MergeChunks:
+ return NProto::EJobType::JT_MERGE_CHUNKS;
+ case NJobTrackerClient::EJobType::AutotomizeChunk:
+ return NProto::EJobType::JT_AUTOTOMIZE_CHUNK;
+ case NJobTrackerClient::EJobType::ShallowMerge:
+ return NProto::EJobType::JT_SHALLOW_MERGE;
+ case NJobTrackerClient::EJobType::ReincarnateChunk:
+ return NProto::EJobType::JT_REINCARNATE_CHUNK;
+ }
+ YT_ABORT();
+}
+
+NJobTrackerClient::EJobType ConvertJobTypeFromProto(
+ NProto::EJobType proto)
+{
+ switch (proto) {
+ case NProto::EJobType::JT_MAP:
+ return NJobTrackerClient::EJobType::Map;
+ case NProto::EJobType::JT_PARTITION_MAP:
+ return NJobTrackerClient::EJobType::PartitionMap;
+ case NProto::EJobType::JT_SORTED_MERGE:
+ return NJobTrackerClient::EJobType::SortedMerge;
+ case NProto::EJobType::JT_ORDERED_MERGE:
+ return NJobTrackerClient::EJobType::OrderedMerge;
+ case NProto::EJobType::JT_UNORDERED_MERGE:
+ return NJobTrackerClient::EJobType::UnorderedMerge;
+ case NProto::EJobType::JT_PARTITION:
+ return NJobTrackerClient::EJobType::Partition;
+ case NProto::EJobType::JT_SIMPLE_SORT:
+ return NJobTrackerClient::EJobType::SimpleSort;
+ case NProto::EJobType::JT_FINAL_SORT:
+ return NJobTrackerClient::EJobType::FinalSort;
+ case NProto::EJobType::JT_SORTED_REDUCE:
+ return NJobTrackerClient::EJobType::SortedReduce;
+ case NProto::EJobType::JT_PARTITION_REDUCE:
+ return NJobTrackerClient::EJobType::PartitionReduce;
+ case NProto::EJobType::JT_REDUCE_COMBINER:
+ return NJobTrackerClient::EJobType::ReduceCombiner;
+ case NProto::EJobType::JT_REMOTE_COPY:
+ return NJobTrackerClient::EJobType::RemoteCopy;
+ case NProto::EJobType::JT_INTERMEDIATE_SORT:
+ return NJobTrackerClient::EJobType::IntermediateSort;
+ case NProto::EJobType::JT_ORDERED_MAP:
+ return NJobTrackerClient::EJobType::OrderedMap;
+ case NProto::EJobType::JT_JOIN_REDUCE:
+ return NJobTrackerClient::EJobType::JoinReduce;
+ case NProto::EJobType::JT_VANILLA:
+ return NJobTrackerClient::EJobType::Vanilla;
+ case NProto::EJobType::JT_SCHEDULER_UNKNOWN:
+ return NJobTrackerClient::EJobType::SchedulerUnknown;
+ case NProto::EJobType::JT_REPLICATE_CHUNK:
+ return NJobTrackerClient::EJobType::ReplicateChunk;
+ case NProto::EJobType::JT_REMOVE_CHUNK:
+ return NJobTrackerClient::EJobType::RemoveChunk;
+ case NProto::EJobType::JT_REPAIR_CHUNK:
+ return NJobTrackerClient::EJobType::RepairChunk;
+ case NProto::EJobType::JT_SEAL_CHUNK:
+ return NJobTrackerClient::EJobType::SealChunk;
+ case NProto::EJobType::JT_MERGE_CHUNKS:
+ return NJobTrackerClient::EJobType::MergeChunks;
+ case NProto::EJobType::JT_AUTOTOMIZE_CHUNK:
+ return NJobTrackerClient::EJobType::AutotomizeChunk;
+ case NProto::EJobType::JT_SHALLOW_MERGE:
+ return NJobTrackerClient::EJobType::ShallowMerge;
+ case NProto::EJobType::JT_REINCARNATE_CHUNK:
+ return NJobTrackerClient::EJobType::ReincarnateChunk;
+ case NProto::EJobType::JT_UNKNOWN:
+ THROW_ERROR_EXCEPTION("Protobuf contains unknown value for job type");
+ }
+ YT_ABORT();
+}
+
+NProto::EJobState ConvertJobStateToProto(
+ NJobTrackerClient::EJobState jobState)
+{
+ switch (jobState) {
+ case NJobTrackerClient::EJobState::Waiting:
+ return NProto::EJobState::JS_WAITING;
+ case NJobTrackerClient::EJobState::Running:
+ return NProto::EJobState::JS_RUNNING;
+ case NJobTrackerClient::EJobState::Aborting:
+ return NProto::EJobState::JS_ABORTING;
+ case NJobTrackerClient::EJobState::Completed:
+ return NProto::EJobState::JS_COMPLETED;
+ case NJobTrackerClient::EJobState::Failed:
+ return NProto::EJobState::JS_FAILED;
+ case NJobTrackerClient::EJobState::Aborted:
+ return NProto::EJobState::JS_ABORTED;
+ case NJobTrackerClient::EJobState::Lost:
+ return NProto::EJobState::JS_LOST;
+ case NJobTrackerClient::EJobState::None:
+ return NProto::EJobState::JS_NONE;
+ }
+ YT_ABORT();
+}
+
+NJobTrackerClient::EJobState ConvertJobStateFromProto(
+ NProto::EJobState proto)
+{
+ switch (proto) {
+ case NProto::EJobState::JS_WAITING:
+ return NJobTrackerClient::EJobState::Waiting;
+ case NProto::EJobState::JS_RUNNING:
+ return NJobTrackerClient::EJobState::Running;
+ case NProto::EJobState::JS_ABORTING:
+ return NJobTrackerClient::EJobState::Aborting;
+ case NProto::EJobState::JS_COMPLETED:
+ return NJobTrackerClient::EJobState::Completed;
+ case NProto::EJobState::JS_FAILED:
+ return NJobTrackerClient::EJobState::Failed;
+ case NProto::EJobState::JS_ABORTED:
+ return NJobTrackerClient::EJobState::Aborted;
+ case NProto::EJobState::JS_LOST:
+ return NJobTrackerClient::EJobState::Lost;
+ case NProto::EJobState::JS_NONE:
+ return NJobTrackerClient::EJobState::None;
+ case NProto::EJobState::JS_UNKNOWN:
+ THROW_ERROR_EXCEPTION("Protobuf contains unknown value for job state");
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsRetriableError(const TError& error, bool retryProxyBanned)
+{
+ if (error.FindMatching(NRpcProxy::EErrorCode::ProxyBanned) ||
+ error.FindMatching(NRpc::EErrorCode::PeerBanned))
+ {
+ return retryProxyBanned;
+ }
+
+ //! Retriable error codes are based on the ones used in http client.
+ return
+ error.FindMatching(NRpc::EErrorCode::RequestQueueSizeLimitExceeded) ||
+ error.FindMatching(NRpc::EErrorCode::TransportError) ||
+ error.FindMatching(NRpc::EErrorCode::Unavailable) ||
+ error.FindMatching(NRpc::EErrorCode::TransientFailure) ||
+ error.FindMatching(NSecurityClient::EErrorCode::RequestQueueSizeLimitExceeded);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void SetTimeoutOptions(
+ NRpc::TClientRequest& request,
+ const TTimeoutOptions& options)
+{
+ request.SetTimeout(options.Timeout);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ROWSETS
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+struct TRowsetTraits;
+
+template <>
+struct TRowsetTraits<TUnversionedRow>
+{
+ static constexpr NProto::ERowsetKind Kind = NProto::RK_UNVERSIONED;
+};
+
+template <>
+struct TRowsetTraits<TVersionedRow>
+{
+ static constexpr NProto::ERowsetKind Kind = NProto::RK_VERSIONED;
+};
+
+void ValidateRowsetDescriptor(
+ const NProto::TRowsetDescriptor& descriptor,
+ int expectedVersion,
+ NProto::ERowsetKind expectedKind,
+ NProto::ERowsetFormat expectedFormat)
+{
+ if (descriptor.wire_format_version() != expectedVersion) {
+ THROW_ERROR_EXCEPTION(
+ "Incompatible rowset wire format version: expected %v, got %v",
+ expectedVersion,
+ descriptor.wire_format_version());
+ }
+
+ if (descriptor.rowset_kind() != expectedKind) {
+ THROW_ERROR_EXCEPTION(
+ "Incompatible rowset kind: expected %Qv, got %Qv",
+ NProto::ERowsetKind_Name(expectedKind),
+ NProto::ERowsetKind_Name(descriptor.rowset_kind()));
+ }
+ if (descriptor.rowset_format() != expectedFormat) {
+ THROW_ERROR_EXCEPTION(
+ "Incompatible rowset format: expected %Qv, got %Qv",
+ NProto::ERowsetFormat_Name(expectedFormat),
+ NProto::ERowsetFormat_Name(descriptor.rowset_format()));
+ }
+}
+
+std::vector<TSharedRef> SerializeRowset(
+ const NTableClient::TNameTablePtr& nameTable,
+ TRange<NTableClient::TUnversionedRow> rows,
+ NProto::TRowsetDescriptor* descriptor)
+{
+ descriptor->Clear();
+ descriptor->set_wire_format_version(NApi::NRpcProxy::CurrentWireFormatVersion);
+ descriptor->set_rowset_kind(NProto::RK_UNVERSIONED);
+ for (int id = 0; id < nameTable->GetSize(); ++id) {
+ auto* entry = descriptor->add_name_table_entries();
+ entry->set_name(TString(nameTable->GetName(id)));
+ }
+
+ auto writer = CreateWireProtocolWriter();
+ writer->WriteUnversionedRowset(rows);
+ return writer->Finish();
+}
+
+template <class TRow>
+std::vector<TSharedRef> SerializeRowset(
+ const TTableSchema& schema,
+ TRange<TRow> rows,
+ NProto::TRowsetDescriptor* descriptor)
+{
+ descriptor->Clear();
+ descriptor->set_wire_format_version(NApi::NRpcProxy::CurrentWireFormatVersion);
+ descriptor->set_rowset_kind(TRowsetTraits<TRow>::Kind);
+ ToProto(descriptor->mutable_schema(), schema);
+
+ // COMPAT(babenko)
+ for (const auto& column : schema.Columns()) {
+ auto* entry = descriptor->add_name_table_entries();
+ entry->set_name(column.Name());
+ // we save physical type for backward compatibility
+ // COMPAT(babenko)
+ entry->set_type(ToProto<int>(column.GetWireType()));
+ // COMPAT(babenko)
+ entry->set_logical_type(ToProto<int>(column.CastToV1Type()));
+ }
+
+ auto writer = CreateWireProtocolWriter();
+ writer->WriteRowset(rows);
+ return writer->Finish();
+}
+
+// Instantiate templates.
+template std::vector<TSharedRef> SerializeRowset(
+ const TTableSchema& schema,
+ TRange<TUnversionedRow> rows,
+ NProto::TRowsetDescriptor* descriptor);
+template std::vector<TSharedRef> SerializeRowset(
+ const TTableSchema& schema,
+ TRange<TVersionedRow> rows,
+ NProto::TRowsetDescriptor* descriptor);
+
+TTableSchemaPtr DeserializeRowsetSchema(
+ const NProto::TRowsetDescriptor& descriptor)
+{
+ if (descriptor.has_schema()) {
+ return NYT::FromProto<TTableSchemaPtr>(descriptor.schema());
+ }
+
+ // COMPAT(babenko)
+ std::vector<TColumnSchema> columns;
+ columns.resize(descriptor.name_table_entries_size());
+ for (int i = 0; i < descriptor.name_table_entries_size(); ++i) {
+ const auto& entry = descriptor.name_table_entries(i);
+ if (entry.has_name()) {
+ columns[i].SetName(entry.name());
+ columns[i].SetStableName(TStableName(entry.name()));
+ }
+ if (entry.has_logical_type()) {
+ auto simpleLogicalType = CheckedEnumCast<NTableClient::ESimpleLogicalValueType>(entry.logical_type());
+ columns[i].SetLogicalType(OptionalLogicalType(SimpleLogicalType(simpleLogicalType)));
+ } else if (entry.has_type()) {
+ auto simpleLogicalType = CheckedEnumCast<NTableClient::ESimpleLogicalValueType>(entry.type());
+ columns[i].SetLogicalType(OptionalLogicalType(SimpleLogicalType(simpleLogicalType)));
+ }
+ }
+
+ auto schema = New<TTableSchema>(std::move(columns));
+ ValidateColumnUniqueness(*schema);
+ return schema;
+}
+
+namespace {
+
+template <class TRow>
+auto ReadRows(IWireProtocolReader* reader, const TTableSchema& schema);
+
+template <>
+auto ReadRows<TUnversionedRow>(IWireProtocolReader* reader, const TTableSchema& /*schema*/)
+{
+ return reader->ReadUnversionedRowset(true);
+}
+
+template <>
+auto ReadRows<TVersionedRow>(IWireProtocolReader* reader, const TTableSchema& schema)
+{
+ auto schemaData = IWireProtocolReader::GetSchemaData(schema, TColumnFilter());
+ return reader->ReadVersionedRowset(schemaData, true);
+}
+
+} // namespace
+
+template <class TRow>
+TIntrusivePtr<NApi::IRowset<TRow>> DeserializeRowset(
+ const NProto::TRowsetDescriptor& descriptor,
+ const TSharedRef& data)
+{
+ if (descriptor.rowset_format() != NApi::NRpcProxy::NProto::RF_YT_WIRE) {
+ THROW_ERROR_EXCEPTION("Unsupported rowset format %Qv",
+ NApi::NRpcProxy::NProto::ERowsetFormat_Name(descriptor.rowset_format()));
+ }
+
+ ValidateRowsetDescriptor(
+ descriptor,
+ NApi::NRpcProxy::CurrentWireFormatVersion,
+ TRowsetTraits<TRow>::Kind,
+ NApi::NRpcProxy::NProto::RF_YT_WIRE);
+
+ struct TDeserializedRowsetTag { };
+ auto reader = CreateWireProtocolReader(data, New<TRowBuffer>(TDeserializedRowsetTag()));
+
+ auto schema = DeserializeRowsetSchema(descriptor);
+ auto rows = ReadRows<TRow>(reader.get(), *schema);
+ return NApi::CreateRowset(std::move(schema), std::move(rows));
+}
+
+// Instantiate templates.
+template NApi::IUnversionedRowsetPtr DeserializeRowset(
+ const NProto::TRowsetDescriptor& descriptor,
+ const TSharedRef& data);
+template NApi::IVersionedRowsetPtr DeserializeRowset(
+ const NProto::TRowsetDescriptor& descriptor,
+ const TSharedRef& data);
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::vector<TSharedRef> SerializeRowset(
+ const NTableClient::TTableSchema& schema,
+ TRange<TTypeErasedRow> rows,
+ NProto::TRowsetDescriptor* descriptor,
+ bool versioned)
+{
+ if (versioned) {
+ return SerializeRowset(
+ schema,
+ ReinterpretCastRange<TVersionedRow>(rows),
+ descriptor);
+ } else {
+ return SerializeRowset(
+ schema,
+ ReinterpretCastRange<TUnversionedRow>(rows),
+ descriptor);
+ }
+}
+
+TIntrusivePtr<NApi::IRowset<TTypeErasedRow>> DeserializeRowset(
+ const NProto::TRowsetDescriptor& descriptor,
+ const TSharedRef& data,
+ bool versioned)
+{
+ if (versioned) {
+ auto rowset = DeserializeRowset<TVersionedRow>(descriptor, data);
+ // TODO(savrus): Get refcounted schema from rowset.
+ auto schema = DeserializeRowsetSchema(descriptor);
+ return CreateRowset(
+ std::move(schema),
+ ReinterpretCastRange<TTypeErasedRow>(rowset->GetRows()));
+ } else {
+ auto rowset = DeserializeRowset<TUnversionedRow>(descriptor, data);
+ // TODO(savrus): Get refcounted schema from rowset.
+ auto schema = DeserializeRowsetSchema(descriptor);
+ return CreateRowset(
+ std::move(schema),
+ ReinterpretCastRange<TTypeErasedRow>(rowset->GetRows()));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void SortByRegexes(std::vector<TString>& values, const std::vector<NRe2::TRe2Ptr>& regexes)
+{
+ auto valueToRank = [&] (const TString& value) -> size_t {
+ for (size_t index = 0; index < regexes.size(); ++index) {
+ if (NRe2::TRe2::FullMatch(NRe2::StringPiece(value), *regexes[index])) {
+ return index;
+ }
+ }
+ return regexes.size();
+ };
+ std::stable_sort(values.begin(), values.end(), [&] (const auto& lhs, const auto& rhs) {
+ return valueToRank(lhs) < valueToRank(rhs);
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/helpers.h b/yt/yt/client/api/rpc_proxy/helpers.h
new file mode 100644
index 0000000000..8c240cac5f
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/helpers.h
@@ -0,0 +1,280 @@
+#pragma once
+
+#include "public.h"
+
+#include <library/cpp/yt/memory/ref.h>
+
+#include <yt/yt/library/re2/re2.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.pb.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void SetTimeoutOptions(
+ NRpc::TClientRequest& request,
+ const NApi::TTimeoutOptions& options);
+
+[[noreturn]] void ThrowUnimplemented(const TString& method);
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+void ToProto(
+ NProto::TTransactionalOptions* proto,
+ const NApi::TTransactionalOptions& options);
+
+void ToProto(
+ NProto::TPrerequisiteOptions* proto,
+ const NApi::TPrerequisiteOptions& options);
+
+void ToProto(
+ NProto::TMasterReadOptions* proto,
+ const NApi::TMasterReadOptions& options);
+
+void ToProto(
+ NProto::TMutatingOptions* proto,
+ const NApi::TMutatingOptions& options);
+
+void ToProto(
+ NProto::TSuppressableAccessTrackingOptions* proto,
+ const NApi::TSuppressableAccessTrackingOptions& options);
+
+void ToProto(
+ NProto::TTabletRangeOptions* proto,
+ const NApi::TTabletRangeOptions& options);
+
+void ToProto(
+ NProto::TRetentionConfig* protoConfig,
+ const NTableClient::TRetentionConfig& config);
+
+void FromProto(
+ NTableClient::TRetentionConfig* config,
+ const NProto::TRetentionConfig& protoConfig);
+
+void ToProto(
+ NProto::TGetFileFromCacheResult* proto,
+ const NApi::TGetFileFromCacheResult& result);
+
+void FromProto(
+ NApi::TGetFileFromCacheResult* result,
+ const NProto::TGetFileFromCacheResult& proto);
+
+void ToProto(
+ NProto::TPutFileToCacheResult* proto,
+ const NApi::TPutFileToCacheResult& result);
+
+void FromProto(
+ NApi::TPutFileToCacheResult* result,
+ const NProto::TPutFileToCacheResult& proto);
+
+void ToProto(
+ NProto::TCheckPermissionResult* proto,
+ const NApi::TCheckPermissionResult& result);
+
+void FromProto(
+ NApi::TCheckPermissionResult* result,
+ const NProto::TCheckPermissionResult& proto);
+
+void ToProto(
+ NProto::TCheckPermissionByAclResult* proto,
+ const NApi::TCheckPermissionByAclResult& result);
+
+void FromProto(
+ NApi::TCheckPermissionByAclResult* result,
+ const NProto::TCheckPermissionByAclResult& proto);
+
+void ToProto(
+ NProto::TListOperationsResult* proto,
+ const NApi::TListOperationsResult& result);
+
+void FromProto(
+ NApi::TListOperationsResult* result,
+ const NProto::TListOperationsResult& proto);
+
+void ToProto(
+ NProto::TListJobsResult* proto,
+ const NApi::TListJobsResult& result);
+
+void FromProto(
+ NApi::TListJobsResult* result,
+ const NProto::TListJobsResult& proto);
+
+void ToProto(NProto::TColumnSchema* protoSchema, const NTableClient::TColumnSchema& schema);
+void FromProto(NTableClient::TColumnSchema* schema, const NProto::TColumnSchema& protoSchema);
+
+void ToProto(NProto::TTableSchema* protoSchema, const NTableClient::TTableSchema& schema);
+void FromProto(NTableClient::TTableSchema* schema, const NProto::TTableSchema& protoSchema);
+
+void ToProto(NProto::TTableSchema* protoSchema, const NTableClient::TTableSchemaPtr& schema);
+void FromProto(NTableClient::TTableSchemaPtr* schema, const NProto::TTableSchema& protoSchema);
+
+// Doesn't fill cell_config_version.
+void ToProto(
+ NProto::TTabletInfo* protoTabletInfo,
+ const NTabletClient::TTabletInfo& tabletInfo);
+// Doesn't fill TableId, UpdateTime and Owners.
+void FromProto(
+ NTabletClient::TTabletInfo* tabletInfo,
+ const NProto::TTabletInfo& protoTabletInfo);
+
+void ToProto(
+ NProto::TTabletReadOptions* protoOptions,
+ const NApi::TTabletReadOptionsBase& options);
+
+void ToProto(
+ NProto::TQueryStatistics* protoStatistics,
+ const NQueryClient::TQueryStatistics& statistics);
+
+void FromProto(
+ NQueryClient::TQueryStatistics* statistics,
+ const NProto::TQueryStatistics& protoStatistics);
+
+void ToProto(
+ NProto::TOperation* protoOperation,
+ const NApi::TOperation& operation);
+
+void FromProto(
+ NApi::TOperation* operation,
+ const NProto::TOperation& protoOperation);
+
+void ToProto(
+ NProto::TJob* protoJob,
+ const NApi::TJob& job);
+
+void FromProto(
+ NApi::TJob* job,
+ const NProto::TJob& protoJob);
+
+void ToProto(
+ NProto::TListJobsStatistics* protoStatistics,
+ const NApi::TListJobsStatistics& statistics);
+
+void FromProto(
+ NApi::TListJobsStatistics* statistics,
+ const NProto::TListJobsStatistics& protoStatistics);
+
+void ToProto(
+ NProto::TFetchChunkSpecConfig* protoFetchChunkSpecConfig,
+ const NChunkClient::TFetchChunkSpecConfigPtr& fetchChunkSpecConfig);
+
+void FromProto(
+ const NChunkClient::TFetchChunkSpecConfigPtr& fetchChunkSpecConfig,
+ const NProto::TFetchChunkSpecConfig& protoFetchChunkSpecConfig);
+
+void ToProto(
+ NProto::TFetcherConfig* protoFetcherConfig,
+ const NChunkClient::TFetcherConfigPtr& fetcherConfig);
+
+void FromProto(
+ const NChunkClient::TFetcherConfigPtr& fetcherConfig,
+ const NProto::TFetcherConfig& protoFetcherConfig);
+
+void ToProto(
+ NProto::TColumnarStatistics* protoStatistics,
+ const NTableClient::TColumnarStatistics& statistics);
+
+void FromProto(
+ NTableClient::TColumnarStatistics* statistics,
+ const NProto::TColumnarStatistics& protoStatistics);
+
+void ToProto(
+ NProto::TMultiTablePartition* protoMultiTablePartition,
+ const NApi::TMultiTablePartition& multiTablePartition);
+
+void FromProto(
+ NApi::TMultiTablePartition* multiTablePartition,
+ const NProto::TMultiTablePartition& protoMultiTablePartition);
+
+void FromProto(
+ NApi::TMultiTablePartitions* multiTablePartitions,
+ const NProto::TRspPartitionTables& protoRspPartitionTables);
+
+void ToProto(
+ NProto::TRowBatchReadOptions* proto,
+ const NQueueClient::TQueueRowBatchReadOptions& result);
+
+void FromProto(
+ NQueueClient::TQueueRowBatchReadOptions* result,
+ const NProto::TRowBatchReadOptions& proto);
+
+NProto::EOperationType ConvertOperationTypeToProto(
+ NScheduler::EOperationType operationType);
+
+NScheduler::EOperationType ConvertOperationTypeFromProto(
+ NProto::EOperationType proto);
+
+NProto::EOperationState ConvertOperationStateToProto(
+ NScheduler::EOperationState operationState);
+
+NScheduler::EOperationState ConvertOperationStateFromProto(
+ NProto::EOperationState proto);
+
+NProto::EJobType ConvertJobTypeToProto(
+ NJobTrackerClient::EJobType jobType);
+
+NJobTrackerClient::EJobType ConvertJobTypeFromProto(
+ NProto::EJobType proto);
+
+NProto::EJobState ConvertJobStateToProto(
+ NJobTrackerClient::EJobState jobState);
+
+NJobTrackerClient::EJobState ConvertJobStateFromProto(
+ NProto::EJobState proto);
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsRetriableError(const TError& error, bool retryProxyBanned = true);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateRowsetDescriptor(
+ const NProto::TRowsetDescriptor& descriptor,
+ int expectedVersion,
+ NProto::ERowsetKind expectedKind,
+ NProto::ERowsetFormat expectedFormat);
+
+std::vector<TSharedRef> SerializeRowset(
+ const NTableClient::TNameTablePtr& nameTable,
+ TRange<NTableClient::TUnversionedRow> rows,
+ NProto::TRowsetDescriptor* descriptor);
+
+template <class TRow>
+std::vector<TSharedRef> SerializeRowset(
+ const NTableClient::TTableSchema& schema,
+ TRange<TRow> rows,
+ NProto::TRowsetDescriptor* descriptor);
+
+template <class TRow>
+TIntrusivePtr<NApi::IRowset<TRow>> DeserializeRowset(
+ const NProto::TRowsetDescriptor& descriptor,
+ const TSharedRef& data);
+
+std::vector<TSharedRef> SerializeRowset(
+ const NTableClient::TTableSchema& schema,
+ TRange<NTableClient::TTypeErasedRow> rows,
+ NProto::TRowsetDescriptor* descriptor,
+ bool versioned);
+
+TIntrusivePtr<NApi::IRowset<NTableClient::TTypeErasedRow>> DeserializeRowset(
+ const NProto::TRowsetDescriptor& descriptor,
+ const TSharedRef& data,
+ bool versioned);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Invokes std::stable_sort reordering addresses by the index of the first regex they match;
+//! addresses not matching any regex are placed at the very end.
+void SortByRegexes(std::vector<TString>& values, const std::vector<NRe2::TRe2Ptr>& regexes);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/journal_reader.cpp b/yt/yt/client/api/rpc_proxy/journal_reader.cpp
new file mode 100644
index 0000000000..b5f82d4126
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/journal_reader.cpp
@@ -0,0 +1,73 @@
+#include "journal_reader.h"
+
+#include <yt/yt/client/api/journal_reader.h>
+
+#include <yt/yt/core/rpc/stream.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TJournalReader
+ : public IJournalReader
+{
+public:
+ explicit TJournalReader(
+ TApiServiceProxy::TReqReadJournalPtr request)
+ : Request_(std::move(request))
+ {
+ YT_VERIFY(Request_);
+ }
+
+ TFuture<void> Open() override
+ {
+ if (!OpenResult_) {
+ OpenResult_ = NRpc::CreateRpcClientInputStream(Request_)
+ .Apply(BIND([=, this, this_ = MakeStrong(this)] (const IAsyncZeroCopyInputStreamPtr& inputStream) {
+ Underlying_ = inputStream;
+ }));
+ }
+
+ return OpenResult_;
+ }
+
+ TFuture<std::vector<TSharedRef>> Read() override
+ {
+ ValidateOpened();
+
+ return Underlying_->Read().Apply(BIND ([] (const TSharedRef& packedRows) {
+ std::vector<TSharedRef> rows;
+ if (packedRows) {
+ UnpackRefs(packedRows, &rows);
+ }
+ return rows;
+ }));
+ }
+
+private:
+ const TApiServiceProxy::TReqReadJournalPtr Request_;
+
+ IAsyncZeroCopyInputStreamPtr Underlying_;
+ TFuture<void> OpenResult_;
+
+ void ValidateOpened()
+ {
+ if (!OpenResult_ || !OpenResult_.IsSet()) {
+ THROW_ERROR_EXCEPTION("Cannot read from an unopened journal reader");
+ }
+ OpenResult_.Get().ThrowOnError();
+ }
+};
+
+IJournalReaderPtr CreateJournalReader(
+ TApiServiceProxy::TReqReadJournalPtr request)
+{
+ return New<TJournalReader>(std::move(request));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/journal_reader.h b/yt/yt/client/api/rpc_proxy/journal_reader.h
new file mode 100644
index 0000000000..a90369ba62
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/journal_reader.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "api_service_proxy.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IJournalReaderPtr CreateJournalReader(
+ TApiServiceProxy::TReqReadJournalPtr request);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/journal_writer.cpp b/yt/yt/client/api/rpc_proxy/journal_writer.cpp
new file mode 100644
index 0000000000..5b66ab93f7
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/journal_writer.cpp
@@ -0,0 +1,91 @@
+#include "journal_writer.h"
+
+#include <yt/yt/client/api/journal_writer.h>
+
+#include <yt/yt/core/rpc/stream.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TJournalWriter
+ : public IJournalWriter
+{
+public:
+ TJournalWriter(
+ TApiServiceProxy::TReqWriteJournalPtr request)
+ : Request_(std::move(request))
+ {
+ YT_VERIFY(Request_);
+ }
+
+ TFuture<void> Open() override
+ {
+ ValidateNotClosed();
+
+ if (!OpenResult_) {
+ OpenResult_ = NRpc::CreateRpcClientOutputStream(Request_, true)
+ .Apply(BIND([=, this, this_ = MakeStrong(this)] (const IAsyncZeroCopyOutputStreamPtr& outputStream) {
+ Underlying_ = outputStream;
+ })).As<void>();
+ }
+
+ return OpenResult_;
+ }
+
+ TFuture<void> Write(TRange<TSharedRef> rows) override
+ {
+ ValidateOpened();
+ ValidateNotClosed();
+
+ if (rows.Empty()) {
+ return VoidFuture;
+ }
+
+ return Underlying_->Write(PackRefs(rows));
+ }
+
+ TFuture<void> Close() override
+ {
+ ValidateOpened();
+ ValidateNotClosed();
+
+ Closed_ = true;
+ return Underlying_->Close();
+ }
+
+private:
+ const TApiServiceProxy::TReqWriteJournalPtr Request_;
+
+ IAsyncZeroCopyOutputStreamPtr Underlying_;
+ TFuture<void> OpenResult_;
+ bool Closed_ = false;
+
+ void ValidateOpened()
+ {
+ if (!OpenResult_ || !OpenResult_.IsSet()) {
+ THROW_ERROR_EXCEPTION("Cannot write into an unopened journal writer");
+ }
+ OpenResult_.Get().ThrowOnError();
+ }
+
+ void ValidateNotClosed()
+ {
+ if (Closed_) {
+ THROW_ERROR_EXCEPTION("Journal writer is closed");
+ }
+ }
+};
+
+IJournalWriterPtr CreateJournalWriter(
+ TApiServiceProxy::TReqWriteJournalPtr request)
+{
+ return New<TJournalWriter>(std::move(request));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/journal_writer.h b/yt/yt/client/api/rpc_proxy/journal_writer.h
new file mode 100644
index 0000000000..cc9cf9dda5
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/journal_writer.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "api_service_proxy.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IJournalWriterPtr CreateJournalWriter(
+ TApiServiceProxy::TReqWriteJournalPtr request);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/private.h b/yt/yt/client/api/rpc_proxy/private.h
new file mode 100644
index 0000000000..78011d2768
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/private.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Parameters specific to sticky transactions.
+// The separate structure wrapped in |std::optional| helps to differentiate
+// between sticky and non-sticky transactions.
+struct TStickyTransactionParameters
+{
+ // Empty if not supported.
+ TString ProxyAddress;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TConnection)
+DECLARE_REFCOUNTED_CLASS(TClientBase)
+DECLARE_REFCOUNTED_CLASS(TClient)
+DECLARE_REFCOUNTED_CLASS(TTransaction)
+
+inline const NLogging::TLogger RpcProxyClientLogger("RpcProxyClient");
+
+////////////////////////////////////////////////////////////////////////////////
+
+THashMap<TString, TString> ParseProxyUrlAliasingRules(TString envConfig);
+void ApplyProxyUrlAliasingRules(
+ TString& url,
+ const std::optional<THashMap<TString, TString>>& proxyUrlAliasingRules = std::nullopt);
+TString NormalizeHttpProxyUrl(
+ TString url,
+ const std::optional<THashMap<TString, TString>>& proxyUrlAliasingRules = std::nullopt);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/protocol_version.h b/yt/yt/client/api/rpc_proxy/protocol_version.h
new file mode 100644
index 0000000000..8b75926232
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/protocol_version.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "protocol_version_variables.h"
diff --git a/yt/yt/client/api/rpc_proxy/protocol_version_variables.h.in b/yt/yt/client/api/rpc_proxy/protocol_version_variables.h.in
new file mode 100644
index 0000000000..f22e31d560
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/protocol_version_variables.h.in
@@ -0,0 +1,10 @@
+// We could inline this variables in .cpp file but `ymake' has disappointing bug:
+// bug https://st.yandex-team.ru/DEVTOOLS-4546.
+//
+// And compilation breaks if .cpp.in file includes any autogenerated file like .pb.h
+static constexpr int YTRpcProxyProtocolVersionMajor = @YT_RPC_PROXY_PROTOCOL_VERSION_MAJOR@;
+static constexpr int YTRpcProxyClientProtocolVersionMinor = @YT_RPC_PROXY_CLIENT_PROTOCOL_VERSION_MINOR@;
+static constexpr int YTRpcProxyServerProtocolVersionMinor = @YT_RPC_PROXY_SERVER_PROTOCOL_VERSION_MINOR@;
+
+// Feature versions
+static constexpr int YTRpcModifyRowsStrongLocksVersion = @YT_RPC_MODIFY_ROWS_STRONG_LOCKS_VERSION@;
diff --git a/yt/yt/client/api/rpc_proxy/public.cpp b/yt/yt/client/api/rpc_proxy/public.cpp
new file mode 100644
index 0000000000..8bc9c11a57
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/public.cpp
@@ -0,0 +1,12 @@
+#include "public.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString ApiServiceName = "ApiService";
+const TString DiscoveryServiceName = "DiscoveryService";
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/public.h b/yt/yt/client/api/rpc_proxy/public.h
new file mode 100644
index 0000000000..33f580a444
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/public.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <yt/yt/client/api/public.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TConnectionOptions;
+
+DECLARE_REFCOUNTED_STRUCT(IRowStreamEncoder)
+DECLARE_REFCOUNTED_STRUCT(IRowStreamDecoder)
+
+DECLARE_REFCOUNTED_CLASS(TConnectionConfig)
+
+extern const TString ApiServiceName;
+extern const TString DiscoveryServiceName;
+
+constexpr int CurrentWireFormatVersion = 1;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// COMPAT(babenko): get rid of this in favor of NRpc::EErrorCode::PeerBanned
+YT_DEFINE_ERROR_ENUM(
+ ((ProxyBanned) (2100))
+);
+
+DEFINE_ENUM(ERpcProxyFeature,
+ ((GetInSyncWithoutKeys)(0))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EAddressType,
+ ((InternalRpc) (0))
+ ((MonitoringHttp) (1))
+ ((TvmOnlyInternalRpc) (2))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/row_stream.cpp b/yt/yt/client/api/rpc_proxy/row_stream.cpp
new file mode 100644
index 0000000000..4fac08b20a
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/row_stream.cpp
@@ -0,0 +1,138 @@
+#include "row_stream.h"
+
+#include <library/cpp/yt/memory/ref.h>
+#include <yt/yt/core/misc/protobuf_helpers.h>
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::tuple<TSharedRef, TMutableRef> SerializeRowStreamBlockEnvelope(
+ i64 payloadSize,
+ const NApi::NRpcProxy::NProto::TRowsetDescriptor& descriptor,
+ const NApi::NRpcProxy::NProto::TRowsetStatistics* statistics)
+{
+ /*
+ Block with statistics:
+ count: i32 = 2
+ length-of-part-1: i64
+ part-1:
+ count: i32 = 2
+ length-of-part-1-1: i64
+ part-1-1: descriptor
+ length-of-part-1-2: i64
+ part-1-2: payload
+ length-of-part-2: i64
+ part-2: statistics
+
+ Block without statistics:
+ count: i32 = 2
+ length-of-part-1: i64
+ part-1: descriptor
+ length-of-part-2: i64
+ part-2: payload
+ */
+
+ i64 totalSize = 0;
+ totalSize += sizeof (i32); // partCount
+ totalSize += sizeof (i64) * 2; // partLength * 2
+ totalSize += descriptor.ByteSizeLong(); // descriptor
+ totalSize += payloadSize;
+ if (statistics) {
+ totalSize += sizeof (i32); // partCount
+ totalSize += sizeof (i64) * 2; // partLength * 2
+ totalSize += statistics->ByteSize();
+ }
+
+ struct TSerializedRowStreamBlockTag { };
+ auto block = TSharedMutableRef::Allocate<TSerializedRowStreamBlockTag>(totalSize, {.InitializeStorage = false});
+
+ char* current = block.Begin();
+
+ auto writeInt32 = [&] (i32 value) {
+ *reinterpret_cast<i32*>(current) = value;
+ current += sizeof (i32);
+ };
+
+ auto writeInt64 = [&] (i64 value) {
+ *reinterpret_cast<i64*>(current) = value;
+ current += sizeof (i64);
+ };
+
+ TMutableRef payloadRef;
+ auto skipPayload = [&] {
+ payloadRef = TMutableRef(current, current + payloadSize);
+ current += payloadSize;
+ };
+
+ auto writeProto = [&] (const auto& proto) {
+ current = reinterpret_cast<char*>(proto.SerializeWithCachedSizesToArray(reinterpret_cast<ui8*>(current)));
+ };
+
+ if (statistics) {
+ writeInt32(2); // partCount
+ writeInt64(sizeof (i32) + 2 * sizeof (i64) + descriptor.ByteSizeLong() + payloadSize); // partLength
+ }
+
+ writeInt32(2); // partCount
+ writeInt64(descriptor.ByteSizeLong()); // partLength
+ writeProto(descriptor);
+ writeInt64(payloadSize); // partLength
+ skipPayload();
+
+ if (statistics) {
+ writeInt64(statistics->ByteSize()); // partLength
+ writeProto(*statistics);
+ }
+
+ YT_VERIFY(current == block.End());
+
+ return std::make_tuple(std::move(block), payloadRef);
+}
+
+TSharedRef DeserializeRowStreamBlockEnvelope(
+ const TSharedRef& block,
+ NApi::NRpcProxy::NProto::TRowsetDescriptor* descriptor,
+ NApi::NRpcProxy::NProto::TRowsetStatistics* statistics)
+{
+ TSharedRef rowsRef;
+ if (statistics) {
+ auto parts = UnpackRefs(block);
+ if (parts.size() != 2) {
+ THROW_ERROR_EXCEPTION(
+ "Error deserializing row stream: expected %v packed refs, got %v",
+ 2,
+ parts.size());
+ }
+
+ rowsRef = parts[0];
+ const auto& statisticsRef = parts[1];
+ if (!TryDeserializeProto(statistics, statisticsRef)) {
+ THROW_ERROR_EXCEPTION("Error deserializing rowset statistics");
+ }
+ } else {
+ rowsRef = block;
+ }
+
+ auto parts = UnpackRefs(rowsRef);
+ if (parts.size() != 2) {
+ THROW_ERROR_EXCEPTION(
+ "Error deserializing row stream: expected %v packed refs, got %v",
+ 2,
+ parts.size());
+ }
+
+ const auto& descriptorRef = parts[0];
+ const auto& payloadRef = parts[1];
+
+ if (!TryDeserializeProto(descriptor, descriptorRef)) {
+ THROW_ERROR_EXCEPTION("Error deserializing rowset descriptor");
+ }
+
+ return payloadRef;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/row_stream.h b/yt/yt/client/api/rpc_proxy/row_stream.h
new file mode 100644
index 0000000000..6b3d449894
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/row_stream.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.pb.h>
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <library/cpp/yt/memory/ref.h>
+#include <yt/yt/core/misc/range.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IRowStreamEncoder
+ : public virtual TRefCounted
+{
+ virtual TSharedRef Encode(
+ const NTableClient::IUnversionedRowBatchPtr& batch,
+ const NProto::TRowsetStatistics* statistics) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IRowStreamEncoder)
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IRowStreamDecoder
+ : public virtual TRefCounted
+{
+ virtual NTableClient::IUnversionedRowBatchPtr Decode(
+ const TSharedRef& payloadRef,
+ const NProto::TRowsetDescriptor& descriptor) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IRowStreamDecoder)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A helper for formatting row stream blocks.
+/*!
+ * Serializes #descriptor and (if given) #statistics.
+ *
+ * \returns a tuple consisting of
+ * 1) the whole row stream block
+ * 2) the ref where payload (of size #payloadSize) must be placed
+ */
+std::tuple<TSharedRef, TMutableRef> SerializeRowStreamBlockEnvelope(
+ i64 payloadSize,
+ const NApi::NRpcProxy::NProto::TRowsetDescriptor& descriptor,
+ const NApi::NRpcProxy::NProto::TRowsetStatistics* statistics);
+
+//! A helper for parsing row stream blocks.
+/*!
+ * \returns the paylod slice
+ */
+TSharedRef DeserializeRowStreamBlockEnvelope(
+ const TSharedRef& block,
+ NApi::NRpcProxy::NProto::TRowsetDescriptor* descriptor,
+ NApi::NRpcProxy::NProto::TRowsetStatistics* statistics);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/table_mount_cache.cpp b/yt/yt/client/api/rpc_proxy/table_mount_cache.cpp
new file mode 100644
index 0000000000..8fa52f652d
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/table_mount_cache.cpp
@@ -0,0 +1,144 @@
+#include "table_mount_cache.h"
+#include "api_service_proxy.h"
+#include "helpers.h"
+
+#include <yt/yt/client/api/config.h>
+
+#include <yt/yt/client/tablet_client/config.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache_detail.h>
+
+#include <yt/yt/client/table_client/helpers.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NRpc;
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NYPath;
+
+using NYT::FromProto;
+
+///////////////////////////////////////////////////////////////////////////////
+
+class TTableMountCache
+ : public TTableMountCacheBase
+{
+public:
+ TTableMountCache(
+ TTableMountCacheConfigPtr config,
+ IChannelPtr channel,
+ const NLogging::TLogger& logger,
+ TDuration timeout)
+ : TTableMountCacheBase(std::move(config), logger)
+ , Channel_(std::move(channel))
+ , Timeout_(timeout)
+ { }
+
+private:
+ TFuture<TTableMountInfoPtr> DoGet(const NYPath::TYPath& path, bool /*isPeriodicUpdate*/) noexcept override
+ {
+ YT_LOG_DEBUG("Requesting table mount info (Path: %v)",
+ path);
+
+ TApiServiceProxy proxy(Channel_);
+ proxy.SetDefaultTimeout(Timeout_);
+
+ auto req = proxy.GetTableMountInfo();
+ req->set_path(path);
+
+ return req->Invoke().Apply(
+ BIND([=, this, this_ = MakeStrong(this)] (const TApiServiceProxy::TRspGetTableMountInfoPtr& rsp) {
+ auto tableInfo = New<TTableMountInfo>();
+ tableInfo->Path = path;
+ auto tableId = FromProto<NObjectClient::TObjectId>(rsp->table_id());
+ tableInfo->TableId = tableId;
+
+ auto primarySchema = NYT::FromProto<NTableClient::TTableSchemaPtr>(rsp->schema());
+ tableInfo->Schemas[ETableSchemaKind::Primary] = primarySchema;
+ tableInfo->Schemas[ETableSchemaKind::Write] = primarySchema->ToWrite();
+ tableInfo->Schemas[ETableSchemaKind::VersionedWrite] = primarySchema->ToVersionedWrite();
+ tableInfo->Schemas[ETableSchemaKind::Delete] = primarySchema->ToDelete();
+ tableInfo->Schemas[ETableSchemaKind::Query] = primarySchema->ToQuery();
+ tableInfo->Schemas[ETableSchemaKind::Lookup] = primarySchema->ToLookup();
+ tableInfo->Schemas[ETableSchemaKind::PrimaryWithTabletIndex] = primarySchema->WithTabletIndex();
+
+ tableInfo->UpstreamReplicaId = FromProto<TTableReplicaId>(rsp->upstream_replica_id());
+ tableInfo->Dynamic = rsp->dynamic();
+ tableInfo->NeedKeyEvaluation = primarySchema->HasComputedColumns();
+
+ if (rsp->has_physical_path()) {
+ tableInfo->PhysicalPath = rsp->physical_path();
+ } else {
+ tableInfo->PhysicalPath = path;
+ }
+
+ for (const auto& protoTabletInfo : rsp->tablets()) {
+ auto tabletInfo = New<NTabletClient::TTabletInfo>();
+ FromProto(tabletInfo.Get(), protoTabletInfo);
+ tabletInfo->TableId = tableId;
+ tabletInfo->UpdateTime = Now();
+ tabletInfo->Owners.push_back(MakeWeak(tableInfo));
+
+ tabletInfo = TabletInfoCache_.Insert(std::move(tabletInfo));
+ tableInfo->Tablets.push_back(tabletInfo);
+ if (tabletInfo->State == ETabletState::Mounted) {
+ tableInfo->MountedTablets.push_back(tabletInfo);
+ }
+ }
+
+ for (const auto& protoReplicaInfo : rsp->replicas()) {
+ auto replicaInfo = New<TTableReplicaInfo>();
+ replicaInfo->ReplicaId = FromProto<TTableReplicaId>(protoReplicaInfo.replica_id());
+ replicaInfo->ClusterName = protoReplicaInfo.cluster_name();
+ replicaInfo->ReplicaPath = protoReplicaInfo.replica_path();
+ replicaInfo->Mode = ETableReplicaMode(protoReplicaInfo.mode());
+ tableInfo->Replicas.push_back(replicaInfo);
+ }
+
+ if (tableInfo->IsSorted()) {
+ tableInfo->LowerCapBound = MinKey();
+ tableInfo->UpperCapBound = MaxKey();
+ } else {
+ tableInfo->LowerCapBound = MakeUnversionedOwningRow(static_cast<int>(0));
+ tableInfo->UpperCapBound = MakeUnversionedOwningRow(static_cast<int>(tableInfo->Tablets.size()));
+ }
+
+ YT_LOG_DEBUG("Table mount info received (Path: %v, TableId: %v, TabletCount: %v, Dynamic: %v)",
+ path,
+ tableInfo->TableId,
+ tableInfo->Tablets.size(),
+ tableInfo->Dynamic);
+
+ return tableInfo;
+ }));
+ }
+
+private:
+ const IChannelPtr Channel_;
+ const TDuration Timeout_;
+
+ void InvalidateTable(const TTableMountInfoPtr& tableInfo) override
+ {
+ InvalidateValue(tableInfo->Path, tableInfo);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ITableMountCachePtr CreateTableMountCache(
+ TTableMountCacheConfigPtr config,
+ IChannelPtr channel,
+ const NLogging::TLogger& logger,
+ TDuration timeout)
+{
+ return New<TTableMountCache>(
+ std::move(config),
+ std::move(channel),
+ logger,
+ timeout);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/table_mount_cache.h b/yt/yt/client/api/rpc_proxy/table_mount_cache.h
new file mode 100644
index 0000000000..d312583fdd
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/table_mount_cache.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTabletClient::ITableMountCachePtr CreateTableMountCache(
+ TTableMountCacheConfigPtr config,
+ NRpc::IChannelPtr channel,
+ const NLogging::TLogger& logger,
+ TDuration timeout);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/table_reader.cpp b/yt/yt/client/api/rpc_proxy/table_reader.cpp
new file mode 100644
index 0000000000..2bb1410874
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/table_reader.cpp
@@ -0,0 +1,257 @@
+#include "table_reader.h"
+#include "helpers.h"
+#include "row_stream.h"
+#include "wire_row_stream.h"
+
+#include <yt/yt/client/api/rowset.h>
+#include <yt/yt/client/api/table_reader.h>
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/data_statistics.pb.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/unversioned_reader.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/rpc/stream.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NConcurrency;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableReader
+ : public ITableReader
+{
+public:
+ TTableReader(
+ IAsyncZeroCopyInputStreamPtr underlying,
+ i64 startRowIndex,
+ const std::vector<TString>& omittedInaccessibleColumns,
+ TTableSchemaPtr schema,
+ const NApi::NRpcProxy::NProto::TRowsetStatistics& statistics)
+ : Underlying_ (std::move(underlying))
+ , StartRowIndex_(startRowIndex)
+ , TableSchema_(std::move(schema))
+ , OmittedInaccessibleColumns_(omittedInaccessibleColumns)
+ , Decoder_(CreateWireRowStreamDecoder(NameTable_))
+ {
+ YT_VERIFY(Underlying_);
+
+ ApplyReaderStatistics(statistics);
+
+ RowsWithStatisticsFuture_ = GetRowsWithStatistics();
+ ReadyEvent_.TrySetFrom(RowsWithStatisticsFuture_);
+ }
+
+ i64 GetStartRowIndex() const override
+ {
+ return StartRowIndex_;
+ }
+
+ i64 GetTotalRowCount() const override
+ {
+ return TotalRowCount_;
+ }
+
+ NChunkClient::NProto::TDataStatistics GetDataStatistics() const override
+ {
+ auto dataStatistics = DataStatistics_;
+ dataStatistics.set_row_count(RowCount_);
+ dataStatistics.set_data_weight(DataWeight_);
+
+ return dataStatistics;
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ return ReadyEvent_;
+ }
+
+ IUnversionedRowBatchPtr Read(const TRowBatchReadOptions& options) override
+ {
+ StoredRows_.clear();
+
+ if (!ReadyEvent_.IsSet() || !ReadyEvent_.Get().IsOK()) {
+ return CreateEmptyUnversionedRowBatch();
+ }
+
+ if (!Finished_) {
+ ReadyEvent_ = NewPromise<void>();
+ }
+
+ std::vector<TUnversionedRow> rows;
+ rows.reserve(options.MaxRowsPerRead);
+ i64 dataWeight = 0;
+
+ while (RowsWithStatisticsFuture_ &&
+ RowsWithStatisticsFuture_.IsSet() &&
+ RowsWithStatisticsFuture_.Get().IsOK() &&
+ !Finished_ &&
+ std::ssize(rows) < options.MaxRowsPerRead &&
+ dataWeight < options.MaxDataWeightPerRead)
+ {
+ const auto& currentRows = RowsWithStatisticsFuture_.Get().Value().Rows;
+ const auto& currentStatistics = RowsWithStatisticsFuture_.Get().Value().Statistics;
+
+ if (currentRows.Empty()) {
+ ReadyEvent_.Set();
+ Finished_ = true;
+ ApplyReaderStatistics(currentStatistics);
+ continue;
+ }
+
+ while (CurrentRowsOffset_ < std::ssize(currentRows) &&
+ std::ssize(rows) < options.MaxRowsPerRead &&
+ dataWeight < options.MaxDataWeightPerRead)
+ {
+ auto row = currentRows[CurrentRowsOffset_++];
+ rows.push_back(row);
+ dataWeight += GetDataWeight(row);
+ }
+
+ StoredRows_.push_back(currentRows);
+ ApplyReaderStatistics(currentStatistics);
+
+ if (CurrentRowsOffset_ == std::ssize(currentRows)) {
+ RowsWithStatisticsFuture_ = GetRowsWithStatistics();
+ CurrentRowsOffset_ = 0;
+ }
+ }
+
+ RowCount_ += rows.size();
+ DataWeight_ += dataWeight;
+
+ ReadyEvent_.TrySetFrom(RowsWithStatisticsFuture_);
+ return rows.empty()
+ ? nullptr
+ : CreateBatchFromUnversionedRows(MakeSharedRange(std::move(rows), MakeStrong(this)));
+ }
+
+ const TNameTablePtr& GetNameTable() const override
+ {
+ return NameTable_;
+ }
+
+ const TTableSchemaPtr& GetTableSchema() const override
+ {
+ return TableSchema_;
+ }
+
+ const std::vector<TString>& GetOmittedInaccessibleColumns() const override
+ {
+ return OmittedInaccessibleColumns_;
+ }
+
+private:
+ struct TRowsWithStatistics
+ {
+ TSharedRange<TUnversionedRow> Rows;
+ NApi::NRpcProxy::NProto::TRowsetStatistics Statistics;
+ };
+
+ const IAsyncZeroCopyInputStreamPtr Underlying_;
+ const i64 StartRowIndex_;
+ const TTableSchemaPtr TableSchema_;
+ const std::vector<TString> OmittedInaccessibleColumns_;
+
+ const TNameTablePtr NameTable_ = New<TNameTable>();
+ const IRowStreamDecoderPtr Decoder_;
+
+ NChunkClient::NProto::TDataStatistics DataStatistics_;
+ i64 TotalRowCount_;
+
+ i64 RowCount_ = 0;
+ i64 DataWeight_ = 0;
+
+ TNameTableToSchemaIdMapping IdMapping_;
+
+ TPromise<void> ReadyEvent_ = NewPromise<void>();
+
+ std::vector<TSharedRange<TUnversionedRow>> StoredRows_;
+ TFuture<TRowsWithStatistics> RowsWithStatisticsFuture_;
+ i64 CurrentRowsOffset_ = 0;
+
+ bool Finished_ = false;
+
+ void ApplyReaderStatistics(const NApi::NRpcProxy::NProto::TRowsetStatistics& statistics)
+ {
+ TotalRowCount_ = statistics.total_row_count();
+ DataStatistics_ = statistics.data_statistics();
+ }
+
+ TFuture<TRowsWithStatistics> GetRowsWithStatistics()
+ {
+ return Underlying_->Read()
+ .Apply(BIND([this, weakThis = MakeWeak(this)] (const TSharedRef& block) {
+ auto this_ = weakThis.Lock();
+ if (!this_) {
+ THROW_ERROR_EXCEPTION(NYT::EErrorCode::Canceled, "Reader destroyed");
+ }
+
+ NApi::NRpcProxy::NProto::TRowsetDescriptor descriptor;
+ NApi::NRpcProxy::NProto::TRowsetStatistics statistics;
+ auto payloadRef = DeserializeRowStreamBlockEnvelope(block, &descriptor, &statistics);
+
+ ValidateRowsetDescriptor(
+ descriptor,
+ NApi::NRpcProxy::CurrentWireFormatVersion,
+ NApi::NRpcProxy::NProto::RK_UNVERSIONED,
+ NApi::NRpcProxy::NProto::ERowsetFormat::RF_YT_WIRE);
+
+ auto decoder = GetOrCreateDecoder(descriptor.rowset_format());
+ auto batch = decoder->Decode(payloadRef, descriptor);
+ auto rows = batch->MaterializeRows();
+ auto rowsWithStatistics = TRowsWithStatistics{
+ std::move(rows),
+ std::move(statistics)
+ };
+
+ if (rowsWithStatistics.Rows.Empty()) {
+ return ExpectEndOfStream(Underlying_).Apply(BIND([=] () {
+ return std::move(rowsWithStatistics);
+ }));
+ }
+ return MakeFuture(std::move(rowsWithStatistics));
+ }));
+ }
+
+ IRowStreamDecoderPtr GetOrCreateDecoder(NApi::NRpcProxy::NProto::ERowsetFormat format)
+ {
+ if (format != NApi::NRpcProxy::NProto::RF_YT_WIRE) {
+ THROW_ERROR_EXCEPTION("Unsupported rowset format %Qv",
+ NApi::NRpcProxy::NProto::ERowsetFormat_Name(format));
+ }
+ return Decoder_;
+ }
+};
+
+TFuture<ITableReaderPtr> CreateTableReader(TApiServiceProxy::TReqReadTablePtr request)
+{
+ return NRpc::CreateRpcClientInputStream(std::move(request))
+ .Apply(BIND([=] (const IAsyncZeroCopyInputStreamPtr& inputStream) {
+ return inputStream->Read().Apply(BIND([=] (const TSharedRef& metaRef) {
+ NApi::NRpcProxy::NProto::TRspReadTableMeta meta;
+ if (!TryDeserializeProto(&meta, metaRef)) {
+ THROW_ERROR_EXCEPTION("Failed to deserialize table reader meta information");
+ }
+
+ i64 startRowIndex = meta.start_row_index();
+ auto omittedInaccessibleColumns = FromProto<std::vector<TString>>(
+ meta.omitted_inaccessible_columns());
+ auto schema = NYT::FromProto<TTableSchemaPtr>(meta.schema());
+ return New<TTableReader>(
+ inputStream,
+ startRowIndex,
+ std::move(omittedInaccessibleColumns),
+ std::move(schema),
+ meta.statistics());
+ })).As<ITableReaderPtr>();
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/table_reader.h b/yt/yt/client/api/rpc_proxy/table_reader.h
new file mode 100644
index 0000000000..cc84ef2ab0
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/table_reader.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "api_service_proxy.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<ITableReaderPtr> CreateTableReader(
+ TApiServiceProxy::TReqReadTablePtr request);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/table_writer.cpp b/yt/yt/client/api/rpc_proxy/table_writer.cpp
new file mode 100644
index 0000000000..40e696bf05
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/table_writer.cpp
@@ -0,0 +1,106 @@
+#include "table_writer.h"
+#include "helpers.h"
+#include "row_stream.h"
+#include "wire_row_stream.h"
+
+#include <yt/yt/client/api/table_writer.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/rpc/stream.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NConcurrency;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableWriter
+ : public ITableWriter
+{
+public:
+ TTableWriter(
+ IAsyncZeroCopyOutputStreamPtr underlying,
+ TTableSchemaPtr schema)
+ : Underlying_(std::move(underlying))
+ , Schema_(std::move(schema))
+ , Encoder_(CreateWireRowStreamEncoder(NameTable_))
+ {
+ YT_VERIFY(Underlying_);
+ NameTable_->SetEnableColumnNameValidation();
+ }
+
+ bool Write(TRange<TUnversionedRow> rows) override
+ {
+ YT_VERIFY(!Closed_);
+ YT_VERIFY(ReadyEvent_.IsSet() && ReadyEvent_.Get().IsOK());
+
+ auto batch = CreateBatchFromUnversionedRows(TSharedRange<TUnversionedRow>(rows, nullptr));
+
+ auto block = Encoder_->Encode(batch, nullptr);
+
+ ReadyEvent_ = NewPromise<void>();
+ ReadyEvent_.TrySetFrom(Underlying_->Write(std::move(block)));
+
+ return ReadyEvent_.IsSet() && ReadyEvent_.Get().IsOK();
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ return ReadyEvent_;
+ }
+
+ TFuture<void> Close() override
+ {
+ YT_VERIFY(!Closed_);
+ Closed_ = true;
+
+ return Underlying_->Close();
+ }
+
+ const TNameTablePtr& GetNameTable() const override
+ {
+ return NameTable_;
+ }
+
+ const TTableSchemaPtr& GetSchema() const override
+ {
+ return Schema_;
+ }
+
+private:
+ const IAsyncZeroCopyOutputStreamPtr Underlying_;
+ const TTableSchemaPtr Schema_;
+
+ const TNameTablePtr NameTable_ = New<TNameTable>();
+ const IRowStreamEncoderPtr Encoder_;
+
+ TPromise<void> ReadyEvent_ = MakePromise<void>(TError());
+ bool Closed_ = false;
+};
+
+TFuture<ITableWriterPtr> CreateTableWriter(
+ TApiServiceProxy::TReqWriteTablePtr request)
+{
+ auto schema = New<TTableSchema>();
+ return NRpc::CreateRpcClientOutputStream(
+ std::move(request),
+ BIND ([=] (const TSharedRef& metaRef) {
+ NApi::NRpcProxy::NProto::TWriteTableMeta meta;
+ if (!TryDeserializeProto(&meta, metaRef)) {
+ THROW_ERROR_EXCEPTION("Failed to deserialize schema for table writer");
+ }
+
+ FromProto(schema.Get(), meta.schema());
+ }))
+ .Apply(BIND([=] (const IAsyncZeroCopyOutputStreamPtr& outputStream) {
+ return New<TTableWriter>(outputStream, std::move(schema));
+ })).As<ITableWriterPtr>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/table_writer.h b/yt/yt/client/api/rpc_proxy/table_writer.h
new file mode 100644
index 0000000000..ce67e7ed0d
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/table_writer.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "api_service_proxy.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<ITableWriterPtr> CreateTableWriter(
+ TApiServiceProxy::TReqWriteTablePtr request);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/timestamp_provider.cpp b/yt/yt/client/api/rpc_proxy/timestamp_provider.cpp
new file mode 100644
index 0000000000..faec95465e
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/timestamp_provider.cpp
@@ -0,0 +1,69 @@
+#include "timestamp_provider.h"
+#include "api_service_proxy.h"
+#include "connection_impl.h"
+
+#include <yt/yt/client/transaction_client/timestamp_provider_base.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NTransactionClient;
+using namespace NObjectClient;
+using namespace NRpc;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTimestampProvider
+ : public TTimestampProviderBase
+{
+public:
+ TTimestampProvider(
+ IChannelPtr channel,
+ TDuration rpcTimeout,
+ TDuration latestTimestampUpdatePeriod,
+ TCellTag clockClusterTag)
+ : TTimestampProviderBase(latestTimestampUpdatePeriod)
+ , Channel_(std::move(channel))
+ , RpcTimeout_(rpcTimeout)
+ , ClockClusterTag_(clockClusterTag)
+ { }
+
+private:
+ const IChannelPtr Channel_;
+ const TDuration RpcTimeout_;
+ const TCellTag ClockClusterTag_;
+
+ TFuture<NTransactionClient::TTimestamp> DoGenerateTimestamps(int count) override
+ {
+ TApiServiceProxy proxy(Channel_);
+
+ auto req = proxy.GenerateTimestamps();
+ req->SetTimeout(RpcTimeout_);
+ req->set_count(count);
+ if (ClockClusterTag_ != InvalidCellTag) {
+ req->set_clock_cluster_tag(ToProto<int>(ClockClusterTag_));
+ }
+
+ return req->Invoke().Apply(BIND([] (const TApiServiceProxy::TRspGenerateTimestampsPtr& rsp) {
+ return rsp->timestamp();
+ }));
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTransactionClient::ITimestampProviderPtr CreateTimestampProvider(
+ IChannelPtr channel,
+ TDuration rpcTimeout,
+ TDuration latestTimestampUpdatePeriod,
+ TCellTag clockClusterTag)
+{
+ return New<TTimestampProvider>(
+ std::move(channel),
+ rpcTimeout,
+ latestTimestampUpdatePeriod,
+ clockClusterTag);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/timestamp_provider.h b/yt/yt/client/api/rpc_proxy/timestamp_provider.h
new file mode 100644
index 0000000000..b3abf287b5
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/timestamp_provider.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "private.h"
+
+#include <yt/yt/core/rpc/public.h>
+
+#include <yt/yt/client/transaction_client/public.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTransactionClient::ITimestampProviderPtr CreateTimestampProvider(
+ NRpc::IChannelPtr channel,
+ TDuration rpcTimeout,
+ TDuration latestTimestampUpdatePeriod,
+ NObjectClient::TCellTag clockClusterTag = NObjectClient::InvalidCellTag);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/transaction.cpp b/yt/yt/client/api/rpc_proxy/transaction.cpp
new file mode 100644
index 0000000000..9e0c7423a1
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/transaction.cpp
@@ -0,0 +1,45 @@
+#include "transaction.h"
+
+#include "transaction_impl.h"
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::ITransactionPtr CreateTransaction(
+ TConnectionPtr connection,
+ TClientPtr client,
+ NRpc::IChannelPtr channel,
+ NTransactionClient::TTransactionId id,
+ NTransactionClient::TTimestamp startTimestamp,
+ NTransactionClient::ETransactionType type,
+ NTransactionClient::EAtomicity atomicity,
+ NTransactionClient::EDurability durability,
+ TDuration timeout,
+ bool pingAncestors,
+ std::optional<TDuration> pingPeriod,
+ std::optional<TStickyTransactionParameters> stickyParameters,
+ i64 sequenceNumberSourceId,
+ TStringBuf capitalizedCreationReason)
+{
+ return New<TTransaction>(
+ std::move(connection),
+ std::move(client),
+ std::move(channel),
+ id,
+ startTimestamp,
+ type,
+ atomicity,
+ durability,
+ timeout,
+ pingAncestors,
+ pingPeriod,
+ std::move(stickyParameters),
+ sequenceNumberSourceId,
+ capitalizedCreationReason);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/transaction.h b/yt/yt/client/api/rpc_proxy/transaction.h
new file mode 100644
index 0000000000..bb75aedc27
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/transaction.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "private.h"
+
+#include <yt/yt/core/rpc/public.h>
+
+#include <yt/yt/client/api/public.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::ITransactionPtr CreateTransaction(
+ TConnectionPtr connection,
+ TClientPtr client,
+ NRpc::IChannelPtr channel,
+ NTransactionClient::TTransactionId id,
+ NTransactionClient::TTimestamp startTimestamp,
+ NTransactionClient::ETransactionType type,
+ NTransactionClient::EAtomicity atomicity,
+ NTransactionClient::EDurability durability,
+ TDuration timeout,
+ bool pingAncestors,
+ std::optional<TDuration> pingPeriod,
+ std::optional<TStickyTransactionParameters> stickyParameters,
+ i64 sequenceNumberSourceId,
+ TStringBuf capitalizedCreationReason);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/transaction_impl-inl.h b/yt/yt/client/api/rpc_proxy/transaction_impl-inl.h
new file mode 100644
index 0000000000..d175aacbbd
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/transaction_impl-inl.h
@@ -0,0 +1,30 @@
+#ifndef TRANSACTION_IMPL_INL_H_
+#error "Direct inclusion of this file is not allowed, include transaction_impl.h"
+// For the sake of sane code completion.
+#include "transaction_impl.h"
+#endif
+#undef TRANSACTION_IMPL_INL_H_
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+T TTransaction::PatchTransactionId(const T& options)
+{
+ auto copiedOptions = options;
+ copiedOptions.TransactionId = Id_;
+ return copiedOptions;
+}
+
+template <class T>
+T TTransaction::PatchTransactionTimestamp(const T& options)
+{
+ auto copiedOptions = options;
+ copiedOptions.Timestamp = StartTimestamp_;
+ return copiedOptions;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/transaction_impl.cpp b/yt/yt/client/api/rpc_proxy/transaction_impl.cpp
new file mode 100644
index 0000000000..20dadc0449
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/transaction_impl.cpp
@@ -0,0 +1,1075 @@
+#include "transaction_impl.h"
+#include "client_impl.h"
+#include "helpers.h"
+#include "config.h"
+#include "private.h"
+
+#include <yt/yt/client/transaction_client/helpers.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+
+#include <yt/yt/client/api/transaction.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NConcurrency;
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NTransactionClient;
+using namespace NObjectClient;
+using namespace NTableClient;
+using namespace NCypressClient;
+using namespace NApi;
+using namespace NYTree;
+using namespace NYPath;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTransaction::TTransaction(
+ TConnectionPtr connection,
+ TClientPtr client,
+ NRpc::IChannelPtr channel,
+ TTransactionId id,
+ TTimestamp startTimestamp,
+ ETransactionType type,
+ EAtomicity atomicity,
+ EDurability durability,
+ TDuration timeout,
+ bool pingAncestors,
+ std::optional<TDuration> pingPeriod,
+ std::optional<TStickyTransactionParameters> stickyParameters,
+ i64 sequenceNumberSourceId,
+ TStringBuf capitalizedCreationReason)
+ : Connection_(std::move(connection))
+ , Client_(std::move(client))
+ , Channel_(std::move(channel))
+ , Id_(id)
+ , StartTimestamp_(startTimestamp)
+ , Type_(type)
+ , Atomicity_(atomicity)
+ , Durability_(durability)
+ , Timeout_(timeout)
+ , PingAncestors_(pingAncestors)
+ , PingPeriod_(pingPeriod)
+ , StickyProxyAddress_(stickyParameters ? std::move(stickyParameters->ProxyAddress) : TString())
+ , SequenceNumberSourceId_(sequenceNumberSourceId)
+ , Logger(RpcProxyClientLogger.WithTag("TransactionId: %v, %v",
+ Id_,
+ Connection_->GetLoggingTag()))
+ , Proxy_(Channel_)
+{
+ const auto& config = Connection_->GetConfig();
+ Proxy_.SetDefaultTimeout(config->RpcTimeout);
+ Proxy_.SetDefaultRequestCodec(config->RequestCodec);
+ Proxy_.SetDefaultResponseCodec(config->ResponseCodec);
+ Proxy_.SetDefaultEnableLegacyRpcCodecs(config->EnableLegacyRpcCodecs);
+
+ YT_LOG_DEBUG("%v (Type: %v, StartTimestamp: %v, Atomicity: %v, "
+ "Durability: %v, Timeout: %v, PingAncestors: %v, PingPeriod: %v, Sticky: %v, StickyProxyAddress: %v)",
+ capitalizedCreationReason,
+ GetType(),
+ GetStartTimestamp(),
+ GetAtomicity(),
+ GetDurability(),
+ GetTimeout(),
+ PingAncestors_,
+ PingPeriod_,
+ /*sticky*/ stickyParameters.has_value(),
+ StickyProxyAddress_);
+
+ // TODO(babenko): don't run periodic pings if client explicitly disables them in options
+ RunPeriodicPings();
+}
+
+IConnectionPtr TTransaction::GetConnection()
+{
+ return Connection_;
+}
+
+IClientPtr TTransaction::GetClient() const
+{
+ return Client_;
+}
+
+TTransactionId TTransaction::GetId() const
+{
+ return Id_;
+}
+
+TTimestamp TTransaction::GetStartTimestamp() const
+{
+ return StartTimestamp_;
+}
+
+ETransactionType TTransaction::GetType() const
+{
+ return Type_;
+}
+
+EAtomicity TTransaction::GetAtomicity() const
+{
+ return Atomicity_;
+}
+
+EDurability TTransaction::GetDurability() const
+{
+ return Durability_;
+}
+
+TDuration TTransaction::GetTimeout() const
+{
+ return Timeout_;
+}
+
+void TTransaction::RegisterAlienTransaction(const ITransactionPtr& transaction)
+{
+ {
+ auto guard = Guard(SpinLock_);
+
+ if (State_ != ETransactionState::Active) {
+ THROW_ERROR_EXCEPTION(
+ NTransactionClient::EErrorCode::InvalidTransactionState,
+ "Transaction %v is in %Qlv state",
+ GetId(),
+ State_);
+ }
+
+ if (GetType() != ETransactionType::Tablet) {
+ THROW_ERROR_EXCEPTION(
+ NTransactionClient::EErrorCode::MalformedAlienTransaction,
+ "Transaction %v is of type %Qlv and hence does not allow alien transactions",
+ GetId(),
+ GetType());
+ }
+
+ if (GetId() != transaction->GetId()) {
+ THROW_ERROR_EXCEPTION(
+ NTransactionClient::EErrorCode::MalformedAlienTransaction,
+ "Transaction id mismatch: native %v, alien %v",
+ GetId(),
+ transaction->GetId());
+ }
+
+ AlienTransactions_.push_back(transaction);
+ }
+
+ YT_LOG_DEBUG("Alien transaction registered (AlienConnection: {%v})",
+ transaction->GetConnection()->GetLoggingTag());
+}
+
+TFuture<void> TTransaction::Ping(const NApi::TTransactionPingOptions& /*options*/)
+{
+ return SendPing();
+}
+
+void TTransaction::Detach()
+{
+ {
+ auto guard = Guard(SpinLock_);
+
+ if (State_ == ETransactionState::Detached) {
+ return;
+ }
+
+ State_ = ETransactionState::Detached;
+ }
+
+ YT_LOG_DEBUG("Transaction detached");
+
+ auto req = Proxy_.DetachTransaction();
+ ToProto(req->mutable_transaction_id(), GetId());
+ // Fire-and-forget.
+ YT_UNUSED_FUTURE(req->Invoke());
+}
+
+void TTransaction::SubscribeCommitted(const TCommittedHandler& handler)
+{
+ Committed_.Subscribe(handler);
+}
+
+void TTransaction::UnsubscribeCommitted(const TCommittedHandler& handler)
+{
+ Committed_.Unsubscribe(handler);
+}
+
+void TTransaction::SubscribeAborted(const TAbortedHandler& handler)
+{
+ Aborted_.Subscribe(handler);
+}
+
+void TTransaction::UnsubscribeAborted(const TAbortedHandler& handler)
+{
+ Aborted_.Unsubscribe(handler);
+}
+
+TFuture<TTransactionFlushResult> TTransaction::Flush()
+{
+ std::vector<TFuture<void>> futures;
+ {
+ auto guard = Guard(SpinLock_);
+
+ if (State_ != ETransactionState::Active) {
+ return MakeFuture<TTransactionFlushResult>(TError(
+ NTransactionClient::EErrorCode::InvalidTransactionState,
+ "Transaction %v is in %Qlv state",
+ GetId(),
+ State_));
+ }
+
+ if (!AlienTransactions_.empty()) {
+ return MakeFuture<TTransactionFlushResult>(TError(
+ NTransactionClient::EErrorCode::AlienTransactionsForbidden,
+ "Cannot flush transaction %v since it has %v alien transaction(s)",
+ GetId(),
+ AlienTransactions_.size()));
+ }
+
+ State_ = ETransactionState::Flushing;
+ futures = FlushModifyRowsRequests();
+ }
+
+ YT_LOG_DEBUG("Flushing transaction");
+
+ return AllSucceeded(futures)
+ .Apply(
+ BIND([=, this, this_ = MakeStrong(this)] {
+ auto req = Proxy_.FlushTransaction();
+ ToProto(req->mutable_transaction_id(), GetId());
+ return req->Invoke();
+ }))
+ .Apply(
+ BIND([=, this, this_ = MakeStrong(this)] (const TApiServiceProxy::TErrorOrRspFlushTransactionPtr& rspOrError) -> TErrorOr<TTransactionFlushResult> {
+ {
+ auto guard = Guard(SpinLock_);
+ if (rspOrError.IsOK() && State_ == ETransactionState::Flushing) {
+ State_ = ETransactionState::Flushed;
+ } else if (!rspOrError.IsOK()) {
+ YT_LOG_DEBUG(rspOrError, "Error flushing transaction");
+ YT_UNUSED_FUTURE(DoAbort(&guard));
+ THROW_ERROR_EXCEPTION("Error flushing transaction %v",
+ GetId())
+ << rspOrError;
+ }
+ }
+
+ const auto& rsp = rspOrError.Value();
+ TTransactionFlushResult result{
+ .ParticipantCellIds = FromProto<std::vector<TCellId>>(rsp->participant_cell_ids())
+ };
+
+ YT_LOG_DEBUG("Transaction flushed (ParticipantCellIds: %v)",
+ result.ParticipantCellIds);
+
+ return result;
+ }));
+}
+
+TFuture<TTransactionCommitResult> TTransaction::Commit(const TTransactionCommitOptions& options)
+{
+ std::vector<TFuture<void>> futures;
+ std::vector<NApi::ITransactionPtr> alienTransactions;
+ {
+ auto guard = Guard(SpinLock_);
+
+ if (State_ != ETransactionState::Active) {
+ return MakeFuture<TTransactionCommitResult>(TError(
+ NTransactionClient::EErrorCode::InvalidTransactionState,
+ "Transaction %v is in %Qlv state",
+ GetId(),
+ State_));
+ }
+
+ State_ = ETransactionState::Committing;
+ futures = FlushModifyRowsRequests();
+ alienTransactions = std::move(AlienTransactions_);
+ }
+
+ YT_LOG_DEBUG("Committing transaction (AlienTransactionCount: %v)",
+ alienTransactions.size());
+
+ for (const auto& transaction : alienTransactions) {
+ futures.push_back(
+ transaction->Flush().Apply(
+ BIND([=, this, this_ = MakeStrong(this)] (const TErrorOr<TTransactionFlushResult>& resultOrError) {
+ THROW_ERROR_EXCEPTION_IF_FAILED(resultOrError, "Error flushing alien transaction");
+
+ const auto& result = resultOrError.Value();
+
+ YT_LOG_DEBUG("Alien transaction flushed (ParticipantCellIds: %v, AlienConnection: {%v})",
+ result.ParticipantCellIds,
+ transaction->GetConnection()->GetLoggingTag());
+
+ for (auto cellId : result.ParticipantCellIds) {
+ AdditionalParticipantCellIds_.insert(cellId);
+ }
+ })));
+ }
+
+ return AllSucceeded(futures)
+ .Apply(
+ BIND([=, this, this_ = MakeStrong(this)] {
+ auto req = Proxy_.CommitTransaction();
+ ToProto(req->mutable_transaction_id(), GetId());
+ ToProto(req->mutable_additional_participant_cell_ids(), AdditionalParticipantCellIds_);
+ ToProto(req->mutable_prerequisite_options(), options);
+ return req->Invoke();
+ }))
+ .Apply(
+ BIND([=, this, this_ = MakeStrong(this)] (const TErrorOr<TApiServiceProxy::TRspCommitTransactionPtr>& rspOrError) {
+ {
+ auto guard = Guard(SpinLock_);
+ if (rspOrError.IsOK() && State_ == ETransactionState::Committing) {
+ State_ = ETransactionState::Committed;
+ } else if (!rspOrError.IsOK()) {
+ YT_UNUSED_FUTURE(DoAbort(&guard));
+ THROW_ERROR_EXCEPTION("Error committing transaction %v",
+ GetId())
+ << rspOrError;
+ }
+ }
+
+ for (const auto& transaction : alienTransactions) {
+ transaction->Detach();
+ }
+
+ const auto& rsp = rspOrError.Value();
+ TTransactionCommitResult result{
+ .PrimaryCommitTimestamp = rsp->primary_commit_timestamp(),
+ .CommitTimestamps = FromProto<NHiveClient::TTimestampMap>(rsp->commit_timestamps())
+ };
+
+ YT_LOG_DEBUG("Transaction committed (CommitTimestamps: %v)",
+ result.CommitTimestamps);
+
+ Committed_.Fire();
+
+ return result;
+ }));
+}
+
+TFuture<void> TTransaction::Abort(const TTransactionAbortOptions& options)
+{
+ auto guard = Guard(SpinLock_);
+
+ if (State_ == ETransactionState::Committed || State_ == ETransactionState::Detached) {
+ return MakeFuture<void>(TError(
+ NTransactionClient::EErrorCode::InvalidTransactionState,
+ "Cannot abort since transaction %v is in %Qlv state",
+ GetId(),
+ State_));
+ }
+
+ return DoAbort(&guard, options);
+}
+
+void TTransaction::ModifyRows(
+ const TYPath& path,
+ TNameTablePtr nameTable,
+ TSharedRange<TRowModification> modifications,
+ const TModifyRowsOptions& options)
+{
+ ValidateTabletTransactionId(GetId());
+
+ for (const auto& modification : modifications) {
+ // TODO(sandello): handle versioned rows
+ YT_VERIFY(
+ modification.Type == ERowModificationType::Write ||
+ modification.Type == ERowModificationType::Delete ||
+ modification.Type == ERowModificationType::WriteAndLock);
+ }
+
+ auto reqSequenceNumber = ModifyRowsRequestSequenceCounter_.fetch_add(1);
+
+ auto req = Proxy_.ModifyRows();
+ req->set_sequence_number(reqSequenceNumber);
+ req->set_sequence_number_source_id(SequenceNumberSourceId_);
+ ToProto(req->mutable_transaction_id(), GetId());
+ req->set_path(path);
+ req->set_require_sync_replica(options.RequireSyncReplica);
+ ToProto(req->mutable_upstream_replica_id(), options.UpstreamReplicaId);
+ req->set_allow_missing_key_columns(options.AllowMissingKeyColumns);
+
+ std::vector<TUnversionedRow> rows;
+ rows.reserve(modifications.Size());
+
+ bool usedStrongLocks = false;
+ for (const auto& modification : modifications) {
+ auto mask = modification.Locks;
+ for (int index = 0; index < TLegacyLockMask::MaxCount; ++index) {
+ if (mask.Get(index) > MaxOldLockType) {
+ THROW_ERROR_EXCEPTION("New locks are not supported in RPC client yet")
+ << TErrorAttribute("lock_index", index)
+ << TErrorAttribute("lock_type", mask.Get(index));
+ }
+ usedStrongLocks |= mask.Get(index) == ELockType::SharedStrong;
+ }
+ }
+
+ if (usedStrongLocks) {
+ req->Header().set_protocol_version_minor(YTRpcModifyRowsStrongLocksVersion);
+ }
+
+ for (const auto& modification : modifications) {
+ rows.emplace_back(modification.Row);
+ req->add_row_modification_types(static_cast<NProto::ERowModificationType>(modification.Type));
+ if (usedStrongLocks) {
+ auto locks = modification.Locks;
+ YT_VERIFY(!locks.HasNewLocks());
+ req->add_row_locks(locks.ToLegacyMask().GetBitmap());
+ } else {
+ TLegacyLockBitmap bitmap = 0;
+ for (int index = 0; index < TLegacyLockMask::MaxCount; ++index) {
+ if (modification.Locks.Get(index) == ELockType::SharedWeak) {
+ bitmap |= 1u << index;
+ }
+ }
+ req->add_row_read_locks(bitmap);
+ }
+ }
+
+ req->Attachments() = SerializeRowset(
+ nameTable,
+ MakeRange(rows),
+ req->mutable_rowset_descriptor());
+
+ TFuture<void> future;
+ const auto& config = Connection_->GetConfig();
+ if (config->ModifyRowsBatchCapacity == 0) {
+ ValidateActive();
+ future = req->Invoke().As<void>();
+ } else {
+ YT_LOG_DEBUG("Pushing a subrequest into a batch modify rows request (SubrequestAttachmentCount: 1+%v)",
+ req->Attachments().size());
+
+ auto reqBody = SerializeProtoToRef(*req);
+
+ {
+ auto guard = Guard(SpinLock_);
+
+ DoValidateActive();
+
+ if (!BatchModifyRowsRequest_) {
+ BatchModifyRowsRequest_ = Proxy_.BatchModifyRows();
+ ToProto(BatchModifyRowsRequest_->mutable_transaction_id(), GetId());
+ }
+
+ BatchModifyRowsRequest_->Attachments().push_back(reqBody);
+ BatchModifyRowsRequest_->Attachments().insert(
+ BatchModifyRowsRequest_->Attachments().end(),
+ req->Attachments().begin(),
+ req->Attachments().end());
+ BatchModifyRowsRequest_->add_part_counts(req->Attachments().size());
+
+ if (BatchModifyRowsRequest_->part_counts_size() == config->ModifyRowsBatchCapacity) {
+ future = InvokeBatchModifyRowsRequest();
+ }
+ }
+ }
+
+ if (future) {
+ future
+ .Subscribe(BIND([=, this, this_ = MakeStrong(this)](const TError& error) {
+ if (!error.IsOK()) {
+ YT_LOG_DEBUG(error, "Error sending row modifications");
+ YT_UNUSED_FUTURE(Abort());
+ }
+ }));
+
+ {
+ auto guard = Guard(SpinLock_);
+ BatchModifyRowsFutures_.push_back(std::move(future));
+ }
+ }
+}
+
+TFuture<ITransactionPtr> TTransaction::StartTransaction(
+ ETransactionType type,
+ const TTransactionStartOptions& options)
+{
+ ValidateActive();
+ return Client_->StartTransaction(
+ type,
+ PatchTransactionId(options));
+}
+
+TFuture<IUnversionedRowsetPtr> TTransaction::LookupRows(
+ const TYPath& path,
+ TNameTablePtr nameTable,
+ const TSharedRange<TLegacyKey>& keys,
+ const TLookupRowsOptions& options)
+{
+ ValidateActive();
+ return Client_->LookupRows(
+ path,
+ std::move(nameTable),
+ keys,
+ PatchTransactionTimestamp(options));
+}
+
+TFuture<IVersionedRowsetPtr> TTransaction::VersionedLookupRows(
+ const TYPath& path,
+ TNameTablePtr nameTable,
+ const TSharedRange<TLegacyKey>& keys,
+ const TVersionedLookupRowsOptions& options)
+{
+ ValidateActive();
+ return Client_->VersionedLookupRows(
+ path,
+ std::move(nameTable),
+ keys,
+ PatchTransactionTimestamp(options));
+}
+
+TFuture<std::vector<IUnversionedRowsetPtr>> TTransaction::MultiLookup(
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options)
+{
+ ValidateActive();
+ return Client_->MultiLookup(
+ subrequests,
+ PatchTransactionTimestamp(options));
+}
+
+TFuture<TSelectRowsResult> TTransaction::SelectRows(
+ const TString& query,
+ const TSelectRowsOptions& options)
+{
+ ValidateActive();
+ return Client_->SelectRows(
+ query,
+ PatchTransactionTimestamp(options));
+}
+
+TFuture<NYson::TYsonString> TTransaction::ExplainQuery(
+ const TString& query,
+ const TExplainQueryOptions& options)
+{
+ ValidateActive();
+ return Client_->ExplainQuery(
+ query,
+ PatchTransactionTimestamp(options));
+}
+
+TFuture<TPullRowsResult> TTransaction::PullRows(
+ const TYPath& path,
+ const TPullRowsOptions& options)
+{
+ ValidateActive();
+ return Client_->PullRows(
+ path,
+ options);
+}
+
+TFuture<ITableReaderPtr> TTransaction::CreateTableReader(
+ const TRichYPath& path,
+ const NApi::TTableReaderOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateTableReader(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<ITableWriterPtr> TTransaction::CreateTableWriter(
+ const TRichYPath& path,
+ const NApi::TTableWriterOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateTableWriter(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<NYson::TYsonString> TTransaction::GetNode(
+ const TYPath& path,
+ const TGetNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->GetNode(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::SetNode(
+ const TYPath& path,
+ const NYson::TYsonString& value,
+ const TSetNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->SetNode(
+ path,
+ value,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::MultisetAttributesNode(
+ const TYPath& path,
+ const IMapNodePtr& attributes,
+ const TMultisetAttributesNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->MultisetAttributesNode(
+ path,
+ attributes,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::RemoveNode(
+ const TYPath& path,
+ const TRemoveNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->RemoveNode(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<NYson::TYsonString> TTransaction::ListNode(
+ const TYPath& path,
+ const TListNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->ListNode(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<TNodeId> TTransaction::CreateNode(
+ const TYPath& path,
+ EObjectType type,
+ const TCreateNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateNode(
+ path,
+ type,
+ PatchTransactionId(options));
+}
+
+TFuture<TLockNodeResult> TTransaction::LockNode(
+ const TYPath& path,
+ ELockMode mode,
+ const TLockNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->LockNode(
+ path,
+ mode,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::UnlockNode(
+ const NYPath::TYPath& path,
+ const NApi::TUnlockNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->UnlockNode(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<TNodeId> TTransaction::CopyNode(
+ const TYPath& srcPath,
+ const TYPath& dstPath,
+ const TCopyNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->CopyNode(
+ srcPath,
+ dstPath,
+ PatchTransactionId(options));
+}
+
+TFuture<TNodeId> TTransaction::MoveNode(
+ const TYPath& srcPath,
+ const TYPath& dstPath,
+ const TMoveNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->MoveNode(
+ srcPath,
+ dstPath,
+ PatchTransactionId(options));
+}
+
+TFuture<TNodeId> TTransaction::LinkNode(
+ const TYPath& srcPath,
+ const TYPath& dstPath,
+ const TLinkNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->LinkNode(
+ srcPath,
+ dstPath,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::ConcatenateNodes(
+ const std::vector<TRichYPath>& srcPaths,
+ const TRichYPath& dstPath,
+ const TConcatenateNodesOptions& options)
+{
+ ValidateActive();
+ return Client_->ConcatenateNodes(
+ srcPaths,
+ dstPath,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::ExternalizeNode(
+ const TYPath& path,
+ TCellTag cellTag,
+ const TExternalizeNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->ExternalizeNode(
+ path,
+ cellTag,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::InternalizeNode(
+ const TYPath& path,
+ const TInternalizeNodeOptions& options)
+{
+ ValidateActive();
+ return Client_->InternalizeNode(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<bool> TTransaction::NodeExists(
+ const TYPath& path,
+ const TNodeExistsOptions& options)
+{
+ ValidateActive();
+ return Client_->NodeExists(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<TObjectId> TTransaction::CreateObject(
+ EObjectType type,
+ const TCreateObjectOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateObject(type, options);
+}
+
+TFuture<IFileReaderPtr> TTransaction::CreateFileReader(
+ const TYPath& path,
+ const TFileReaderOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateFileReader(
+ path,
+ PatchTransactionId(options));
+}
+
+IFileWriterPtr TTransaction::CreateFileWriter(
+ const TRichYPath& path,
+ const TFileWriterOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateFileWriter(
+ path,
+ PatchTransactionId(options));
+}
+
+IJournalReaderPtr TTransaction::CreateJournalReader(
+ const TYPath& path,
+ const TJournalReaderOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateJournalReader(
+ path,
+ PatchTransactionId(options));
+}
+
+IJournalWriterPtr TTransaction::CreateJournalWriter(
+ const TYPath& path,
+ const TJournalWriterOptions& options)
+{
+ ValidateActive();
+ return Client_->CreateJournalWriter(
+ path,
+ PatchTransactionId(options));
+}
+
+TFuture<void> TTransaction::DoAbort(
+ TGuard<NThreading::TSpinLock>* guard,
+ const TTransactionAbortOptions& /*options*/)
+{
+ VERIFY_SPINLOCK_AFFINITY(SpinLock_);
+
+ if (State_ == ETransactionState::Aborting || State_ == ETransactionState::Aborted) {
+ return AbortPromise_.ToFuture();
+ }
+
+ YT_LOG_DEBUG("Aborting transaction");
+
+ State_ = ETransactionState::Aborting;
+
+ auto alienTransactions = AlienTransactions_;
+
+ guard->Release();
+
+ auto req = Proxy_.AbortTransaction();
+ ToProto(req->mutable_transaction_id(), GetId());
+
+ AbortPromise_.TrySetFrom(req->Invoke().Apply(
+ BIND([=, this, this_ = MakeStrong(this)] (const TApiServiceProxy::TErrorOrRspAbortTransactionPtr& rspOrError) {
+ {
+ auto guard = Guard(SpinLock_);
+
+ if (State_ != ETransactionState::Aborting) {
+ YT_LOG_DEBUG(rspOrError, "Transaction is no longer aborting, abort response ignored");
+ return;
+ }
+
+ if (rspOrError.IsOK()) {
+ YT_LOG_DEBUG("Transaction aborted");
+ } else if (rspOrError.FindMatching(NTransactionClient::EErrorCode::NoSuchTransaction)) {
+ YT_LOG_DEBUG("Transaction has expired or was already aborted, ignored");
+ } else {
+ YT_LOG_DEBUG(rspOrError, "Error aborting transaction, considered detached");
+ State_ = ETransactionState::Detached;
+ THROW_ERROR_EXCEPTION("Error aborting transaction %v",
+ GetId())
+ << rspOrError;
+ }
+
+ State_ = ETransactionState::Aborted;
+ }
+
+ Aborted_.Fire(TError("Transaction aborted by user request"));
+ })));
+
+ for (const auto& transaction : alienTransactions) {
+ YT_UNUSED_FUTURE(transaction->Abort());
+ }
+
+ return AbortPromise_.ToFuture();
+}
+
+TFuture<void> TTransaction::SendPing()
+{
+ YT_LOG_DEBUG("Pinging transaction");
+
+ auto req = Proxy_.PingTransaction();
+ ToProto(req->mutable_transaction_id(), GetId());
+ req->set_ping_ancestors(PingAncestors_);
+
+ return req->Invoke().Apply(
+ BIND([=, this, this_ = MakeStrong(this)] (const TApiServiceProxy::TErrorOrRspPingTransactionPtr& rspOrError) {
+ if (rspOrError.IsOK()) {
+ YT_LOG_DEBUG("Transaction pinged");
+ } else if (rspOrError.FindMatching(NTransactionClient::EErrorCode::NoSuchTransaction)) {
+ // Hard error.
+ YT_LOG_DEBUG("Transaction has expired or was aborted");
+
+ bool fireAborted = false;
+ {
+ auto guard = Guard(SpinLock_);
+ if (State_ != ETransactionState::Committed &&
+ State_ != ETransactionState::Flushed &&
+ State_ != ETransactionState::FlushedModifications &&
+ State_ != ETransactionState::Aborted &&
+ State_ != ETransactionState::Detached)
+ {
+ State_ = ETransactionState::Aborted;
+ fireAborted = true;
+ }
+ }
+
+ auto error = TError(
+ NTransactionClient::EErrorCode::NoSuchTransaction,
+ "Transaction %v has expired or was aborted",
+ GetId());
+
+ if (fireAborted) {
+ AbortPromise_.TrySet();
+ Aborted_.Fire(error);
+ }
+
+ THROW_ERROR(error);
+ } else {
+ // Soft error.
+ YT_LOG_DEBUG(rspOrError, "Error pinging transaction");
+ THROW_ERROR_EXCEPTION("Error pinging transaction %v",
+ GetId())
+ << rspOrError;
+ }
+ }));
+}
+
+void TTransaction::RunPeriodicPings()
+{
+ if (!PingPeriod_) {
+ return;
+ }
+
+ if (!IsPingableState()) {
+ return;
+ }
+
+ SendPing().Subscribe(BIND([=, this, this_ = MakeStrong(this)] (const TError& error) {
+ if (!IsPingableState()) {
+ return;
+ }
+
+ if (error.FindMatching(NYT::EErrorCode::Timeout)) {
+ RunPeriodicPings();
+ return;
+ }
+
+ YT_LOG_DEBUG("Transaction ping scheduled");
+
+ TDelayedExecutor::Submit(
+ BIND(&TTransaction::RunPeriodicPings, MakeWeak(this)),
+ *PingPeriod_);
+ }));
+}
+
+bool TTransaction::IsPingableState()
+{
+ auto guard = Guard(SpinLock_);
+ return
+ State_ == ETransactionState::Active ||
+ State_ == ETransactionState::Flushing ||
+ State_ == ETransactionState::Flushed ||
+ State_ == ETransactionState::FlushingModifications ||
+ State_ == ETransactionState::FlushedModifications ||
+ State_ == ETransactionState::Committing;
+}
+
+void TTransaction::ValidateActive()
+{
+ auto guard = Guard(SpinLock_);
+ DoValidateActive();
+}
+
+void TTransaction::DoValidateActive()
+{
+ VERIFY_SPINLOCK_AFFINITY(SpinLock_);
+ if (State_ != ETransactionState::Active) {
+ THROW_ERROR_EXCEPTION(
+ NTransactionClient::EErrorCode::InvalidTransactionState,
+ "Transaction %v is not active",
+ GetId());
+ }
+}
+
+TApiServiceProxy::TReqBatchModifyRowsPtr TTransaction::CreateBatchModifyRowsRequest()
+{
+ auto req = Proxy_.BatchModifyRows();
+ ToProto(req->mutable_transaction_id(), GetId());
+ return req;
+}
+
+TFuture<void> TTransaction::InvokeBatchModifyRowsRequest()
+{
+ VERIFY_SPINLOCK_AFFINITY(SpinLock_);
+ YT_VERIFY(BatchModifyRowsRequest_);
+
+ TApiServiceProxy::TReqBatchModifyRowsPtr batchRequest;
+ batchRequest.Swap(BatchModifyRowsRequest_);
+ if (batchRequest->part_counts_size() == 0) {
+ return VoidFuture;
+ }
+
+ YT_LOG_DEBUG("Invoking a batch modify rows request (Subrequests: %v)",
+ batchRequest->part_counts_size());
+
+ return batchRequest->Invoke().As<void>();
+}
+
+std::vector<TFuture<void>> TTransaction::FlushModifyRowsRequests()
+{
+ VERIFY_SPINLOCK_AFFINITY(SpinLock_);
+
+ auto futures = std::move(BatchModifyRowsFutures_);
+ if (BatchModifyRowsRequest_) {
+ futures.push_back(InvokeBatchModifyRowsRequest());
+ }
+ return futures;
+}
+
+TTransactionStartOptions TTransaction::PatchTransactionId(const TTransactionStartOptions& options)
+{
+ auto copiedOptions = options;
+ copiedOptions.ParentId = GetId();
+ return copiedOptions;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString& TTransaction::GetStickyProxyAddress() const
+{
+ return StickyProxyAddress_;
+}
+
+TFuture<void> TTransaction::FlushModifications()
+{
+ std::vector<TFuture<void>> futures;
+ {
+ auto guard = Guard(SpinLock_);
+
+ if (State_ != ETransactionState::Active) {
+ THROW_ERROR_EXCEPTION(
+ NTransactionClient::EErrorCode::InvalidTransactionState,
+ "Transaction %v is in %Qlv state",
+ GetId(),
+ State_);
+ }
+
+ if (!AlienTransactions_.empty()) {
+ return MakeFuture<void>(TError(
+ NTransactionClient::EErrorCode::AlienTransactionsForbidden,
+ "Cannot flush transaction %v modifications since it has %v alien transaction(s)",
+ GetId(),
+ AlienTransactions_.size()));
+ }
+
+ State_ = ETransactionState::FlushingModifications;
+ futures = FlushModifyRowsRequests();
+ }
+
+ YT_LOG_DEBUG("Flushing transaction modifications");
+
+ return AllSucceeded(futures)
+ .Apply(BIND([this, this_ = MakeStrong(this)] (const TError& rspOrError) {
+ {
+ auto guard = Guard(SpinLock_);
+ if (rspOrError.IsOK() && State_ == ETransactionState::FlushingModifications) {
+ State_ = ETransactionState::FlushedModifications;
+ } else if (!rspOrError.IsOK()) {
+ YT_LOG_DEBUG(rspOrError, "Error flushing transaction modifications");
+ YT_UNUSED_FUTURE(DoAbort(&guard));
+ THROW_ERROR_EXCEPTION("Error flushing transaction %v modifications",
+ GetId())
+ << rspOrError;
+ }
+ }
+
+ YT_LOG_DEBUG("Transaction modifications flushed");
+
+ ModificationsFlushed_.Fire();
+
+ return TError();
+ }));
+}
+
+void TTransaction::SubscribeModificationsFlushed(const TModificationsFlushedHandler& handler)
+{
+ ModificationsFlushed_.Subscribe(handler);
+}
+
+void TTransaction::UnsubscribeModificationsFlushed(const TModificationsFlushedHandler& handler)
+{
+ ModificationsFlushed_.Unsubscribe(handler);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/rpc_proxy/transaction_impl.h b/yt/yt/client/api/rpc_proxy/transaction_impl.h
new file mode 100644
index 0000000000..415b0b4686
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/transaction_impl.h
@@ -0,0 +1,292 @@
+#pragma once
+
+#include "client_base.h"
+
+#include <yt/yt/client/api/transaction.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ETransactionState,
+ (Active)
+ (Committing)
+ (Committed)
+ (Flushing)
+ (Flushed)
+ (FlushingModifications)
+ (FlushedModifications)
+ (Aborting)
+ (Aborted)
+ (Detached)
+);
+
+class TTransaction
+ : public NApi::ITransaction
+{
+public:
+ TTransaction(
+ TConnectionPtr connection,
+ TClientPtr client,
+ NRpc::IChannelPtr channel,
+ NTransactionClient::TTransactionId id,
+ NTransactionClient::TTimestamp startTimestamp,
+ NTransactionClient::ETransactionType type,
+ NTransactionClient::EAtomicity atomicity,
+ NTransactionClient::EDurability durability,
+ TDuration timeout,
+ bool pingAncestors,
+ std::optional<TDuration> pingPeriod,
+ std::optional<TStickyTransactionParameters> stickyParameters,
+ i64 sequenceNumberSourceId,
+ TStringBuf capitalizedCreationReason);
+
+ // ITransaction implementation.
+ NApi::IConnectionPtr GetConnection() override;
+ NApi::IClientPtr GetClient() const override;
+
+ NTransactionClient::ETransactionType GetType() const override;
+ NTransactionClient::TTransactionId GetId() const override;
+ NTransactionClient::TTimestamp GetStartTimestamp() const override;
+ NTransactionClient::EAtomicity GetAtomicity() const override;
+ NTransactionClient::EDurability GetDurability() const override;
+ TDuration GetTimeout() const override;
+
+ TFuture<void> Ping(const NApi::TTransactionPingOptions& options = {}) override;
+ TFuture<NApi::TTransactionFlushResult> Flush() override;
+ TFuture<NApi::TTransactionCommitResult> Commit(const NApi::TTransactionCommitOptions&) override;
+ TFuture<void> Abort(const NApi::TTransactionAbortOptions& options = {}) override;
+ void Detach() override;
+ void RegisterAlienTransaction(const ITransactionPtr& transaction) override;
+
+ void SubscribeCommitted(const TCommittedHandler& handler) override;
+ void UnsubscribeCommitted(const TCommittedHandler& handler) override;
+
+ void SubscribeAborted(const TAbortedHandler& handler) override;
+ void UnsubscribeAborted(const TAbortedHandler& handler) override;
+
+ void ModifyRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<NApi::TRowModification> modifications,
+ const NApi::TModifyRowsOptions& options) override;
+
+ // IClientBase implementation.
+ TFuture<NApi::ITransactionPtr> StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const NApi::TTransactionStartOptions& options) override;
+
+ TFuture<NApi::IUnversionedRowsetPtr> LookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const NApi::TLookupRowsOptions& options) override;
+
+ TFuture<NApi::IVersionedRowsetPtr> VersionedLookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const NApi::TVersionedLookupRowsOptions& options) override;
+
+ TFuture<std::vector<IUnversionedRowsetPtr>> MultiLookup(
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options) override;
+
+ TFuture<NApi::TSelectRowsResult> SelectRows(
+ const TString& query,
+ const NApi::TSelectRowsOptions& options) override;
+
+ TFuture<NYson::TYsonString> ExplainQuery(
+ const TString& query,
+ const NApi::TExplainQueryOptions& options) override;
+
+ TFuture<NApi::TPullRowsResult> PullRows(
+ const NYPath::TYPath& path,
+ const NApi::TPullRowsOptions& options) override;
+
+ TFuture<ITableReaderPtr> CreateTableReader(
+ const NYPath::TRichYPath& path,
+ const NApi::TTableReaderOptions& options) override;
+
+ TFuture<ITableWriterPtr> CreateTableWriter(
+ const NYPath::TRichYPath& path,
+ const NApi::TTableWriterOptions& options) override;
+
+ TFuture<NYson::TYsonString> GetNode(
+ const NYPath::TYPath& path,
+ const NApi::TGetNodeOptions& options) override;
+
+ TFuture<void> SetNode(
+ const NYPath::TYPath& path,
+ const NYson::TYsonString& value,
+ const NApi::TSetNodeOptions& options) override;
+
+ TFuture<void> MultisetAttributesNode(
+ const NYPath::TYPath& path,
+ const NYTree::IMapNodePtr& attributes,
+ const NApi::TMultisetAttributesNodeOptions& options) override;
+
+ TFuture<void> RemoveNode(
+ const NYPath::TYPath& path,
+ const NApi::TRemoveNodeOptions& options) override;
+
+ TFuture<NYson::TYsonString> ListNode(
+ const NYPath::TYPath& path,
+ const NApi::TListNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> CreateNode(
+ const NYPath::TYPath& path,
+ NObjectClient::EObjectType type,
+ const NApi::TCreateNodeOptions& options) override;
+
+ TFuture<NApi::TLockNodeResult> LockNode(
+ const NYPath::TYPath& path,
+ NCypressClient::ELockMode mode,
+ const NApi::TLockNodeOptions& options) override;
+
+ TFuture<void> UnlockNode(
+ const NYPath::TYPath& path,
+ const NApi::TUnlockNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> CopyNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const NApi::TCopyNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> MoveNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const NApi::TMoveNodeOptions& options) override;
+
+ TFuture<NCypressClient::TNodeId> LinkNode(
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const NApi::TLinkNodeOptions& options) override;
+
+ TFuture<void> ConcatenateNodes(
+ const std::vector<NYPath::TRichYPath>& srcPaths,
+ const NYPath::TRichYPath& dstPath,
+ const NApi::TConcatenateNodesOptions& options) override;
+
+ TFuture<void> ExternalizeNode(
+ const NYPath::TYPath& path,
+ NObjectClient::TCellTag cellTag,
+ const TExternalizeNodeOptions& options) override;
+
+ TFuture<void> InternalizeNode(
+ const NYPath::TYPath& path,
+ const TInternalizeNodeOptions& options) override;
+
+ TFuture<bool> NodeExists(
+ const NYPath::TYPath& path,
+ const NApi::TNodeExistsOptions& options) override;
+
+ TFuture<NObjectClient::TObjectId> CreateObject(
+ NObjectClient::EObjectType type,
+ const NApi::TCreateObjectOptions& options) override;
+
+ TFuture<NApi::IFileReaderPtr> CreateFileReader(
+ const NYPath::TYPath& path,
+ const NApi::TFileReaderOptions& options) override;
+
+ NApi::IFileWriterPtr CreateFileWriter(
+ const NYPath::TRichYPath& path,
+ const NApi::TFileWriterOptions& options) override;
+
+ NApi::IJournalReaderPtr CreateJournalReader(
+ const NYPath::TYPath& path,
+ const NApi::TJournalReaderOptions& options) override;
+
+ NApi::IJournalWriterPtr CreateJournalWriter(
+ const NYPath::TYPath& path,
+ const NApi::TJournalWriterOptions& options) override;
+
+ // Custom methods.
+
+ //! Returns proxy address this transaction is sticking to.
+ //! Empty for non-sticky transactions (e.g.: master) or
+ //! if address resolution is not supported by the implementation.
+ const TString& GetStickyProxyAddress() const;
+
+ //! Flushes all modifications to RPC proxy.
+ //!
+ //! In contrast to #Flush does not send #FlushTransaction request to RPC proxy and does not change
+ //! the state of the transaction within RPC proxy allowing to work with the transaction after the call
+ //! from a different transaction client.
+ //!
+ //! Useful for sending modifications to the same transaction from several different sources.
+ TFuture<void> FlushModifications();
+
+ using TModificationsFlushedHandlerSignature = void();
+ using TModificationsFlushedHandler = TCallback<TModificationsFlushedHandlerSignature>;
+ void SubscribeModificationsFlushed(const TModificationsFlushedHandler& handler);
+ void UnsubscribeModificationsFlushed(const TModificationsFlushedHandler& handler);
+
+private:
+ const TConnectionPtr Connection_;
+ const TClientPtr Client_;
+ const NRpc::IChannelPtr Channel_;
+ const NTransactionClient::TTransactionId Id_;
+ const NTransactionClient::TTimestamp StartTimestamp_;
+ const NTransactionClient::ETransactionType Type_;
+ const NTransactionClient::EAtomicity Atomicity_;
+ const NTransactionClient::EDurability Durability_;
+ const TDuration Timeout_;
+ const bool PingAncestors_;
+ const std::optional<TDuration> PingPeriod_;
+ const TString StickyProxyAddress_;
+ const i64 SequenceNumberSourceId_;
+
+ const NLogging::TLogger Logger;
+
+ TApiServiceProxy Proxy_;
+
+ std::atomic<i64> ModifyRowsRequestSequenceCounter_ = 0;
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock_);
+ ETransactionState State_ = ETransactionState::Active;
+ const TPromise<void> AbortPromise_ = NewPromise<void>();
+ std::vector<NApi::ITransactionPtr> AlienTransactions_;
+
+ THashSet<NObjectClient::TCellId> AdditionalParticipantCellIds_;
+
+ TApiServiceProxy::TReqBatchModifyRowsPtr BatchModifyRowsRequest_;
+ std::vector<TFuture<void>> BatchModifyRowsFutures_;
+
+ TSingleShotCallbackList<TCommittedHandlerSignature> Committed_;
+ TSingleShotCallbackList<TAbortedHandlerSignature> Aborted_;
+ TSingleShotCallbackList<TModificationsFlushedHandlerSignature> ModificationsFlushed_;
+
+ TFuture<void> SendPing();
+ void RunPeriodicPings();
+ bool IsPingableState();
+
+ TFuture<void> DoAbort(
+ TGuard<NThreading::TSpinLock>* guard,
+ const TTransactionAbortOptions& options = {});
+
+ void ValidateActive();
+ void DoValidateActive();
+
+ TApiServiceProxy::TReqBatchModifyRowsPtr CreateBatchModifyRowsRequest();
+ TFuture<void> InvokeBatchModifyRowsRequest();
+ std::vector<TFuture<void>> FlushModifyRowsRequests();
+
+ template <class T>
+ T PatchTransactionId(const T& options);
+ NApi::TTransactionStartOptions PatchTransactionId(const NApi::TTransactionStartOptions& options);
+ template <class T>
+ T PatchTransactionTimestamp(const T& options);
+};
+
+DEFINE_REFCOUNTED_TYPE(TTransaction)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
+#define TRANSACTION_IMPL_INL_H_
+#include "transaction_impl-inl.h"
+#undef TRANSACTION_IMPL_INL_H_
diff --git a/yt/yt/client/api/rpc_proxy/wire_row_stream.cpp b/yt/yt/client/api/rpc_proxy/wire_row_stream.cpp
new file mode 100644
index 0000000000..b3ea4244f5
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/wire_row_stream.cpp
@@ -0,0 +1,141 @@
+#include "wire_row_stream.h"
+#include "row_stream.h"
+#include "helpers.h"
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/row_batch.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/wire_protocol.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+
+#include <yt/yt/core/misc/range.h>
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWireRowStreamEncoder
+ : public IRowStreamEncoder
+{
+public:
+ explicit TWireRowStreamEncoder(TNameTablePtr nameTable)
+ : NameTable_(std::move(nameTable))
+ { }
+
+ TSharedRef Encode(
+ const IUnversionedRowBatchPtr& batch,
+ const NApi::NRpcProxy::NProto::TRowsetStatistics* statistics) override
+ {
+ YT_VERIFY(NameTableSize_ <= NameTable_->GetSize());
+
+ NProto::TRowsetDescriptor descriptor;
+ descriptor.set_wire_format_version(NApi::NRpcProxy::CurrentWireFormatVersion);
+ descriptor.set_rowset_kind(NApi::NRpcProxy::NProto::RK_UNVERSIONED);
+ descriptor.set_rowset_format(NApi::NRpcProxy::NProto::RF_YT_WIRE);
+ for (int id = NameTableSize_; id < NameTable_->GetSize(); ++id) {
+ auto* entry = descriptor.add_name_table_entries();
+ entry->set_name(TString(NameTable_->GetName(id)));
+ }
+ NameTableSize_ += descriptor.name_table_entries_size();
+
+ auto writer = CreateWireProtocolWriter();
+ auto rows = batch->MaterializeRows();
+ writer->WriteUnversionedRowset(rows);
+ auto rowRefs = writer->Finish();
+
+ auto [block, payloadRef] = SerializeRowStreamBlockEnvelope(
+ GetByteSize(rowRefs),
+ descriptor,
+ statistics);
+
+ MergeRefsToRef(rowRefs, payloadRef);
+
+ return block;
+ }
+
+private:
+ const TNameTablePtr NameTable_;
+
+ int NameTableSize_ = 0;
+};
+
+IRowStreamEncoderPtr CreateWireRowStreamEncoder(TNameTablePtr nameTable)
+{
+ return New<TWireRowStreamEncoder>(std::move(nameTable));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWireRowStreamDecoder
+ : public IRowStreamDecoder
+{
+public:
+ explicit TWireRowStreamDecoder(TNameTablePtr nameTable)
+ : NameTable_(std::move(nameTable))
+ {
+ Descriptor_.set_wire_format_version(NApi::NRpcProxy::CurrentWireFormatVersion);
+ Descriptor_.set_rowset_kind(NApi::NRpcProxy::NProto::RK_UNVERSIONED);
+ }
+
+ IUnversionedRowBatchPtr Decode(
+ const TSharedRef& payloadRef,
+ const NProto::TRowsetDescriptor& descriptorDelta) override
+ {
+ struct TWireRowStreamDecoderTag { };
+ auto reader = CreateWireProtocolReader(payloadRef, New<TRowBuffer>(TWireRowStreamDecoderTag()));
+ auto rows = reader->ReadUnversionedRowset(true);
+
+ auto oldNameTableSize = Descriptor_.name_table_entries_size();
+ YT_VERIFY(oldNameTableSize <= NameTable_->GetSize());
+
+ Descriptor_.MergeFrom(descriptorDelta);
+ auto newNameTableSize = Descriptor_.name_table_entries_size();
+
+ IdMapping_.resize(newNameTableSize);
+ for (int id = oldNameTableSize; id < newNameTableSize; ++id) {
+ const auto& name = Descriptor_.name_table_entries(id).name();
+ auto mappedId = NameTable_->GetIdOrRegisterName(name);
+ IdMapping_[id] = mappedId;
+ HasNontrivialIdMapping_ |= (id != mappedId);
+ }
+
+ if (HasNontrivialIdMapping_) {
+ for (auto row : rows) {
+ auto mutableRow = TMutableUnversionedRow(row.ToTypeErasedRow());
+ for (auto& value : mutableRow) {
+ auto newId = ApplyIdMapping(value, &IdMapping_);
+ if (newId < 0 || newId >= NameTable_->GetSize()) {
+ THROW_ERROR_EXCEPTION("Id mapping returned an invalid value %v for id %v: "
+ "expected a value in [0, %v) range",
+ newId,
+ value.Id,
+ NameTable_->GetSize());
+ }
+ value.Id = newId;
+ }
+ }
+ }
+
+ return CreateBatchFromUnversionedRows(std::move(rows));
+ }
+
+private:
+ const TNameTablePtr NameTable_;
+
+ NApi::NRpcProxy::NProto::TRowsetDescriptor Descriptor_;
+ TNameTableToSchemaIdMapping IdMapping_;
+ bool HasNontrivialIdMapping_ = false;
+};
+
+IRowStreamDecoderPtr CreateWireRowStreamDecoder(TNameTablePtr nameTable)
+{
+ return New<TWireRowStreamDecoder>(std::move(nameTable));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
+
diff --git a/yt/yt/client/api/rpc_proxy/wire_row_stream.h b/yt/yt/client/api/rpc_proxy/wire_row_stream.h
new file mode 100644
index 0000000000..528a15fdeb
--- /dev/null
+++ b/yt/yt/client/api/rpc_proxy/wire_row_stream.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+namespace NYT::NApi::NRpcProxy {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IRowStreamEncoderPtr CreateWireRowStreamEncoder(NTableClient::TNameTablePtr nameTable);
+IRowStreamDecoderPtr CreateWireRowStreamDecoder(NTableClient::TNameTablePtr nameTable);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/api/security_client.cpp b/yt/yt/client/api/security_client.cpp
new file mode 100644
index 0000000000..51fa33d26a
--- /dev/null
+++ b/yt/yt/client/api/security_client.cpp
@@ -0,0 +1,92 @@
+#include "security_client.h"
+
+namespace NYT::NApi {
+
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError TCheckPermissionResult::ToError(
+ const TString& user,
+ EPermission permission,
+ const std::optional<TString>& column) const
+{
+ switch (Action) {
+ case NSecurityClient::ESecurityAction::Allow:
+ return TError();
+
+ case NSecurityClient::ESecurityAction::Deny: {
+ TError error;
+ if (ObjectName && SubjectName) {
+ error = TError(
+ NSecurityClient::EErrorCode::AuthorizationError,
+ "Access denied for user %Qv: %Qlv permission is denied for %Qv by ACE at %v",
+ user,
+ permission,
+ *SubjectName,
+ *ObjectName);
+ } else {
+ error = TError(
+ NSecurityClient::EErrorCode::AuthorizationError,
+ "Access denied for user %Qv: %Qlv permission is not allowed by any matching ACE",
+ user,
+ permission);
+ }
+ error.MutableAttributes()->Set("user", user);
+ error.MutableAttributes()->Set("permission", permission);
+ if (ObjectId) {
+ error.MutableAttributes()->Set("denied_by", ObjectId);
+ }
+ if (SubjectId) {
+ error.MutableAttributes()->Set("denied_for", SubjectId);
+ }
+ if (column) {
+ error.MutableAttributes()->Set("column", *column);
+ }
+ return error;
+ }
+
+ default:
+ YT_ABORT();
+ }
+}
+
+TError TCheckPermissionByAclResult::ToError(const TString &user, EPermission permission) const
+{
+ switch (Action) {
+ case NSecurityClient::ESecurityAction::Allow:
+ return TError();
+
+ case NSecurityClient::ESecurityAction::Deny: {
+ TError error;
+ if (SubjectName) {
+ error = TError(
+ NSecurityClient::EErrorCode::AuthorizationError,
+ "Access denied for user %Qv: %Qlv permission is denied for %Qv by ACL",
+ user,
+ permission,
+ *SubjectName);
+ } else {
+ error = TError(
+ NSecurityClient::EErrorCode::AuthorizationError,
+ "Access denied for user %Qv: %Qlv permission is not allowed by any matching ACE",
+ user,
+ permission);
+ }
+ error.MutableAttributes()->Set("user", user);
+ error.MutableAttributes()->Set("permission", permission);
+ if (SubjectId) {
+ error.MutableAttributes()->Set("denied_for", SubjectId);
+ }
+ return error;
+ }
+
+ default:
+ YT_ABORT();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/security_client.h b/yt/yt/client/api/security_client.h
new file mode 100644
index 0000000000..ed76f72d26
--- /dev/null
+++ b/yt/yt/client/api/security_client.h
@@ -0,0 +1,152 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/client/security_client/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TAddMemberOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{ };
+
+struct TRemoveMemberOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TPrerequisiteOptions
+{ };
+
+struct TCheckPermissionOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+ , public TTransactionalOptions
+ , public TPrerequisiteOptions
+{
+ std::optional<std::vector<TString>> Columns;
+ std::optional<bool> Vital;
+};
+
+struct TCheckPermissionResult
+{
+ TError ToError(
+ const TString& user,
+ NYTree::EPermission permission,
+ const std::optional<TString>& columns = {}) const;
+
+ NSecurityClient::ESecurityAction Action;
+ NObjectClient::TObjectId ObjectId;
+ std::optional<TString> ObjectName;
+ NSecurityClient::TSubjectId SubjectId;
+ std::optional<TString> SubjectName;
+};
+
+struct TCheckPermissionResponse
+ : public TCheckPermissionResult
+{
+ std::optional<std::vector<TCheckPermissionResult>> Columns;
+};
+
+struct TCheckPermissionByAclOptions
+ : public TTimeoutOptions
+ , public TMasterReadOptions
+ , public TPrerequisiteOptions
+{
+ bool IgnoreMissingSubjects = false;
+};
+
+struct TCheckPermissionByAclResult
+{
+ TError ToError(const TString& user, NYTree::EPermission permission) const;
+
+ NSecurityClient::ESecurityAction Action;
+ NSecurityClient::TSubjectId SubjectId;
+ std::optional<TString> SubjectName;
+ std::vector<TString> MissingSubjects;
+};
+
+struct TSetUserPasswordOptions
+ : public TTimeoutOptions
+{ };
+
+struct TIssueTokenOptions
+ : public TTimeoutOptions
+{ };
+
+struct TIssueTokenResult
+{
+ TString Token;
+};
+
+struct TRevokeTokenOptions
+ : public TTimeoutOptions
+{ };
+
+struct TListUserTokensOptions
+ : public TTimeoutOptions
+{ };
+
+struct TListUserTokensResult
+{
+ // Tokens are SHA256-encoded.
+ std::vector<TString> Tokens;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ISecurityClient
+{
+ virtual TFuture<void> AddMember(
+ const TString& group,
+ const TString& member,
+ const TAddMemberOptions& options = {}) = 0;
+
+ virtual TFuture<void> RemoveMember(
+ const TString& group,
+ const TString& member,
+ const TRemoveMemberOptions& options = {}) = 0;
+
+ virtual TFuture<TCheckPermissionResponse> CheckPermission(
+ const TString& user,
+ const NYPath::TYPath& path,
+ NYTree::EPermission permission,
+ const TCheckPermissionOptions& options = {}) = 0;
+
+ virtual TFuture<TCheckPermissionByAclResult> CheckPermissionByAcl(
+ const std::optional<TString>& user,
+ NYTree::EPermission permission,
+ NYTree::INodePtr acl,
+ const TCheckPermissionByAclOptions& options = {}) = 0;
+
+ // Methods below correspond to simple authentication scheme
+ // and are intended to be used on clusters without third-party tokens (e.g. Yandex blackbox).
+ virtual TFuture<void> SetUserPassword(
+ const TString& user,
+ const TString& currentPasswordSha256,
+ const TString& newPasswordSha256,
+ const TSetUserPasswordOptions& options) = 0;
+
+ virtual TFuture<TIssueTokenResult> IssueToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TIssueTokenOptions& options) = 0;
+
+ virtual TFuture<void> RevokeToken(
+ const TString& user,
+ const TString& passwordSha256,
+ const TString& tokenSha256,
+ const TRevokeTokenOptions& options) = 0;
+
+ virtual TFuture<TListUserTokensResult> ListUserTokens(
+ const TString& user,
+ const TString& passwordSha256,
+ const TListUserTokensOptions& options) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/skynet.cpp b/yt/yt/client/api/skynet.cpp
new file mode 100644
index 0000000000..f650dc47f3
--- /dev/null
+++ b/yt/yt/client/api/skynet.cpp
@@ -0,0 +1,49 @@
+#include "skynet.h"
+
+#include <yt/yt/client/chunk_client/read_limit.h>
+#include <yt/yt/client/chunk_client/helpers.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NApi {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NChunkClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TSkynetSharePartsLocations& skynetPartsLocations, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("nodes").Value(skynetPartsLocations.NodeDirectory)
+ .Item("chunk_specs").DoListFor(
+ skynetPartsLocations.ChunkSpecs,
+ [&] (TFluentList fluent, const NChunkClient::NProto::TChunkSpec& spec) {
+ fluent
+ .Item()
+ .BeginMap()
+ .Item("chunk_id").Value(FromProto<TChunkId>(spec.chunk_id()))
+ .Item("row_index").Value(spec.table_row_index())
+ .Item("row_count").Value(spec.row_count_override())
+ .Item("range_index").Value(spec.range_index())
+ .DoIf(spec.has_lower_limit(), [&] (TFluentMap fluent) {
+ fluent.Item("lower_limit").Value(FromProto<TLegacyReadLimit>(spec.lower_limit()));
+ })
+ .DoIf(spec.has_upper_limit(), [&] (TFluentMap fluent) {
+ fluent.Item("upper_limit").Value(FromProto<TLegacyReadLimit>(spec.upper_limit()));
+ })
+ .Item("replicas").DoListFor(
+ GetReplicasFromChunkSpec(spec),
+ [] (TFluentList fluent, TChunkReplica replica) {
+ fluent.Item().Value(replica.GetNodeId());
+ })
+ .EndMap();
+ })
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/skynet.h b/yt/yt/client/api/skynet.h
new file mode 100644
index 0000000000..4e81d291ce
--- /dev/null
+++ b/yt/yt/client/api/skynet.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.pb.h>
+
+#include <yt/yt/client/node_tracker_client/node_directory.h>
+
+#include <yt/yt/core/yson/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSkynetSharePartsLocations
+ : public TRefCounted
+{
+ NNodeTrackerClient::TNodeDirectoryPtr NodeDirectory = New<NNodeTrackerClient::TNodeDirectory>();
+ std::vector<NChunkClient::NProto::TChunkSpec> ChunkSpecs;
+};
+
+DEFINE_REFCOUNTED_TYPE(TSkynetSharePartsLocations)
+
+void Serialize(
+ const TSkynetSharePartsLocations& skynetPartsLocations,
+ NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/sticky_transaction_pool.cpp b/yt/yt/client/api/sticky_transaction_pool.cpp
new file mode 100644
index 0000000000..0b2f264157
--- /dev/null
+++ b/yt/yt/client/api/sticky_transaction_pool.cpp
@@ -0,0 +1,191 @@
+#include "sticky_transaction_pool.h"
+
+#include "transaction.h"
+
+#include <yt/yt/core/concurrency/lease_manager.h>
+
+#include <library/cpp/yt/threading/spin_lock.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NTransactionClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+ITransactionPtr IStickyTransactionPool::GetTransactionAndRenewLeaseOrThrow(
+ TTransactionId transactionId)
+{
+ auto transaction = FindTransactionAndRenewLease(transactionId);
+ if (!transaction) {
+ THROW_ERROR_EXCEPTION(
+ NTransactionClient::EErrorCode::NoSuchTransaction,
+ "Sticky transaction %v is not found, "
+ "this usually means that you use tablet transactions within HTTP API; "
+ "consider using RPC API instead",
+ transactionId);
+ }
+
+ return transaction;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TStickyTransactionPool
+ : public IStickyTransactionPool
+{
+public:
+ explicit TStickyTransactionPool(const NLogging::TLogger& logger)
+ : Logger(logger)
+ { }
+
+ ITransactionPtr RegisterTransaction(ITransactionPtr transaction) override
+ {
+ auto transactionId = transaction->GetId();
+ TStickyTransactionEntry entry{
+ transaction,
+ NConcurrency::TLeaseManager::CreateLease(
+ transaction->GetTimeout(),
+ BIND(&TStickyTransactionPool::OnStickyTransactionLeaseExpired, MakeWeak(this), transactionId, MakeWeak(transaction)))
+ };
+
+ bool inserted = false;
+ {
+ auto guard = WriterGuard(StickyTransactionLock_);
+ inserted = IdToStickyTransactionEntry_.emplace(transactionId, entry).second;
+ }
+
+ if (!inserted) {
+ NConcurrency::TLeaseManager::CloseLease(entry.Lease);
+ THROW_ERROR_EXCEPTION(NTransactionClient::EErrorCode::InvalidTransactionState,
+ "Failed to register duplicate sticky transaction %v",
+ transactionId);
+ }
+
+ transaction->SubscribeCommitted(
+ BIND(&TStickyTransactionPool::OnStickyTransactionCommitted, MakeWeak(this), transactionId));
+ transaction->SubscribeAborted(
+ BIND(&TStickyTransactionPool::OnStickyTransactionAborted, MakeWeak(this), transactionId));
+
+ YT_LOG_DEBUG("Sticky transaction registered (TransactionId: %v)",
+ transactionId);
+
+ return transaction;
+ }
+
+ void UnregisterTransaction(TTransactionId transactionId) override
+ {
+ TStickyTransactionEntry entry;
+ {
+ auto guard = WriterGuard(StickyTransactionLock_);
+ auto it = IdToStickyTransactionEntry_.find(transactionId);
+ if (it == IdToStickyTransactionEntry_.end()) {
+ return;
+ }
+ entry = std::move(it->second);
+ IdToStickyTransactionEntry_.erase(it);
+ }
+
+ YT_LOG_DEBUG("Sticky transaction unregistered (TransactionId: %v)",
+ transactionId);
+ }
+
+ ITransactionPtr FindTransactionAndRenewLease(TTransactionId transactionId) override
+ {
+ ITransactionPtr transaction;
+ NConcurrency::TLease lease;
+ {
+ auto guard = ReaderGuard(StickyTransactionLock_);
+ auto it = IdToStickyTransactionEntry_.find(transactionId);
+ if (it == IdToStickyTransactionEntry_.end()) {
+ return nullptr;
+ }
+ const auto& entry = it->second;
+ transaction = entry.Transaction;
+ lease = entry.Lease;
+ }
+ NConcurrency::TLeaseManager::RenewLease(lease);
+ YT_LOG_DEBUG("Sticky transaction lease renewed (TransactionId: %v)",
+ transactionId);
+ return transaction;
+ }
+
+private:
+ const NLogging::TLogger Logger;
+
+ struct TStickyTransactionEntry
+ {
+ ITransactionPtr Transaction;
+ NConcurrency::TLease Lease;
+ };
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TReaderWriterSpinLock, StickyTransactionLock_);
+ THashMap<TTransactionId, TStickyTransactionEntry> IdToStickyTransactionEntry_;
+
+ void OnStickyTransactionLeaseExpired(TTransactionId transactionId, TWeakPtr<ITransaction> weakTransaction)
+ {
+ auto transaction = weakTransaction.Lock();
+ if (!transaction) {
+ return;
+ }
+
+ {
+ auto guard = WriterGuard(StickyTransactionLock_);
+ auto it = IdToStickyTransactionEntry_.find(transactionId);
+ if (it == IdToStickyTransactionEntry_.end()) {
+ return;
+ }
+ if (it->second.Transaction != transaction) {
+ return;
+ }
+ IdToStickyTransactionEntry_.erase(it);
+ }
+
+ YT_LOG_DEBUG("Sticky transaction lease expired (TransactionId: %v)",
+ transactionId);
+
+ YT_UNUSED_FUTURE(transaction->Abort());
+ }
+
+ void OnStickyTransactionCommitted(TTransactionId transactionId)
+ {
+ OnStickyTransactionFinished(transactionId);
+ }
+
+ void OnStickyTransactionAborted(TTransactionId transactionId, const TError& /*error*/)
+ {
+ OnStickyTransactionFinished(transactionId);
+ }
+
+ void OnStickyTransactionFinished(TTransactionId transactionId)
+ {
+ NConcurrency::TLease lease;
+ {
+ auto guard = WriterGuard(StickyTransactionLock_);
+ auto it = IdToStickyTransactionEntry_.find(transactionId);
+ if (it == IdToStickyTransactionEntry_.end()) {
+ return;
+ }
+ lease = it->second.Lease;
+ IdToStickyTransactionEntry_.erase(it);
+ }
+
+ YT_LOG_DEBUG("Sticky transaction unregistered (TransactionId: %v)",
+ transactionId);
+
+ NConcurrency::TLeaseManager::CloseLease(lease);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IStickyTransactionPoolPtr CreateStickyTransactionPool(
+ const NLogging::TLogger& logger)
+{
+ return New<TStickyTransactionPool>(logger);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/sticky_transaction_pool.h b/yt/yt/client/api/sticky_transaction_pool.h
new file mode 100644
index 0000000000..0e50472006
--- /dev/null
+++ b/yt/yt/client/api/sticky_transaction_pool.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "client.h"
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IStickyTransactionPool
+ : public virtual TRefCounted
+{
+ //! Registers transaction in the pool.
+ virtual ITransactionPtr RegisterTransaction(ITransactionPtr transaction) = 0;
+
+ //! Unregisters transaction from the pool.
+ virtual void UnregisterTransaction(NTransactionClient::TTransactionId transactionId) = 0;
+
+ //! Finds a transaction by id and renews its lease. Returns |nullptr| if transaction is not found.
+ virtual ITransactionPtr FindTransactionAndRenewLease(NTransactionClient::TTransactionId transactionId) = 0;
+
+ //! Finds a transaction by id and renews its lease. Throws if transaction is not found.
+ ITransactionPtr GetTransactionAndRenewLeaseOrThrow(NTransactionClient::TTransactionId transactionId);
+};
+
+DEFINE_REFCOUNTED_TYPE(IStickyTransactionPool)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IStickyTransactionPoolPtr CreateStickyTransactionPool(const NLogging::TLogger& logger);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/table_client.cpp b/yt/yt/client/api/table_client.cpp
new file mode 100644
index 0000000000..2a53f95307
--- /dev/null
+++ b/yt/yt/client/api/table_client.cpp
@@ -0,0 +1,35 @@
+#include "table_client.h"
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NApi {
+
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TMultiTablePartition& partition, NYson::IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("table_ranges").Value(partition.TableRanges)
+ .Item("aggregate_statistics").Value(partition.AggregateStatistics)
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TMultiTablePartitions& partitions, NYson::IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("partitions").Value(partitions.Partitions)
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/table_client.h b/yt/yt/client/api/table_client.h
new file mode 100644
index 0000000000..843d2d0c20
--- /dev/null
+++ b/yt/yt/client/api/table_client.h
@@ -0,0 +1,561 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/client/hydra/public.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/client/table_client/row_base.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/chunk_stripe_statistics.h>
+#include <yt/yt/client/table_client/columnar_statistics.h>
+
+#include <yt/yt/client/chaos_client/replication_card.h>
+
+#include <yt/yt/client/query_client/query_statistics.h>
+
+#include <yt/yt/core/ypath/public.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <library/cpp/yt/memory/shared_range.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TDetailedProfilingInfo final
+{
+ bool EnableDetailedTableProfiling = false;
+ NYPath::TYPath TablePath;
+ TDuration MountCacheWaitTime;
+
+ int WastedSubrequestCount = 0;
+
+ std::vector<TErrorCode> RetryReasons;
+};
+
+DEFINE_REFCOUNTED_TYPE(TDetailedProfilingInfo)
+
+struct TLookupRequestOptions
+ : public TFallbackReplicaOptions
+{
+ NTableClient::TColumnFilter ColumnFilter;
+ bool KeepMissingRows = false;
+ bool EnablePartialResult = false;
+ std::optional<bool> UseLookupCache;
+ TDetailedProfilingInfoPtr DetailedProfilingInfo;
+};
+
+struct TLookupRowsOptionsBase
+ : public TTabletReadOptions
+ , public TLookupRequestOptions
+ , public TMultiplexingBandOptions
+{ };
+
+struct TLookupRowsOptions
+ : public TLookupRowsOptionsBase
+{ };
+
+struct TVersionedLookupRowsOptions
+ : public TLookupRowsOptionsBase
+{
+ NTableClient::TRetentionConfigPtr RetentionConfig;
+};
+
+struct TMultiLookupSubrequest
+{
+ NYPath::TYPath Path;
+ NTableClient::TNameTablePtr NameTable;
+ TSharedRange<NTableClient::TLegacyKey> Keys;
+
+ // NB: Other options from TLookupRowsOptions that are absent from TLookupRequestOptions are
+ // common and included in TMultiLookupOptions.
+ TLookupRequestOptions Options;
+};
+
+struct TMultiLookupOptions
+ : public TTimeoutOptions
+ , public TTabletReadOptionsBase
+ , public TMultiplexingBandOptions
+{ };
+
+struct TExplainQueryOptions
+ : public TSelectRowsOptionsBase
+{
+ bool VerboseOutput = false;
+};
+
+struct TSelectRowsResult
+{
+ IUnversionedRowsetPtr Rowset;
+ NQueryClient::TQueryStatistics Statistics;
+};
+
+struct TTableReaderOptions
+ : public TTransactionalOptions
+{
+ bool Unordered = false;
+ bool OmitInaccessibleColumns = false;
+ bool EnableTableIndex = false;
+ bool EnableRowIndex = false;
+ bool EnableRangeIndex = false;
+ bool EnableTabletIndex = false;
+ NTableClient::TTableReaderConfigPtr Config;
+};
+
+struct TTableWriterOptions
+ : public TTransactionalOptions
+{
+ bool ValidateAnyIsValidYson = false;
+
+ NTableClient::TTableWriterConfigPtr Config;
+};
+
+struct TTabletRangeOptions
+{
+ std::optional<int> FirstTabletIndex;
+ std::optional<int> LastTabletIndex;
+};
+
+struct TMountTableOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTabletRangeOptions
+{
+ NTabletClient::TTabletCellId CellId = NTabletClient::NullTabletCellId;
+ std::vector<NTabletClient::TTabletCellId> TargetCellIds;
+ bool Freeze = false;
+};
+
+struct TUnmountTableOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTabletRangeOptions
+{
+ bool Force = false;
+};
+
+struct TRemountTableOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTabletRangeOptions
+{ };
+
+struct TFreezeTableOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTabletRangeOptions
+{ };
+
+struct TUnfreezeTableOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTabletRangeOptions
+{ };
+
+struct TReshardTableOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTabletRangeOptions
+{
+ std::optional<bool> Uniform;
+ std::optional<bool> EnableSlicing;
+ std::optional<double> SlicingAccuracy;
+};
+
+struct TReshardTableAutomaticOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTabletRangeOptions
+{
+ bool KeepActions = false;
+};
+
+struct TAlterTableOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+ , public TTransactionalOptions
+{
+ std::optional<NTableClient::TTableSchema> Schema;
+ std::optional<NTableClient::TMasterTableSchemaId> SchemaId;
+ std::optional<bool> Dynamic;
+ std::optional<NTabletClient::TTableReplicaId> UpstreamReplicaId;
+ std::optional<NTableClient::ETableSchemaModification> SchemaModification;
+ std::optional<NChaosClient::TReplicationProgress> ReplicationProgress;
+};
+
+struct TTrimTableOptions
+ : public TTimeoutOptions
+{ };
+
+struct TAlterTableReplicaOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{
+ std::optional<bool> Enabled;
+ std::optional<NTabletClient::ETableReplicaMode> Mode;
+ std::optional<bool> PreserveTimestamps;
+ std::optional<NTransactionClient::EAtomicity> Atomicity;
+ std::optional<bool> EnableReplicatedTableTracker;
+};
+
+struct TGetTablePivotKeysOptions
+ : public TTimeoutOptions
+{
+ bool RepresentKeyAsList = false;
+};
+
+struct TGetInSyncReplicasOptions
+ : public TTimeoutOptions
+ , public TSyncReplicaCacheOptions
+{
+ NTransactionClient::TTimestamp Timestamp = NTransactionClient::NullTimestamp;
+};
+
+struct TGetTabletInfosOptions
+ : public TTimeoutOptions
+{
+ bool RequestErrors = false;
+};
+
+struct TTabletInfo
+{
+ struct TTableReplicaInfo
+ {
+ NTabletClient::TTableReplicaId ReplicaId;
+ NTransactionClient::TTimestamp LastReplicationTimestamp;
+ NTabletClient::ETableReplicaMode Mode;
+ i64 CurrentReplicationRowIndex;
+ i64 CommittedReplicationRowIndex;
+ TError ReplicationError;
+ };
+
+ //! Currently only provided for ordered tablets.
+ //! Indicates the total number of rows added to the tablet (including trimmed ones).
+ // TODO(babenko): implement for sorted tablets
+ i64 TotalRowCount = 0;
+
+ //! Only makes sense for ordered tablet.
+ //! Contains the number of front rows that are trimmed and are not guaranteed to be accessible.
+ i64 TrimmedRowCount = 0;
+
+ //! Only makes sense for replicated tablets.
+ //! Contains the number of rows that are yet to be committed.
+ i64 DelayedLocklessRowCount = 0;
+
+ //! Mostly makes sense for ordered tablets.
+ //! Contains the barrier timestamp of the tablet cell containing the tablet, which lags behind the current timestamp.
+ //! It is guaranteed that all transactions with commit timestamp not exceeding the barrier
+ //! are fully committed; e.g. all their added rows are visible (and are included in TTabletInfo::TotalRowCount).
+ NTransactionClient::TTimestamp BarrierTimestamp;
+
+ //! Contains maximum timestamp of committed transactions.
+ NTransactionClient::TTimestamp LastWriteTimestamp;
+
+ //! Only makes sense for replicated tablets.
+ std::optional<std::vector<TTableReplicaInfo>> TableReplicaInfos;
+
+ //! Set if `RequestErrors` is present in command parameters.
+ std::vector<TError> TabletErrors;
+};
+
+struct TGetTabletErrorsOptions
+ : public TTimeoutOptions
+{
+ std::optional<i64> Limit;
+};
+
+struct TGetTabletErrorsResult
+{
+ bool Incomplete = false;
+ THashMap<NTabletClient::TTabletId, std::vector<TError>> TabletErrors;
+ THashMap<NTabletClient::TTableReplicaId, std::vector<TError>> ReplicationErrors;
+};
+
+struct TBalanceTabletCellsOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{
+ bool KeepActions = false;
+};
+
+struct TTableBackupManifest
+ : public NYTree::TYsonStruct
+{
+ NYTree::TYPath SourcePath;
+ NYTree::TYPath DestinationPath;
+ NTabletClient::EOrderedTableBackupMode OrderedMode;
+
+ REGISTER_YSON_STRUCT(TTableBackupManifest);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.Parameter("source_path", &TThis::SourcePath);
+ registrar.Parameter("destination_path", &TThis::DestinationPath);
+ registrar.Parameter("ordered_mode", &TThis::OrderedMode)
+ .Default(NTabletClient::EOrderedTableBackupMode::Exact);
+ }
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableBackupManifest)
+
+struct TBackupManifest
+ : public NYTree::TYsonStruct
+{
+ THashMap<TString, std::vector<TTableBackupManifestPtr>> Clusters;
+
+ REGISTER_YSON_STRUCT(TBackupManifest);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.Parameter("clusters", &TThis::Clusters);
+ }
+};
+
+DEFINE_REFCOUNTED_TYPE(TBackupManifest)
+
+struct TCreateTableBackupOptions
+ : public TTimeoutOptions
+{
+ TDuration CheckpointTimestampDelay = TDuration::Zero();
+ TDuration CheckpointCheckPeriod = TDuration::Zero();
+ TDuration CheckpointCheckTimeout = TDuration::Zero();
+
+ bool Force = false;
+};
+
+struct TRestoreTableBackupOptions
+ : public TTimeoutOptions
+{
+ bool Force = false;
+ bool Mount = false;
+ bool EnableReplicas = false;
+};
+
+struct TUpdateChaosTableReplicaProgressOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{
+ NChaosClient::TReplicationProgress Progress;
+};
+
+struct TAlterReplicationCardOptions
+ : public TTimeoutOptions
+ , public TMutatingOptions
+{
+ NTabletClient::TReplicatedTableOptionsPtr ReplicatedTableOptions;
+ std::optional<bool> EnableReplicatedTableTracker;
+ std::optional<NChaosClient::TReplicationCardCollocationId> ReplicationCardCollocationId;
+};
+
+struct TGetReplicationCardOptions
+ : public TTimeoutOptions
+ , public NChaosClient::TReplicationCardFetchOptions
+{
+ bool BypassCache = false;
+};
+
+struct TGetColumnarStatisticsOptions
+ : public TTransactionalOptions
+ , public TTimeoutOptions
+{
+ NChunkClient::TFetchChunkSpecConfigPtr FetchChunkSpecConfig;
+ NChunkClient::TFetcherConfigPtr FetcherConfig;
+ NTableClient::EColumnarStatisticsFetcherMode FetcherMode = NTableClient::EColumnarStatisticsFetcherMode::FromNodes;
+ bool EnableEarlyFinish = true;
+};
+
+struct TPartitionTablesOptions
+ : public TTransactionalOptions
+ , public TTimeoutOptions
+{
+ NChunkClient::TFetchChunkSpecConfigPtr FetchChunkSpecConfig;
+ NChunkClient::TFetcherConfigPtr FetcherConfig;
+ NChunkClient::TChunkSliceFetcherConfigPtr ChunkSliceFetcherConfig;
+ NTableClient::ETablePartitionMode PartitionMode = NTableClient::ETablePartitionMode::Unordered;
+ i64 DataWeightPerPartition;
+ std::optional<int> MaxPartitionCount;
+ bool AdjustDataWeightPerPartition = true;
+ bool EnableKeyGuarantee = false;
+};
+
+struct TMultiTablePartition
+{
+ //! Table ranges are indexed by table index.
+ std::vector<NYPath::TRichYPath> TableRanges;
+
+ //! Aggregate statistics of all the table ranges in the partition.
+ NTableClient::TChunkStripeStatistics AggregateStatistics;
+};
+
+void Serialize(const TMultiTablePartition& partitions, NYson::IYsonConsumer* consumer);
+
+struct TMultiTablePartitions
+{
+ std::vector<TMultiTablePartition> Partitions;
+};
+
+void Serialize(const TMultiTablePartitions& partitions, NYson::IYsonConsumer* consumer);
+
+struct TLocateSkynetShareOptions
+ : public TTimeoutOptions
+{
+ NChunkClient::TFetchChunkSpecConfigPtr Config;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ITableClientBase
+{
+ virtual TFuture<IUnversionedRowsetPtr> LookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TLookupRowsOptions& options = {}) = 0;
+
+ virtual TFuture<IVersionedRowsetPtr> VersionedLookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TVersionedLookupRowsOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<IUnversionedRowsetPtr>> MultiLookup(
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options = {}) = 0;
+
+ virtual TFuture<TSelectRowsResult> SelectRows(
+ const TString& query,
+ const TSelectRowsOptions& options = {}) = 0;
+
+ virtual TFuture<NYson::TYsonString> ExplainQuery(
+ const TString& query,
+ const TExplainQueryOptions& options = TExplainQueryOptions()) = 0;
+
+ virtual TFuture<ITableReaderPtr> CreateTableReader(
+ const NYPath::TRichYPath& path,
+ const TTableReaderOptions& options = {}) = 0;
+
+ virtual TFuture<ITableWriterPtr> CreateTableWriter(
+ const NYPath::TRichYPath& path,
+ const TTableWriterOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ITableClient
+{
+ virtual TFuture<void> MountTable(
+ const NYPath::TYPath& path,
+ const TMountTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> UnmountTable(
+ const NYPath::TYPath& path,
+ const TUnmountTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> RemountTable(
+ const NYPath::TYPath& path,
+ const TRemountTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> FreezeTable(
+ const NYPath::TYPath& path,
+ const TFreezeTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> UnfreezeTable(
+ const NYPath::TYPath& path,
+ const TUnfreezeTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> ReshardTable(
+ const NYPath::TYPath& path,
+ const std::vector<NTableClient::TLegacyOwningKey>& pivotKeys,
+ const TReshardTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> ReshardTable(
+ const NYPath::TYPath& path,
+ int tabletCount,
+ const TReshardTableOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<NTabletClient::TTabletActionId>> ReshardTableAutomatic(
+ const NYPath::TYPath& path,
+ const TReshardTableAutomaticOptions& options = {}) = 0;
+
+ virtual TFuture<void> TrimTable(
+ const NYPath::TYPath& path,
+ int tabletIndex,
+ i64 trimmedRowCount,
+ const TTrimTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> AlterTable(
+ const NYPath::TYPath& path,
+ const TAlterTableOptions& options = {}) = 0;
+
+ virtual TFuture<void> AlterTableReplica(
+ NTabletClient::TTableReplicaId replicaId,
+ const TAlterTableReplicaOptions& options = {}) = 0;
+
+ virtual TFuture<NYson::TYsonString> GetTablePivotKeys(
+ const NYPath::TYPath& path,
+ const TGetTablePivotKeysOptions& options = {}) = 0;
+
+ virtual TFuture<void> CreateTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TCreateTableBackupOptions& options = {}) = 0;
+
+ virtual TFuture<void> RestoreTableBackup(
+ const TBackupManifestPtr& manifest,
+ const TRestoreTableBackupOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<NTabletClient::TTableReplicaId>> GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const NTableClient::TNameTablePtr& nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TGetInSyncReplicasOptions& options = {}) = 0;
+
+ //! Same as above but returns the list of replicas that are in sync w.r.t. all of the table keys.
+ virtual TFuture<std::vector<NTabletClient::TTableReplicaId>> GetInSyncReplicas(
+ const NYPath::TYPath& path,
+ const TGetInSyncReplicasOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<TTabletInfo>> GetTabletInfos(
+ const NYPath::TYPath& path,
+ const std::vector<int>& tabletIndexes,
+ const TGetTabletInfosOptions& options = {}) = 0;
+
+ virtual TFuture<TGetTabletErrorsResult> GetTabletErrors(
+ const NYPath::TYPath& path,
+ const TGetTabletErrorsOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<NTabletClient::TTabletActionId>> BalanceTabletCells(
+ const TString& tabletCellBundle,
+ const std::vector<NYPath::TYPath>& movableTables,
+ const TBalanceTabletCellsOptions& options = {}) = 0;
+
+ virtual TFuture<NChaosClient::TReplicationCardPtr> GetReplicationCard(
+ const NChaosClient::TReplicationCardId replicationCardId,
+ const TGetReplicationCardOptions& options = {}) = 0;
+
+ virtual TFuture<void> UpdateChaosTableReplicaProgress(
+ NChaosClient::TReplicaId replicaId,
+ const TUpdateChaosTableReplicaProgressOptions& options = {}) = 0;
+
+ virtual TFuture<void> AlterReplicationCard(
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TAlterReplicationCardOptions& options = {}) = 0;
+
+ virtual TFuture<TSkynetSharePartsLocationsPtr> LocateSkynetShare(
+ const NYPath::TRichYPath& path,
+ const TLocateSkynetShareOptions& options = {}) = 0;
+
+ virtual TFuture<std::vector<NTableClient::TColumnarStatistics>> GetColumnarStatistics(
+ const std::vector<NYPath::TRichYPath>& path,
+ const TGetColumnarStatisticsOptions& options = {}) = 0;
+
+ virtual TFuture<TMultiTablePartitions> PartitionTables(
+ const std::vector<NYPath::TRichYPath>& paths,
+ const TPartitionTablesOptions& options) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/table_reader.h b/yt/yt/client/api/table_reader.h
new file mode 100644
index 0000000000..d2123db691
--- /dev/null
+++ b/yt/yt/client/api/table_reader.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/unversioned_reader.h>
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/data_statistics.pb.h>
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TODO(babenko): consider joining with NTableClient::IRowBatchReader
+struct ITableReader
+ : public virtual TRefCounted
+{
+ //! Returns the starting row index within the table.
+ virtual i64 GetStartRowIndex() const = 0;
+
+ //! Returns the total (approximate) number of rows readable.
+ virtual i64 GetTotalRowCount() const = 0;
+
+ //! Returns various data statistics.
+ virtual NChunkClient::NProto::TDataStatistics GetDataStatistics() const = 0;
+
+ //! Returns an asynchronous flag enabling to wait until data is available.
+ virtual TFuture<void> GetReadyEvent() = 0;
+
+ //! Attempts to read a bunch of #rows. If true is returned but #rows is empty
+ //! the rows are not immediately available and the client must invoke
+ //! #GetReadyEvent and wait. False is returned if the end of table was reached.
+ virtual NTableClient::IUnversionedRowBatchPtr Read(const NTableClient::TRowBatchReadOptions& options = {}) = 0;
+
+ //! Returns the name table used for constructing rows.
+ virtual const NTableClient::TNameTablePtr& GetNameTable() const = 0;
+
+ //! Returns schema of the table.
+ virtual const NTableClient::TTableSchemaPtr& GetTableSchema() const = 0;
+
+ //! Returns the names of columns that are not accessible according to columnar ACL
+ //! and were omitted. See #TTableReaderOptions::OmitInaccessibleColumns.
+ virtual const std::vector<TString>& GetOmittedInaccessibleColumns() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ITableReader)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/table_writer.h b/yt/yt/client/api/table_writer.h
new file mode 100644
index 0000000000..a9aff96356
--- /dev/null
+++ b/yt/yt/client/api/table_writer.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ITableWriter
+ : public virtual TRefCounted
+{
+ //! Attempts to write a bunch of #rows. If false is returned then the rows
+ //! are not accepted and the client must invoke #GetReadyEvent and wait.
+ virtual bool Write(TRange<NTableClient::TUnversionedRow> rows) = 0;
+
+ //! Returns an asynchronous flag enabling to wait until data is written.
+ virtual TFuture<void> GetReadyEvent() = 0;
+
+ //! Closes the writer. Must be the last call to the writer.
+ virtual TFuture<void> Close() = 0;
+
+ //! Returns the name table to be used for constructing rows.
+ virtual const NTableClient::TNameTablePtr& GetNameTable() const = 0;
+
+ //! Returns the schema to be used for constructing rows.
+ virtual const NTableClient::TTableSchemaPtr& GetSchema() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ITableWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/transaction-inl.h b/yt/yt/client/api/transaction-inl.h
new file mode 100644
index 0000000000..c2ceb5f388
--- /dev/null
+++ b/yt/yt/client/api/transaction-inl.h
@@ -0,0 +1,42 @@
+#ifndef TRANSACTION_INL_H_
+#error "Direct inclusion of this file is not allowed, include transaction.h"
+// For the sake of sane code completion.
+#include "transaction.h"
+#endif
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TDerivedTransaction>
+TDerivedTransaction* ITransaction::As()
+{
+ auto* derived = dynamic_cast<TDerivedTransaction*>(this);
+ YT_VERIFY(derived);
+ return derived;
+}
+
+template <class TDerivedTransaction>
+TDerivedTransaction* ITransaction::TryAs()
+{
+ return dynamic_cast<TDerivedTransaction*>(this);
+}
+
+template <class TDerivedTransaction>
+const TDerivedTransaction* ITransaction::As() const
+{
+ const auto* derived = dynamic_cast<const TDerivedTransaction*>(this);
+ YT_VERIFY(derived);
+ return derived;
+}
+
+template <class TDerivedTransaction>
+const TDerivedTransaction* ITransaction::TryAs() const
+{
+ return dynamic_cast<const TDerivedTransaction*>(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
diff --git a/yt/yt/client/api/transaction.cpp b/yt/yt/client/api/transaction.cpp
new file mode 100644
index 0000000000..deec6dccd1
--- /dev/null
+++ b/yt/yt/client/api/transaction.cpp
@@ -0,0 +1,197 @@
+#include "transaction.h"
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+
+#include <yt/yt/client/queue_client/consumer_client.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NQueueClient;
+using namespace NConcurrency;
+
+/////////////////////////////////////////////////////////////////////////////
+
+void ITransaction::WriteRows(
+ const NYPath::TYPath& path,
+ TNameTablePtr nameTable,
+ TSharedRange<TUnversionedRow> rows,
+ const TModifyRowsOptions& options)
+{
+ std::vector<TRowModification> modifications;
+ modifications.reserve(rows.Size());
+
+ for (auto row : rows) {
+ modifications.push_back({ERowModificationType::Write, row.ToTypeErasedRow(), TLockMask()});
+ }
+
+ ModifyRows(
+ path,
+ std::move(nameTable),
+ MakeSharedRange(std::move(modifications), std::move(rows.ReleaseHolder())),
+ options);
+}
+
+void ITransaction::WriteRows(
+ const NYPath::TYPath& path,
+ TNameTablePtr nameTable,
+ TSharedRange<TVersionedRow> rows,
+ const TModifyRowsOptions& options)
+{
+ std::vector<TRowModification> modifications;
+ modifications.reserve(rows.Size());
+
+ for (auto row : rows) {
+ modifications.push_back({ERowModificationType::VersionedWrite, row.ToTypeErasedRow(), TLockMask()});
+ }
+
+ ModifyRows(
+ path,
+ std::move(nameTable),
+ MakeSharedRange(std::move(modifications), std::move(rows.ReleaseHolder())),
+ options);
+}
+
+void ITransaction::DeleteRows(
+ const NYPath::TYPath& path,
+ TNameTablePtr nameTable,
+ TSharedRange<TLegacyKey> keys,
+ const TModifyRowsOptions& options)
+{
+ std::vector<TRowModification> modifications;
+ modifications.reserve(keys.Size());
+ for (auto key : keys) {
+ modifications.push_back({ERowModificationType::Delete, key.ToTypeErasedRow(), TLockMask()});
+ }
+
+ ModifyRows(
+ path,
+ std::move(nameTable),
+ MakeSharedRange(std::move(modifications), std::move(keys.ReleaseHolder())),
+ options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ITransaction::LockRows(
+ const NYPath::TYPath& path,
+ TNameTablePtr nameTable,
+ TSharedRange<TLegacyKey> keys,
+ TLockMask lockMask)
+{
+ std::vector<TRowModification> modifications;
+ modifications.reserve(keys.Size());
+
+ for (auto key : keys) {
+ TRowModification modification;
+ modification.Type = ERowModificationType::WriteAndLock;
+ modification.Row = key.ToTypeErasedRow();
+ modification.Locks = lockMask;
+ modifications.push_back(modification);
+ }
+
+ ModifyRows(
+ path,
+ std::move(nameTable),
+ MakeSharedRange(std::move(modifications), std::move(keys)),
+ TModifyRowsOptions());
+}
+
+void ITransaction::LockRows(
+ const NYPath::TYPath& path,
+ TNameTablePtr nameTable,
+ TSharedRange<TLegacyKey> keys,
+ ELockType lockType)
+{
+ TLockMask lockMask;
+ lockMask.Set(PrimaryLockIndex, lockType);
+ LockRows(path, nameTable, keys, lockMask);
+}
+
+void ITransaction::LockRows(
+ const NYPath::TYPath& path,
+ TNameTablePtr nameTable,
+ TSharedRange<TLegacyKey> keys,
+ const std::vector<TString>& locks,
+ ELockType lockType)
+{
+ const auto& tableMountCache = GetClient()->GetTableMountCache();
+ auto tableInfo = WaitFor(tableMountCache->GetTableInfo(path))
+ .ValueOrThrow();
+
+ auto lockMask = GetLockMask(
+ *tableInfo->Schemas[ETableSchemaKind::Write],
+ GetAtomicity() == NTransactionClient::EAtomicity::Full,
+ locks,
+ lockType);
+
+ LockRows(path, nameTable, keys, lockMask);
+}
+
+void ITransaction::AdvanceConsumer(
+ const NYPath::TYPath& path,
+ int partitionIndex,
+ std::optional<i64> oldOffset,
+ i64 newOffset)
+{
+ THROW_ERROR_EXCEPTION_IF(newOffset < 0, "Queue consumer offset %v cannot be negative", newOffset);
+
+ auto consumerClient = CreateBigRTConsumerClient(GetClient(), path);
+ consumerClient->Advance(MakeStrong(this), partitionIndex, oldOffset, newOffset);
+}
+
+void ITransaction::AdvanceConsumer(
+ const NYPath::TRichYPath& consumerPath,
+ const NYPath::TRichYPath& queuePath,
+ int partitionIndex,
+ std::optional<i64> oldOffset,
+ i64 newOffset)
+{
+ THROW_ERROR_EXCEPTION_IF(newOffset < 0, "Queue consumer offset %v cannot be negative", newOffset);
+
+ auto tableMountCache = GetClient()->GetTableMountCache();
+ auto queuePhysicalPath = queuePath;
+ auto queueTableInfoOrError = WaitFor(tableMountCache->GetTableInfo(queuePath.GetPath()));
+ if (queueTableInfoOrError.IsOK()) {
+ queuePhysicalPath = NYPath::TRichYPath(queueTableInfoOrError.Value()->PhysicalPath, queuePath.Attributes());
+ }
+
+ // TODO(achulkov2): Support consumers from any cluster.
+ auto subConsumerClient = CreateSubConsumerClient(GetClient(), consumerPath.GetPath(), queuePhysicalPath);
+ return subConsumerClient->Advance(MakeStrong(this), partitionIndex, oldOffset, newOffset);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<ITransactionPtr> StartAlienTransaction(
+ const ITransactionPtr& localTransaction,
+ const IClientPtr& alienClient,
+ const TAlienTransactionStartOptions& options)
+{
+ YT_VERIFY(localTransaction->GetType() == NTransactionClient::ETransactionType::Tablet);
+
+ if (localTransaction->GetConnection()->IsSameCluster(alienClient->GetConnection())) {
+ return MakeFuture(localTransaction);
+ }
+
+ return alienClient->StartTransaction(
+ NTransactionClient::ETransactionType::Tablet,
+ TTransactionStartOptions{
+ .Id = localTransaction->GetId(),
+ .Atomicity = options.Atomicity,
+ .Durability = options.Durability,
+ .StartTimestamp = options.StartTimestamp
+ }).Apply(BIND([=] (const ITransactionPtr& alienTransaction) {
+ localTransaction->RegisterAlienTransaction(alienTransaction);
+ return alienTransaction;
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/api/transaction.h b/yt/yt/client/api/transaction.h
new file mode 100644
index 0000000000..2d62e5bbaa
--- /dev/null
+++ b/yt/yt/client/api/transaction.h
@@ -0,0 +1,288 @@
+#pragma once
+
+#include "client.h"
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+#include <yt/yt/client/hive/timestamp_map.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/core/actions/signal.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTransactionCommitOptions
+ : public TMutatingOptions
+ , public TPrerequisiteOptions
+ , public TTransactionalOptions
+{
+ //! If not null, then this particular cell will be the coordinator.
+ NObjectClient::TCellId CoordinatorCellId;
+
+ //! If |true| then two-phase-commit protocol is executed regardless of the number of participants.
+ bool Force2PC = false;
+
+ //! Eager: coordinator is committed first, success is reported immediately; the participants are committed afterwards.
+ //! Lazy: all the participants must successfully commit before coordinator commits; only after this success is reported.
+ ETransactionCoordinatorCommitMode CoordinatorCommitMode = ETransactionCoordinatorCommitMode::Eager;
+
+ //! Early: coordinator is prepared first and committed first.
+ //! Late: coordinator is prepared last and after commit timestamp generation; prepare and commit
+ //! are executed in single mutation.
+ ETransactionCoordinatorPrepareMode CoordinatorPrepareMode = ETransactionCoordinatorPrepareMode::Early;
+
+ //! At non-coordinating participants, Transaction Manager will synchronize with
+ //! these cells before running prepare.
+ std::vector<NObjectClient::TCellId> CellIdsToSyncWithBeforePrepare;
+
+ //! If |true| then all participants will use the commit timestamp provided by the coordinator.
+ //! If |false| then the participants will use individual commit timestamps based on their cell tag.
+ bool InheritCommitTimestamp = true;
+
+ //! If |true| then the coordinator will generate a non-null prepare timestamp (which is a lower bound for
+ //! the upcoming commit timestamp) and send it to all the participants.
+ //! If |false| then no prepare timestamp is generated and null value is provided to the participants.
+ //! The latter is useful for async replication that does not involve any local write operations
+ //! and also relies on ETransactionCoordinatorCommitMode::Lazy transactions whose commit may be delayed
+ //! for an arbitrary period of time in case of replica failure.
+ bool GeneratePrepareTimestamp = true;
+
+ //! If non-null then the coordinator will fail the commit if generated commit timestamp
+ //! exceeds |MaxAllowedCommitTimestamp|.
+ NTransactionClient::TTimestamp MaxAllowedCommitTimestamp = NTransactionClient::NullTimestamp;
+
+ //! Cell ids of additional 2PC participants.
+ //! Used to implement cross-cluster commit via RPC proxy.
+ std::vector<NObjectClient::TCellId> AdditionalParticipantCellIds;
+};
+
+struct TTransactionPingOptions
+{
+ bool EnableRetries = false;
+};
+
+struct TTransactionCommitResult
+{
+ //! NullTimestamp for all cases when CommitTimestamps are empty.
+ //! NullTimestamp when the primary cell did not participate in to transaction.
+ NHiveClient::TTimestamp PrimaryCommitTimestamp = NHiveClient::NullTimestamp;
+ //! Empty for non-atomic transactions (timestamps are fake).
+ //! Empty for empty tablet transactions (since the commit is essentially no-op).
+ //! May contain multiple items for cross-cluster commit.
+ NHiveClient::TTimestampMap CommitTimestamps;
+};
+
+struct TTransactionAbortOptions
+ : public TMutatingOptions
+ , public TPrerequisiteOptions
+ , public TTransactionalOptions
+{
+ bool Force = false;
+};
+
+struct TTransactionFlushResult
+{
+ std::vector<NElection::TCellId> ParticipantCellIds;
+};
+
+//! Either a write or delete.
+struct TRowModification
+{
+ //! Discriminates between writes and deletes.
+ ERowModificationType Type;
+ //! Either a row (for write; versioned or unversioned) or a key (for delete; always unversioned).
+ NTableClient::TTypeErasedRow Row;
+ //! Locks.
+ NTableClient::TLockMask Locks;
+};
+
+struct TModifyRowsOptions
+{
+ //! If this happens to be a modification of a replicated table,
+ //! controls if at least one sync replica is required.
+ bool RequireSyncReplica = true;
+
+ //! For chaos replicated tables indicates if it is necessary to explore other replicas.
+ bool TopmostTransaction = true;
+
+ //! For chaos replicas pass replication card to ensure that all data is sent using same meta info.
+ NChaosClient::TReplicationCardPtr ReplicationCard;
+
+ //! For writes to replicas, this is the id of the replica at the upstream cluster.
+ NTabletClient::TTableReplicaId UpstreamReplicaId;
+
+ //! Modifications are sent asynchronously. Sequential numbering is
+ //! required to restore their order.
+ //!
+ //! This parameter is only used by native client (in particular within RPC proxy server).
+ std::optional<i64> SequenceNumber;
+
+ //! Modifications can be sent from several sources in case of several clients
+ //! attached to the same transaction.
+ //!
+ //! Modifications within one source will be serialized by this source sequence numbers.
+ //! Modifications from different sources will be serialized arbitrarily, that is why
+ //! different sources must send independent modifications.
+ //!
+ //! If sequence number is missing, source id is ignored.
+ //!
+ //! This parameter is only used by native client (in particular within RPC proxy server).
+ i64 SequenceNumberSourceId = 0;
+
+ //! If set treat missing key columns as null.
+ bool AllowMissingKeyColumns = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Represents a client-controlled transaction.
+/*
+ * Transactions are created by calling IClientBase::Transaction.
+ *
+ * For some table operations (e.g. #WriteRows), the transaction instance
+ * buffers all modifications and flushes them during #Commit. This, in
+ * particular, explains why these methods return |void|.
+ *
+ * Thread affinity: any
+ */
+struct ITransaction
+ : public virtual IClientBase
+{
+ virtual IClientPtr GetClient() const = 0;
+ virtual NTransactionClient::ETransactionType GetType() const = 0;
+ virtual NTransactionClient::TTransactionId GetId() const = 0;
+ virtual NTransactionClient::TTimestamp GetStartTimestamp() const = 0;
+ virtual NTransactionClient::EAtomicity GetAtomicity() const = 0;
+ virtual NTransactionClient::EDurability GetDurability() const = 0;
+ virtual TDuration GetTimeout() const = 0;
+
+ virtual TFuture<void> Ping(const NApi::TTransactionPingOptions& options = {}) = 0;
+ virtual TFuture<TTransactionCommitResult> Commit(const TTransactionCommitOptions& options = {}) = 0;
+ virtual TFuture<void> Abort(const TTransactionAbortOptions& options = {}) = 0;
+ virtual void Detach() = 0;
+ virtual TFuture<TTransactionFlushResult> Flush() = 0;
+ virtual void RegisterAlienTransaction(const ITransactionPtr& transaction) = 0;
+
+ using TCommittedHandlerSignature = void();
+ using TCommittedHandler = TCallback<TCommittedHandlerSignature>;
+ DECLARE_INTERFACE_SIGNAL(TCommittedHandlerSignature, Committed);
+
+ using TAbortedHandlerSignature = void(const TError& error);
+ using TAbortedHandler = TCallback<TAbortedHandlerSignature>;
+ DECLARE_INTERFACE_SIGNAL(TAbortedHandlerSignature, Aborted);
+
+ // Verified dynamic casts to a more specific interface.
+
+ template <class TDerivedTransaction>
+ TDerivedTransaction* As();
+ template <class TDerivedTransaction>
+ TDerivedTransaction* TryAs();
+
+ template <class TDerivedTransaction>
+ const TDerivedTransaction* As() const;
+ template <class TDerivedTransaction>
+ const TDerivedTransaction* TryAs() const;
+
+ // Tables.
+
+ void WriteRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<NTableClient::TUnversionedRow> rows,
+ const TModifyRowsOptions& options = {});
+
+ void WriteRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<NTableClient::TVersionedRow> rows,
+ const TModifyRowsOptions& options = {});
+
+ void DeleteRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<NTableClient::TLegacyKey> keys,
+ const TModifyRowsOptions& options = {});
+
+ void LockRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<NTableClient::TLegacyKey> keys,
+ NTableClient::TLockMask lockMask);
+
+ void LockRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<NTableClient::TLegacyKey> keys,
+ NTableClient::ELockType lockType = NTableClient::ELockType::SharedStrong);
+
+ void LockRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<NTableClient::TLegacyKey> keys,
+ const std::vector<TString>& locks,
+ NTableClient::ELockType lockType = NTableClient::ELockType::SharedStrong);
+
+ virtual void ModifyRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<TRowModification> modifications,
+ const TModifyRowsOptions& options = TModifyRowsOptions()) = 0;
+
+ // Consumers.
+
+ //! Advance the consumer's offset for the given partition, setting it to a new value.
+ //!
+ //! If oldOffset is specified, the current offset is read inside this transaction and compared with oldOffset.
+ //! If they are equal, the new offset is written, otherwise an exception is thrown.
+ void AdvanceConsumer(
+ const NYPath::TYPath& path,
+ int partitionIndex,
+ std::optional<i64> oldOffset,
+ i64 newOffset);
+
+ void AdvanceConsumer(
+ const NYPath::TRichYPath& consumerPath,
+ const NYPath::TRichYPath& queuePath,
+ int partitionIndex,
+ std::optional<i64> oldOffset,
+ i64 newOffset);
+};
+
+DEFINE_REFCOUNTED_TYPE(ITransaction)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A subset of #TTransactionStartOptions tailored for the case of alien
+//! transactions.
+struct TAlienTransactionStartOptions
+{
+ NTransactionClient::EAtomicity Atomicity = NTransactionClient::EAtomicity::Full;
+ NTransactionClient::EDurability Durability = NTransactionClient::EDurability::Sync;
+
+ NTransactionClient::TTimestamp StartTimestamp = NTransactionClient::NullTimestamp;
+};
+
+//! A helper for starting an alien transaction at #alienClient.
+/*!
+ * Internally, invokes #IClient::StartTransaction and #ITransaction::RegisterAlienTransaction
+ * but also takes care of the following issues:
+ * 1) Alien and local transaction ids must be same;
+ * 2) If #alienClient and #localTransaction have matching clusters then no
+ * new transaction must be created.
+ */
+TFuture<ITransactionPtr> StartAlienTransaction(
+ const ITransactionPtr& localTransaction,
+ const IClientPtr& alienClient,
+ const TAlienTransactionStartOptions& options);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
+
+#define TRANSACTION_INL_H_
+#include "transaction-inl.h"
+#undef TRANSACTION_INL_H_
diff --git a/yt/yt/client/api/transaction_client.h b/yt/yt/client/api/transaction_client.h
new file mode 100644
index 0000000000..272223886a
--- /dev/null
+++ b/yt/yt/client/api/transaction_client.h
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "client_common.h"
+
+#include <yt/yt/core/ytree/public.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTransactionStartOptions
+ : public TMutatingOptions
+{
+ std::optional<TDuration> Timeout;
+
+ //! If not null then the transaction must use this externally provided id.
+ //! Only applicable to tablet transactions.
+ NTransactionClient::TTransactionId Id;
+
+ NTransactionClient::TTransactionId ParentId;
+ std::vector<NTransactionClient::TTransactionId> PrerequisiteTransactionIds;
+
+ std::optional<TInstant> Deadline;
+
+ bool AutoAbort = true;
+ bool Sticky = false;
+
+ std::optional<TDuration> PingPeriod;
+ bool Ping = true;
+ bool PingAncestors = true;
+
+ NYTree::IAttributeDictionaryPtr Attributes;
+
+ NTransactionClient::EAtomicity Atomicity = NTransactionClient::EAtomicity::Full;
+ NTransactionClient::EDurability Durability = NTransactionClient::EDurability::Sync;
+
+ //! If not null then the transaction must use this externally provided start timestamp.
+ //! Only applicable to tablet transactions.
+ NTransactionClient::TTimestamp StartTimestamp = NTransactionClient::NullTimestamp;
+
+ //! For master transactions only; disables generating start timestamp.
+ bool SuppressStartTimestampGeneration = false;
+
+ //! Only for master transactions.
+ //! Indicates the master cell the transaction will be initially started at and controlled by
+ //! (chosen automatically by default).
+ NObjectClient::TCellTag CoordinatorMasterCellTag = NObjectClient::InvalidCellTag;
+
+ //! Only for master transactions.
+ //! Indicates the cells the transaction will be replicated to at the start. None by default,
+ //! but usually the transaction will be able to be replicated at a later time on demand.
+ std::optional<NObjectClient::TCellTagList> ReplicateToMasterCellTags;
+
+ //! Only for master transactions.
+ //! By default, all master transactions are Cypress expect for some
+ //! system ones (e.g. store flusher transactions).
+ bool StartCypressTransaction = true;
+};
+
+struct TTransactionAttachOptions
+{
+ bool AutoAbort = false;
+ std::optional<TDuration> PingPeriod;
+ bool Ping = true;
+ bool PingAncestors = false;
+
+ //! If non-empty, assumes that the transaction is sticky and specifies address of the transaction manager.
+ //! Throws if the transaction is not sticky actually.
+ //! Only supported by RPC proxy client for now. Ignored by other clients.
+ TString StickyAddress;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ITransactionClientBase
+{
+ virtual TFuture<ITransactionPtr> StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const TTransactionStartOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ITransactionClient
+{
+ virtual ITransactionPtr AttachTransaction(
+ NTransactionClient::TTransactionId transactionId,
+ const TTransactionAttachOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/arrow/CMakeLists.linux-aarch64.txt b/yt/yt/client/arrow/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..8807877265
--- /dev/null
+++ b/yt/yt/client/arrow/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,26 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(fbs)
+
+add_library(yt-client-arrow)
+target_compile_options(yt-client-arrow PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-client-arrow PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-client
+ client-arrow-fbs
+)
+target_sources(yt-client-arrow PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/arrow_row_stream_encoder.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/arrow_row_stream_decoder.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/public.cpp
+)
diff --git a/yt/yt/client/arrow/CMakeLists.linux-x86_64.txt b/yt/yt/client/arrow/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..8807877265
--- /dev/null
+++ b/yt/yt/client/arrow/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,26 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(fbs)
+
+add_library(yt-client-arrow)
+target_compile_options(yt-client-arrow PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-client-arrow PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-client
+ client-arrow-fbs
+)
+target_sources(yt-client-arrow PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/arrow_row_stream_encoder.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/arrow_row_stream_decoder.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/public.cpp
+)
diff --git a/yt/yt/client/arrow/CMakeLists.txt b/yt/yt/client/arrow/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/client/arrow/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/client/arrow/arrow_row_stream_decoder.cpp b/yt/yt/client/arrow/arrow_row_stream_decoder.cpp
new file mode 100644
index 0000000000..99277ef173
--- /dev/null
+++ b/yt/yt/client/arrow/arrow_row_stream_decoder.cpp
@@ -0,0 +1,20 @@
+#include "arrow_row_stream_decoder.h"
+
+namespace NYT::NArrow {
+
+using namespace NApi::NRpcProxy;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+IRowStreamDecoderPtr CreateArrowRowStreamDecoder(
+ TTableSchemaPtr /*schema*/,
+ TNameTablePtr /*nameTable*/)
+{
+ THROW_ERROR_EXCEPTION("Arrow decoder is not implemented yet");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NArrow
+
diff --git a/yt/yt/client/arrow/arrow_row_stream_decoder.h b/yt/yt/client/arrow/arrow_row_stream_decoder.h
new file mode 100644
index 0000000000..be8a7cab29
--- /dev/null
+++ b/yt/yt/client/arrow/arrow_row_stream_decoder.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/rpc_proxy/public.h>
+
+#include <yt/yt/client/table_client/public.h>
+
+namespace NYT::NArrow {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::NRpcProxy::IRowStreamDecoderPtr CreateArrowRowStreamDecoder(
+ NTableClient::TTableSchemaPtr schema,
+ NTableClient::TNameTablePtr nameTable);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NArrow
diff --git a/yt/yt/client/arrow/arrow_row_stream_encoder.cpp b/yt/yt/client/arrow/arrow_row_stream_encoder.cpp
new file mode 100644
index 0000000000..a7c1289e13
--- /dev/null
+++ b/yt/yt/client/arrow/arrow_row_stream_encoder.cpp
@@ -0,0 +1,1143 @@
+#include "arrow_row_stream_encoder.h"
+
+#include <yt/yt/client/arrow/fbs/Message.fbs.h>
+#include <yt/yt/client/arrow/fbs/Schema.fbs.h>
+
+#include <yt/yt/client/api/rpc_proxy/row_stream.h>
+#include <yt/yt/client/api/rpc_proxy/wire_row_stream.h>
+
+#include <yt/yt/client/formats/config.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_batch.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/columnar.h>
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/range.h>
+
+#include <util/system/align.h>
+
+namespace NYT::NArrow {
+
+using namespace NApi::NRpcProxy;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto& Logger = ArrowLogger;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+using TBatchColumn = IUnversionedColumnarRowBatch::TColumn;
+using TBodyWriter = std::function<void(TMutableRef)>;
+
+constexpr i64 ArrowAlignment = 8;
+
+flatbuffers::Offset<flatbuffers::String> SerializeString(
+ flatbuffers::FlatBufferBuilder* flatbufBuilder,
+ const TString& str)
+{
+ return flatbufBuilder->CreateString(str.data(), str.length());
+}
+
+std::tuple<org::apache::arrow::flatbuf::Type, flatbuffers::Offset<void>> SerializeColumnType(
+ flatbuffers::FlatBufferBuilder* flatbufBuilder,
+ const TColumnSchema& schema)
+{
+ auto simpleType = CastToV1Type(schema.LogicalType()).first;
+ switch (simpleType) {
+ case ESimpleLogicalValueType::Null:
+ return std::make_tuple(
+ org::apache::arrow::flatbuf::Type_Null,
+ org::apache::arrow::flatbuf::CreateNull(*flatbufBuilder)
+ .Union());
+
+ case ESimpleLogicalValueType::Int64:
+ case ESimpleLogicalValueType::Uint64:
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Uint32:
+ return std::make_tuple(
+ org::apache::arrow::flatbuf::Type_Int,
+ org::apache::arrow::flatbuf::CreateInt(
+ *flatbufBuilder,
+ GetIntegralTypeBitWidth(simpleType),
+ IsIntegralTypeSigned(simpleType)).Union());
+
+ case ESimpleLogicalValueType::Double:
+ return std::make_tuple(
+ org::apache::arrow::flatbuf::Type_FloatingPoint,
+ org::apache::arrow::flatbuf::CreateFloatingPoint(
+ *flatbufBuilder,
+ org::apache::arrow::flatbuf::Precision_DOUBLE)
+ .Union());
+
+ case ESimpleLogicalValueType::Boolean:
+ return std::make_tuple(
+ org::apache::arrow::flatbuf::Type_Bool,
+ org::apache::arrow::flatbuf::CreateBool(*flatbufBuilder)
+ .Union());
+
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Any:
+ return std::make_tuple(
+ org::apache::arrow::flatbuf::Type_Binary,
+ org::apache::arrow::flatbuf::CreateBinary(*flatbufBuilder)
+ .Union());
+
+ case ESimpleLogicalValueType::Utf8:
+ return std::make_tuple(
+ org::apache::arrow::flatbuf::Type_Utf8,
+ org::apache::arrow::flatbuf::CreateUtf8(*flatbufBuilder)
+ .Union());
+
+ // TODO(babenko): the following types are not supported:
+ // Date
+ // Datetime
+ // Interval
+ // Timestamp
+
+ default:
+ THROW_ERROR_EXCEPTION("Column %v has type %Qlv that is not currently supported by Arrow encoder",
+ schema.GetDiagnosticNameString(),
+ simpleType);
+ }
+}
+
+bool IsRleButNotDictionaryEncodedStringLikeColumn(const TBatchColumn& column)
+{
+ auto simpleType = CastToV1Type(column.Type).first;
+ return
+ IsStringLikeType(simpleType) &&
+ column.Rle &&
+ !column.Rle->ValueColumn->Dictionary;
+}
+
+bool IsRleAndDictionaryEncodedColumn(const TBatchColumn& column)
+{
+ return
+ column.Rle &&
+ column.Rle->ValueColumn->Dictionary;
+}
+
+bool IsDictionaryEncodedColumn(const TBatchColumn& column)
+{
+ return
+ column.Dictionary ||
+ IsRleAndDictionaryEncodedColumn(column) ||
+ IsRleButNotDictionaryEncodedStringLikeColumn(column);
+}
+
+struct TTypedBatchColumn
+{
+ const TBatchColumn* Column;
+ TLogicalTypePtr Type;
+};
+
+struct TRecordBatchBodyPart
+{
+ i64 Size;
+ TBodyWriter Writer;
+};
+
+struct TRecordBatchSerializationContext final
+{
+ explicit TRecordBatchSerializationContext(flatbuffers::FlatBufferBuilder* flatbufBuilder)
+ : FlatbufBuilder(flatbufBuilder)
+ { }
+
+ void AddFieldNode(i64 length, i64 nullCount)
+ {
+ FieldNodes.emplace_back(length, nullCount);
+ }
+
+ void AddBuffer(i64 size, TBodyWriter writer)
+ {
+ YT_LOG_DEBUG("Buffer registered (Offset: %v, Size: %v)",
+ CurrentBodyOffset,
+ size);
+
+ Buffers.emplace_back(CurrentBodyOffset, size);
+ CurrentBodyOffset += AlignUp<i64>(size, ArrowAlignment);
+ Parts.push_back(TRecordBatchBodyPart{size, std::move(writer)});
+ }
+
+ flatbuffers::FlatBufferBuilder* const FlatbufBuilder;
+
+ i64 CurrentBodyOffset = 0;
+ std::vector<org::apache::arrow::flatbuf::FieldNode> FieldNodes;
+ std::vector<org::apache::arrow::flatbuf::Buffer> Buffers;
+ std::vector<TRecordBatchBodyPart> Parts;
+};
+
+template <class T>
+TMutableRange<T> GetTypedValues(TMutableRef ref)
+{
+ return MakeMutableRange(
+ reinterpret_cast<T*>(ref.Begin()),
+ reinterpret_cast<T*>(ref.End()));
+}
+
+void SerializeColumnPrologue(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ if (column->NullBitmap ||
+ column->Rle && column->Rle->ValueColumn->NullBitmap)
+ {
+ if (column->Rle) {
+ const auto* valueColumn = column->Rle->ValueColumn;
+ auto rleIndexes = column->GetTypedValues<ui64>();
+
+ context->AddFieldNode(
+ column->ValueCount,
+ CountOnesInRleBitmap(
+ valueColumn->NullBitmap->Data,
+ rleIndexes,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount));
+
+ context->AddBuffer(
+ GetBitmapByteSize(column->ValueCount),
+ [=] (TMutableRef dstRef) {
+ BuildValidityBitmapFromRleNullBitmap(
+ valueColumn->NullBitmap->Data,
+ rleIndexes,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount,
+ dstRef);
+ });
+ } else {
+ context->AddFieldNode(
+ column->ValueCount,
+ CountOnesInBitmap(
+ column->NullBitmap->Data,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount));
+
+ context->AddBuffer(
+ GetBitmapByteSize(column->ValueCount),
+ [=] (TMutableRef dstRef) {
+ CopyBitmapRangeToBitmapNegated(
+ column->NullBitmap->Data,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount,
+ dstRef);
+ });
+ }
+ } else {
+ context->AddFieldNode(
+ column->ValueCount,
+ 0);
+
+ context->AddBuffer(
+ 0,
+ [=] (TMutableRef /*dstRef*/) { });
+ }
+}
+
+void SerializeRleButNotDictionaryEncodedStringLikeColumn(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ YT_VERIFY(column->Values);
+ YT_VERIFY(column->Values->BitWidth == 64);
+ YT_VERIFY(column->Values->BaseValue == 0);
+ YT_VERIFY(!column->Values->ZigZagEncoded);
+
+ YT_LOG_DEBUG("Adding RLE but not dictionary-encoded string-like column (ColumnId: %v, StartIndex: %v, ValueCount: %v)",
+ column->Id,
+ column->StartIndex,
+ column->ValueCount);
+
+ SerializeColumnPrologue(typedColumn, context);
+
+ auto rleIndexes = column->GetTypedValues<ui64>();
+
+ context->AddBuffer(
+ sizeof (ui32) * column->ValueCount,
+ [=] (TMutableRef dstRef) {
+ BuildIotaDictionaryIndexesFromRleIndexes(
+ rleIndexes,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount,
+ GetTypedValues<ui32>(dstRef));
+ });
+}
+
+void SerializeDictionaryColumn(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ YT_VERIFY(column->Values);
+ YT_VERIFY(column->Dictionary->ZeroMeansNull);
+ YT_VERIFY(column->Values->BitWidth == 32);
+ YT_VERIFY(column->Values->BaseValue == 0);
+ YT_VERIFY(!column->Values->ZigZagEncoded);
+
+ YT_LOG_DEBUG("Adding dictionary column (ColumnId: %v, StartIndex: %v, ValueCount: %v, Rle: %v)",
+ column->Id,
+ column->StartIndex,
+ column->ValueCount,
+ column->Rle.has_value());
+
+ auto relevantDictionaryIndexes = column->GetRelevantTypedValues<ui32>();
+
+ context->AddFieldNode(
+ column->ValueCount,
+ CountNullsInDictionaryIndexesWithZeroNull(relevantDictionaryIndexes));
+
+ context->AddBuffer(
+ GetBitmapByteSize(column->ValueCount),
+ [=] (TMutableRef dstRef) {
+ BuildValidityBitmapFromDictionaryIndexesWithZeroNull(
+ relevantDictionaryIndexes,
+ dstRef);
+ });
+
+ context->AddBuffer(
+ sizeof (ui32) * column->ValueCount,
+ [=] (TMutableRef dstRef) {
+ BuildDictionaryIndexesFromDictionaryIndexesWithZeroNull(
+ relevantDictionaryIndexes,
+ GetTypedValues<ui32>(dstRef));
+ });
+}
+
+void SerializeRleDictionaryColumn(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ YT_VERIFY(column->Values);
+ YT_VERIFY(column->Values->BitWidth == 64);
+ YT_VERIFY(column->Values->BaseValue == 0);
+ YT_VERIFY(!column->Values->ZigZagEncoded);
+ YT_VERIFY(column->Rle->ValueColumn->Dictionary->ZeroMeansNull);
+ YT_VERIFY(column->Rle->ValueColumn->Values->BitWidth == 32);
+ YT_VERIFY(column->Rle->ValueColumn->Values->BaseValue == 0);
+ YT_VERIFY(!column->Rle->ValueColumn->Values->ZigZagEncoded);
+
+ YT_LOG_DEBUG("Adding dictionary column (ColumnId: %v, StartIndex: %v, ValueCount: %v, Rle: %v)",
+ column->Id,
+ column->StartIndex,
+ column->ValueCount,
+ column->Rle.has_value());
+
+ auto dictionaryIndexes = column->Rle->ValueColumn->GetTypedValues<ui32>();
+ auto rleIndexes = column->GetTypedValues<ui64>();
+
+ context->AddFieldNode(
+ column->ValueCount,
+ CountNullsInRleDictionaryIndexesWithZeroNull(
+ dictionaryIndexes,
+ rleIndexes,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount));
+
+ context->AddBuffer(
+ GetBitmapByteSize(column->ValueCount),
+ [=] (TMutableRef dstRef) {
+ BuildValidityBitmapFromRleDictionaryIndexesWithZeroNull(
+ dictionaryIndexes,
+ rleIndexes,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount,
+ dstRef);
+ });
+
+ context->AddBuffer(
+ sizeof (ui32) * column->ValueCount,
+ [=] (TMutableRef dstRef) {
+ BuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNull(
+ dictionaryIndexes,
+ rleIndexes,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount,
+ GetTypedValues<ui32>(dstRef));
+ });
+}
+
+void SerializeIntegerColumn(
+ const TTypedBatchColumn& typedColumn,
+ ESimpleLogicalValueType simpleType,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ YT_VERIFY(column->Values);
+
+ YT_LOG_DEBUG("Adding integer column (ColumnId: %v, StartIndex: %v, ValueCount: %v, Rle: %v)",
+ column->Id,
+ column->StartIndex,
+ column->ValueCount,
+ column->Rle.has_value());
+
+ SerializeColumnPrologue(typedColumn, context);
+
+ context->AddBuffer(
+ column->ValueCount * GetIntegralTypeByteSize(simpleType),
+ [=] (TMutableRef dstRef) {
+ const auto* valueColumn = column->Rle
+ ? column->Rle->ValueColumn
+ : column;
+ auto values = valueColumn->GetTypedValues<ui64>();
+
+ auto rleIndexes = column->Rle
+ ? column->GetTypedValues<ui64>()
+ : TRange<ui64>();
+
+ switch (simpleType) {
+ #define XX(cppType, ytType) \
+ case ESimpleLogicalValueType::ytType: { \
+ auto dstValues = GetTypedValues<cppType>(dstRef); \
+ auto* currentOutput = dstValues.Begin(); \
+ DecodeIntegerVector( \
+ column->StartIndex, \
+ column->StartIndex + column->ValueCount, \
+ valueColumn->Values->BaseValue, \
+ valueColumn->Values->ZigZagEncoded, \
+ TRange<ui32>(), \
+ rleIndexes, \
+ [&] (auto index) { \
+ return values[index]; \
+ }, \
+ [&] (auto value) { \
+ *currentOutput++ = value; \
+ }); \
+ break; \
+ }
+
+ XX( i8, Int8)
+ XX( i16, Int16)
+ XX( i32, Int32)
+ XX( i64, Int64)
+ XX( ui8, Uint8)
+ XX(ui16, Uint16)
+ XX(ui32, Uint32)
+ XX(ui64, Uint64)
+
+ #undef XX
+
+ default:
+ THROW_ERROR_EXCEPTION("Integer column %v has unexpected type %Qlv",
+ typedColumn.Column->Id,
+ simpleType);
+ }
+ });
+}
+
+void SerializeDoubleColumn(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ YT_VERIFY(column->Values);
+ YT_VERIFY(column->Values->BitWidth == 64);
+ YT_VERIFY(column->Values->BaseValue == 0);
+ YT_VERIFY(!column->Values->ZigZagEncoded);
+
+ YT_LOG_DEBUG("Adding double column (ColumnId: %v, StartIndex: %v, ValueCount: %v)",
+ column->Id,
+ column->StartIndex,
+ column->ValueCount,
+ column->Rle.has_value());
+
+ SerializeColumnPrologue(typedColumn, context);
+
+ context->AddBuffer(
+ column->ValueCount * sizeof(double),
+ [=] (TMutableRef dstRef) {
+ auto relevantValues = column->GetRelevantTypedValues<double>();
+ ::memcpy(
+ dstRef.Begin(),
+ relevantValues.Begin(),
+ column->ValueCount * sizeof(double));
+ });
+}
+
+void SerializeStringLikeColumn(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ YT_VERIFY(column->Values);
+ YT_VERIFY(column->Values->BaseValue == 0);
+ YT_VERIFY(column->Values->BitWidth == 32);
+ YT_VERIFY(column->Values->ZigZagEncoded);
+ YT_VERIFY(column->Strings);
+ YT_VERIFY(column->Strings->AvgLength);
+ YT_VERIFY(!column->Rle);
+
+ auto startIndex = column->StartIndex;
+ auto endIndex = startIndex + column->ValueCount;
+ auto stringData = column->Strings->Data;
+ auto avgLength = *column->Strings->AvgLength;
+
+ auto offsets = column->GetTypedValues<ui32>();
+ auto startOffset = DecodeStringOffset(offsets, avgLength, startIndex);
+ auto endOffset = DecodeStringOffset(offsets, avgLength, endIndex);
+ auto stringsSize = endOffset - startOffset;
+
+ YT_LOG_DEBUG("Adding string-like column (ColumnId: %v, StartIndex: %v, ValueCount: %v, StartOffset: %v, EndOffset: %v, StringsSize: %v)",
+ column->Id,
+ column->StartIndex,
+ column->ValueCount,
+ startOffset,
+ endOffset,
+ stringsSize);
+
+ SerializeColumnPrologue(typedColumn, context);
+
+ context->AddBuffer(
+ sizeof(i32) * (column->ValueCount + 1),
+ [=] (TMutableRef dstRef) {
+ DecodeStringOffsets(
+ offsets,
+ avgLength,
+ startIndex,
+ endIndex,
+ GetTypedValues<ui32>(dstRef));
+ });
+
+ context->AddBuffer(
+ stringsSize,
+ [=] (TMutableRef dstRef) {
+ ::memcpy(
+ dstRef.Begin(),
+ stringData.Begin() + startOffset,
+ stringsSize);
+ });
+}
+
+void SerializeBooleanColumn(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+ YT_VERIFY(column->Values);
+ YT_VERIFY(!column->Values->ZigZagEncoded);
+ YT_VERIFY(column->Values->BaseValue == 0);
+ YT_VERIFY(column->Values->BitWidth == 1);
+
+ YT_LOG_DEBUG("Adding boolean column (ColumnId: %v, StartIndex: %v, ValueCount: %v)",
+ column->Id,
+ column->StartIndex,
+ column->ValueCount);
+
+ SerializeColumnPrologue(typedColumn, context);
+
+ context->AddBuffer(
+ GetBitmapByteSize(column->ValueCount),
+ [=] (TMutableRef dstRef) {
+ CopyBitmapRangeToBitmap(
+ column->Values->Data,
+ column->StartIndex,
+ column->StartIndex + column->ValueCount,
+ dstRef);
+ });
+}
+
+void SerializeColumn(
+ const TTypedBatchColumn& typedColumn,
+ TRecordBatchSerializationContext* context)
+{
+ const auto* column = typedColumn.Column;
+
+ if (IsRleButNotDictionaryEncodedStringLikeColumn(*typedColumn.Column)) {
+ SerializeRleButNotDictionaryEncodedStringLikeColumn(typedColumn, context);
+ return;
+ }
+
+ if (column->Dictionary) {
+ SerializeDictionaryColumn(typedColumn, context);
+ return;
+ }
+
+ if (column->Rle && column->Rle->ValueColumn->Dictionary) {
+ SerializeRleDictionaryColumn(typedColumn, context);
+ return;
+ }
+
+ auto simpleType = CastToV1Type(typedColumn.Type).first;
+ if (IsIntegralType(simpleType)) {
+ SerializeIntegerColumn(typedColumn, simpleType, context);
+ } else if (simpleType == ESimpleLogicalValueType::Double) {
+ SerializeDoubleColumn(typedColumn, context);
+ } else if (IsStringLikeType(simpleType)) {
+ SerializeStringLikeColumn(typedColumn, context);
+ } else if (simpleType == ESimpleLogicalValueType::Boolean) {
+ SerializeBooleanColumn(typedColumn, context);
+ } else if (simpleType == ESimpleLogicalValueType::Null) {
+ // No buffers are allocated for null columns.
+ } else {
+ THROW_ERROR_EXCEPTION("Column %v has unexpected type %Qlv",
+ typedColumn.Column->Id,
+ simpleType);
+ }
+}
+
+auto SerializeRecordBatch(
+ flatbuffers::FlatBufferBuilder* flatbufBuilder,
+ int length,
+ TRange<TTypedBatchColumn> typedColumns)
+{
+ auto context = New<TRecordBatchSerializationContext>(flatbufBuilder);
+
+ for (const auto& typedColumn : typedColumns) {
+ SerializeColumn(typedColumn, context.Get());
+ }
+
+ auto fieldNodesOffset = flatbufBuilder->CreateVectorOfStructs(context->FieldNodes);
+
+ auto buffersOffset = flatbufBuilder->CreateVectorOfStructs(context->Buffers);
+
+ auto recordBatchOffset = org::apache::arrow::flatbuf::CreateRecordBatch(
+ *flatbufBuilder,
+ length,
+ fieldNodesOffset,
+ buffersOffset);
+
+ auto totalSize = context->CurrentBodyOffset;
+
+ return std::make_tuple(
+ recordBatchOffset,
+ totalSize,
+ [context = std::move(context)] (TMutableRef dstRef) {
+ char* current = dstRef.Begin();
+ for (const auto& part : context->Parts) {
+ part.Writer(TMutableRef(current, current + part.Size));
+ current += AlignUp<i64>(part.Size, ArrowAlignment);
+ }
+ YT_VERIFY(current == dstRef.End());
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TArrowRowStreamEncoder)
+
+class TArrowRowStreamEncoder
+ : public IRowStreamEncoder
+{
+public:
+ TArrowRowStreamEncoder(
+ TTableSchemaPtr schema,
+ TNameTablePtr nameTable,
+ IRowStreamEncoderPtr fallbackEncoder,
+ NFormats::TControlAttributesConfigPtr controlAttributesConfig)
+ : Schema_(std::move(schema))
+ , NameTable_(std::move(nameTable))
+ , FallbackEncoder_(std::move(fallbackEncoder))
+ , ControlAttributesConfig_(controlAttributesConfig)
+ {
+ if (ControlAttributesConfig_->EnableRowIndex) {
+ RowIndexId_ = NameTable_->GetIdOrRegisterName(RowIndexColumnName);
+ }
+
+ if (ControlAttributesConfig_->EnableRangeIndex) {
+ RangeIndexId_ = NameTable_->GetIdOrRegisterName(RangeIndexColumnName);
+ }
+
+ if (ControlAttributesConfig_->EnableTableIndex) {
+ TableIndexId_ = NameTable_->GetIdOrRegisterName(TableIndexColumnName);
+ }
+
+ if (ControlAttributesConfig_->EnableTabletIndex) {
+ TabletIndexId_ = NameTable_->GetIdOrRegisterName(TabletIndexColumnName);
+ }
+
+ YT_LOG_DEBUG("Row stream encoder created (Schema: %v)",
+ *Schema_);
+ }
+
+ const TTableSchemaPtr& GetSchema()
+ {
+ return Schema_;
+ }
+
+ const TNameTablePtr& GetNameTable()
+ {
+ return NameTable_;
+ }
+
+ bool IsFirstBatch()
+ {
+ return FirstBatch_;
+ }
+
+ std::vector<IUnversionedColumnarRowBatch::TDictionaryId>& ArrowDictionaryIds()
+ {
+ return ArrowDictionaryIds_;
+ }
+
+ bool IsTableIndexColumnId(int id) const
+ {
+ return id == TableIndexId_;
+ }
+
+ bool IsRowIndexColumnId(int id) const
+ {
+ return id == RowIndexId_;
+ }
+
+ bool IsRangeIndexColumnId(int id) const
+ {
+ return id == RangeIndexId_;
+ }
+
+ bool IsTabletIndexColumnId(int id) const
+ {
+ return id == TabletIndexId_;
+ }
+
+ bool IsSystemColumnId(int id) const
+ {
+ return IsTableIndexColumnId(id) ||
+ IsRangeIndexColumnId(id) ||
+ IsRowIndexColumnId(id) ||
+ IsTabletIndexColumnId(id);
+ }
+
+ bool IsSystemColumnEnable(int columnIdx) {
+ return ControlAttributesConfig_->EnableTableIndex && IsTableIndexColumnId(columnIdx) ||
+ ControlAttributesConfig_->EnableRangeIndex && IsRangeIndexColumnId(columnIdx) ||
+ ControlAttributesConfig_->EnableRowIndex && IsRowIndexColumnId(columnIdx) ||
+ ControlAttributesConfig_->EnableTabletIndex && IsTabletIndexColumnId(columnIdx);
+ }
+
+ TSharedRef Encode(
+ const IUnversionedRowBatchPtr& batch,
+ const NApi::NRpcProxy::NProto::TRowsetStatistics* statistics) override;
+
+private:
+ const TTableSchemaPtr Schema_;
+ const TNameTablePtr NameTable_;
+ const IRowStreamEncoderPtr FallbackEncoder_;
+ const NFormats::TControlAttributesConfigPtr ControlAttributesConfig_;
+
+ int RowIndexId_ = -1;
+ int RangeIndexId_ = -1;
+ int TableIndexId_ = -1;
+ int TabletIndexId_ = -1;
+
+ bool FirstBatch_ = true;
+ std::vector<IUnversionedColumnarRowBatch::TDictionaryId> ArrowDictionaryIds_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TArrowRowStreamEncoder)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TArrowRowStreamBlockEncoder
+{
+public:
+ TArrowRowStreamBlockEncoder(
+ TArrowRowStreamEncoderPtr streamEncoder,
+ IUnversionedColumnarRowBatchPtr batch)
+ : StreamEncoder_(std::move(streamEncoder))
+ , Batch_(std::move(batch))
+ {
+ PrepareColumns();
+ if (IsSchemaMessageNeeded()) {
+ if (!StreamEncoder_->IsFirstBatch()) {
+ RegisterEosMarker();
+ }
+ ResetArrowDictionaries();
+ PrepareSchema();
+ }
+ PrepareDictionaryBatches();
+ PrepareRecordBatch();
+ }
+
+ i64 GetPayloadSize() const
+ {
+ i64 size = 0;
+ for (const auto& message : Messages_) {
+ size += sizeof (ui32); // continuation indicator
+ size += sizeof (ui32); // metadata size
+ if (message.FlatbufBuilder) {
+ size += AlignUp<i64>(message.FlatbufBuilder->GetSize(), ArrowAlignment); // metadata message
+ size += AlignUp<i64>(message.BodySize, ArrowAlignment); // body
+ }
+ }
+ return size;
+ }
+
+ void WritePayload(TMutableRef payloadRef)
+ {
+ YT_LOG_DEBUG("Started writing payload (Size: %v)",
+ payloadRef.Size());
+ char* current = payloadRef.Begin();
+ for (const auto& message : Messages_) {
+ // Continuation indicator
+ *reinterpret_cast<ui32*>(current) = 0xFFFFFFFF;
+ current += sizeof(ui32);
+
+ if (message.FlatbufBuilder) {
+ auto metadataSize = message.FlatbufBuilder->GetSize();
+ auto* metadataPtr = message.FlatbufBuilder->GetBufferPointer();
+
+ // Metadata size
+ *reinterpret_cast<ui32*>(current) = AlignUp<i64>(metadataSize, ArrowAlignment);
+ current += sizeof(ui32);
+
+ // Metadata message
+ ::memcpy(current, metadataPtr, metadataSize);
+ current += AlignUp<i64>(metadataSize, ArrowAlignment);
+
+ // Body
+ if (message.BodyWriter) {
+ message.BodyWriter(TMutableRef(current, current + message.BodySize));
+ current += AlignUp<i64>(message.BodySize, ArrowAlignment);
+ } else {
+ YT_VERIFY(message.BodySize == 0);
+ }
+ } else {
+ // EOS marker
+ *reinterpret_cast<ui32*>(current) = 0;
+ current += sizeof(ui32);
+ }
+ }
+ YT_VERIFY(current == payloadRef.End());
+ YT_LOG_DEBUG("Finished writing payload");
+ }
+
+private:
+ const TArrowRowStreamEncoderPtr StreamEncoder_;
+ const IUnversionedColumnarRowBatchPtr Batch_;
+
+ std::vector<TTypedBatchColumn> TypedColumns_;
+
+ struct TMessage
+ {
+ std::optional<flatbuffers::FlatBufferBuilder> FlatbufBuilder;
+ i64 BodySize;
+ TBodyWriter BodyWriter;
+ };
+
+ std::vector<TMessage> Messages_;
+
+ void RegisterEosMarker()
+ {
+ YT_LOG_DEBUG("EOS marker registered");
+
+ Messages_.push_back(TMessage{
+ std::nullopt,
+ 0,
+ TBodyWriter()
+ });
+ }
+
+ void RegisterMessage(
+ org::apache::arrow::flatbuf::MessageHeader type,
+ flatbuffers::FlatBufferBuilder&& flatbufBuilder,
+ i64 bodySize = 0,
+ std::function<void(TMutableRef)> bodyWriter = nullptr)
+ {
+ YT_LOG_DEBUG("Message registered (Type: %v, MessageSize: %v, BodySize: %v)",
+ org::apache::arrow::flatbuf::EnumNamesMessageHeader()[type],
+ flatbufBuilder.GetSize(),
+ bodySize);
+
+ YT_VERIFY((bodySize % ArrowAlignment) == 0);
+ Messages_.push_back(TMessage{
+ std::move(flatbufBuilder),
+ bodySize,
+ std::move(bodyWriter)
+ });
+ }
+
+ std::optional<TColumnSchema> FindColumnSchema(const TBatchColumn& column)
+ {
+ YT_VERIFY(column.Id >= 0);
+ auto name = StreamEncoder_->GetNameTable()->GetName(column.Id);
+ auto columnSchemaPtr = StreamEncoder_->GetSchema()->FindColumn(name);
+ if (!columnSchemaPtr) {
+ if (StreamEncoder_->IsSystemColumnId(column.Id) && StreamEncoder_->IsSystemColumnEnable(column.Id)) {
+ return TColumnSchema(TString(name), EValueType::Int64);
+ }
+ return std::nullopt;
+ }
+ auto columnSchema = *columnSchemaPtr;
+ if (CastToV1Type(columnSchema.LogicalType()).first != ESimpleLogicalValueType::Null) {
+ return columnSchema;
+ }
+ return std::nullopt;
+ }
+
+ void PrepareColumns()
+ {
+ auto batchColumns = Batch_->MaterializeColumns();
+ TypedColumns_.reserve(batchColumns.Size());
+ for (const auto* column : batchColumns) {
+ // Ignoring null schema column and not enabled system columns.
+ auto columnSchema = FindColumnSchema(*column);
+ if (columnSchema) {
+ TypedColumns_.push_back(TTypedBatchColumn{
+ column,
+ columnSchema->LogicalType()
+ });
+ }
+ }
+ }
+
+ bool IsSchemaMessageNeeded()
+ {
+ if (StreamEncoder_->IsFirstBatch()) {
+ return true;
+ }
+
+ YT_VERIFY(StreamEncoder_->ArrowDictionaryIds().size() == TypedColumns_.size());
+
+ bool result = StreamEncoder_->IsFirstBatch();
+ for (int index = 0; index < std::ssize(TypedColumns_); ++index) {
+ bool currentDictionary = IsDictionaryEncodedColumn(*TypedColumns_[index].Column);
+ bool previousDictionary = StreamEncoder_->ArrowDictionaryIds()[index] != IUnversionedColumnarRowBatch::NullDictionaryId;
+ if (currentDictionary != previousDictionary) {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ void ResetArrowDictionaries()
+ {
+ StreamEncoder_->ArrowDictionaryIds().assign(TypedColumns_.size(), IUnversionedColumnarRowBatch::NullDictionaryId);
+ }
+
+
+ void PrepareSchema()
+ {
+ flatbuffers::FlatBufferBuilder flatbufBuilder;
+
+ int arrowDictionaryIdCounter = 0;
+ std::vector<flatbuffers::Offset<org::apache::arrow::flatbuf::Field>> fieldOffsets;
+
+ for (const auto& typedColumn : TypedColumns_) {
+ auto optionalColumnSchema = FindColumnSchema(*typedColumn.Column);
+ YT_VERIFY(optionalColumnSchema != std::nullopt);
+ auto columnSchema = *optionalColumnSchema;
+
+ auto nameOffset = SerializeString(&flatbufBuilder, columnSchema.Name());
+
+ auto [typeType, typeOffset] = SerializeColumnType(&flatbufBuilder, columnSchema);
+
+ flatbuffers::Offset<org::apache::arrow::flatbuf::DictionaryEncoding> dictionaryEncodingOffset;
+
+ auto indexTypeOffset = org::apache::arrow::flatbuf::CreateInt(flatbufBuilder, 32, false);
+
+ if (IsDictionaryEncodedColumn(*typedColumn.Column)) {
+ dictionaryEncodingOffset = org::apache::arrow::flatbuf::CreateDictionaryEncoding(
+ flatbufBuilder,
+ arrowDictionaryIdCounter++,
+ indexTypeOffset);
+ }
+
+ auto fieldOffset = org::apache::arrow::flatbuf::CreateField(
+ flatbufBuilder,
+ nameOffset,
+ columnSchema.LogicalType()->IsNullable(),
+ typeType,
+ typeOffset,
+ dictionaryEncodingOffset);
+
+ fieldOffsets.push_back(fieldOffset);
+ }
+
+ auto fieldsOffset = flatbufBuilder.CreateVector(fieldOffsets);
+
+ auto schemaOffset = org::apache::arrow::flatbuf::CreateSchema(
+ flatbufBuilder,
+ org::apache::arrow::flatbuf::Endianness_Little,
+ fieldsOffset);
+
+ auto messageOffset = org::apache::arrow::flatbuf::CreateMessage(
+ flatbufBuilder,
+ org::apache::arrow::flatbuf::MetadataVersion_V4,
+ org::apache::arrow::flatbuf::MessageHeader_Schema,
+ schemaOffset.Union(),
+ 0);
+
+ flatbufBuilder.Finish(messageOffset);
+
+ RegisterMessage(
+ org::apache::arrow::flatbuf::MessageHeader_Schema,
+ std::move(flatbufBuilder));
+ }
+
+ void PrepareDictionaryBatches()
+ {
+ int arrowDictionaryIdCounter = 0;
+ auto prepareDictionaryBatch = [&] (
+ int columnIndex,
+ IUnversionedColumnarRowBatch::TDictionaryId ytDictionaryId,
+ const TBatchColumn* dictionaryColumn)
+ {
+ int arrowDictionaryId = arrowDictionaryIdCounter++;
+ const auto& typedColumn = TypedColumns_[columnIndex];
+ auto previousYTDictionaryId = StreamEncoder_->ArrowDictionaryIds()[columnIndex];
+ if (ytDictionaryId == previousYTDictionaryId) {
+ YT_LOG_DEBUG("Reusing previous dictionary (ColumnId: %v, YTDictionaryId: %v, ArrowDictionaryId: %v)",
+ typedColumn.Column->Id,
+ ytDictionaryId,
+ arrowDictionaryId);
+ } else {
+ YT_LOG_DEBUG("Sending new dictionary (ColumnId: %v, YTDictionaryId: %v, ArrowDictionaryId: %v)",
+ typedColumn.Column->Id,
+ ytDictionaryId,
+ arrowDictionaryId);
+ PrepareDictionaryBatch(
+ TTypedBatchColumn{dictionaryColumn, typedColumn.Type},
+ arrowDictionaryId);
+ StreamEncoder_->ArrowDictionaryIds()[columnIndex] = ytDictionaryId;
+ }
+ };
+
+ for (int columnIndex = 0; columnIndex < std::ssize(TypedColumns_); ++columnIndex) {
+ const auto& typedColumn = TypedColumns_[columnIndex];
+ if (typedColumn.Column->Dictionary) {
+ YT_LOG_DEBUG("Adding dictionary batch for dictionary-encoded column (ColumnId: %v)",
+ typedColumn.Column->Id);
+ prepareDictionaryBatch(
+ columnIndex,
+ typedColumn.Column->Dictionary->DictionaryId,
+ typedColumn.Column->Dictionary->ValueColumn);
+ } else if (IsRleButNotDictionaryEncodedStringLikeColumn(*typedColumn.Column)) {
+ YT_LOG_DEBUG("Adding dictionary batch for RLE but not dictionary-encoded string-like column (ColumnId: %v)",
+ typedColumn.Column->Id);
+ prepareDictionaryBatch(
+ columnIndex,
+ IUnversionedColumnarRowBatch::GenerateDictionaryId(), // any unique one will do
+ typedColumn.Column->Rle->ValueColumn);
+ } else if (IsRleAndDictionaryEncodedColumn(*typedColumn.Column)) {
+ YT_LOG_DEBUG("Adding dictionary batch for RLE and dictionary-encoded column (ColumnId: %v)",
+ typedColumn.Column->Id);
+ prepareDictionaryBatch(
+ columnIndex,
+ typedColumn.Column->Rle->ValueColumn->Dictionary->DictionaryId,
+ typedColumn.Column->Rle->ValueColumn->Dictionary->ValueColumn);
+ }
+ }
+ }
+
+ void PrepareDictionaryBatch(
+ const TTypedBatchColumn& typedColumn,
+ int arrowDictionaryId)
+ {
+ flatbuffers::FlatBufferBuilder flatbufBuilder;
+
+ auto [recordBatchOffset, bodySize, bodyWriter] = SerializeRecordBatch(
+ &flatbufBuilder,
+ typedColumn.Column->ValueCount,
+ MakeRange({typedColumn}));
+
+ auto dictionaryBatchOffset = org::apache::arrow::flatbuf::CreateDictionaryBatch(
+ flatbufBuilder,
+ arrowDictionaryId,
+ recordBatchOffset);
+
+ auto messageOffset = org::apache::arrow::flatbuf::CreateMessage(
+ flatbufBuilder,
+ org::apache::arrow::flatbuf::MetadataVersion_V4,
+ org::apache::arrow::flatbuf::MessageHeader_DictionaryBatch,
+ dictionaryBatchOffset.Union(),
+ bodySize);
+
+ flatbufBuilder.Finish(messageOffset);
+
+ RegisterMessage(
+ org::apache::arrow::flatbuf::MessageHeader_DictionaryBatch,
+ std::move(flatbufBuilder),
+ bodySize,
+ std::move(bodyWriter));
+ }
+
+ void PrepareRecordBatch()
+ {
+ flatbuffers::FlatBufferBuilder flatbufBuilder;
+
+ auto [recordBatchOffset, bodySize, bodyWriter] = SerializeRecordBatch(
+ &flatbufBuilder,
+ Batch_->GetRowCount(),
+ TypedColumns_);
+
+ auto messageOffset = org::apache::arrow::flatbuf::CreateMessage(
+ flatbufBuilder,
+ org::apache::arrow::flatbuf::MetadataVersion_V4,
+ org::apache::arrow::flatbuf::MessageHeader_RecordBatch,
+ recordBatchOffset.Union(),
+ bodySize);
+
+ flatbufBuilder.Finish(messageOffset);
+
+ RegisterMessage(
+ org::apache::arrow::flatbuf::MessageHeader_RecordBatch,
+ std::move(flatbufBuilder),
+ bodySize,
+ std::move(bodyWriter));
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSharedRef TArrowRowStreamEncoder::Encode(
+ const IUnversionedRowBatchPtr& batch,
+ const NApi::NRpcProxy::NProto::TRowsetStatistics* statistics)
+{
+ auto columnarBatch = batch->TryAsColumnar();
+ if (!columnarBatch) {
+ YT_LOG_DEBUG("Encoding non-columnar batch; running fallback");
+ return FallbackEncoder_->Encode(batch, statistics);
+ }
+ YT_LOG_DEBUG("Encoding columnar batch (RowCount: %v)",
+ batch->GetRowCount());
+
+ NApi::NRpcProxy::NProto::TRowsetDescriptor descriptor;
+ descriptor.set_wire_format_version(NApi::NRpcProxy::CurrentWireFormatVersion);
+ descriptor.set_rowset_kind(NApi::NRpcProxy::NProto::RK_UNVERSIONED);
+ descriptor.set_rowset_format(NApi::NRpcProxy::NProto::RF_ARROW);
+
+ TArrowRowStreamBlockEncoder blockEncoder(this, std::move(columnarBatch));
+
+ auto [block, payloadRef] = SerializeRowStreamBlockEnvelope(
+ blockEncoder.GetPayloadSize(),
+ descriptor,
+ statistics);
+
+ blockEncoder.WritePayload(payloadRef);
+
+ FirstBatch_ = false;
+
+ return block;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+IRowStreamEncoderPtr CreateArrowRowStreamEncoder(
+ TTableSchemaPtr schema,
+ TNameTablePtr nameTable,
+ IRowStreamEncoderPtr fallbackEncoder,
+ NFormats::TControlAttributesConfigPtr controlAttributesConfig)
+{
+ return New<TArrowRowStreamEncoder>(
+ std::move(schema),
+ std::move(nameTable),
+ std::move(fallbackEncoder),
+ std::move(controlAttributesConfig));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NArrow
diff --git a/yt/yt/client/arrow/arrow_row_stream_encoder.h b/yt/yt/client/arrow/arrow_row_stream_encoder.h
new file mode 100644
index 0000000000..792b647d18
--- /dev/null
+++ b/yt/yt/client/arrow/arrow_row_stream_encoder.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/rpc_proxy/public.h>
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/client/formats/public.h>
+
+namespace NYT::NArrow {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::NRpcProxy::IRowStreamEncoderPtr CreateArrowRowStreamEncoder(
+ NTableClient::TTableSchemaPtr schema,
+ NTableClient::TNameTablePtr nameTable,
+ NApi::NRpcProxy::IRowStreamEncoderPtr fallbackEncoder,
+ NFormats::TControlAttributesConfigPtr controlAttributesConfig);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NArrow
diff --git a/yt/yt/client/arrow/fbs/CMakeLists.linux-aarch64.txt b/yt/yt/client/arrow/fbs/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..aaa3e39638
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,78 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+
+add_library(client-arrow-fbs)
+target_link_libraries(client-arrow-fbs PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ contrib-libs-flatbuffers
+)
+target_sources(client-arrow-fbs PRIVATE
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/Message.fbs.cpp
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/Schema.fbs.cpp
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/Tensor.fbs.cpp
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/SparseTensor.fbs.cpp
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/Message.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/Schema.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/Tensor.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/SparseTensor.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
diff --git a/yt/yt/client/arrow/fbs/CMakeLists.linux-x86_64.txt b/yt/yt/client/arrow/fbs/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..aaa3e39638
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,78 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+get_built_tool_path(
+ TOOL_flatc_bin
+ TOOL_flatc_dependency
+ contrib/tools/flatc/bin
+ flatc
+)
+
+add_library(client-arrow-fbs)
+target_link_libraries(client-arrow-fbs PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ contrib-libs-flatbuffers
+)
+target_sources(client-arrow-fbs PRIVATE
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/Message.fbs.cpp
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/Schema.fbs.cpp
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/Tensor.fbs.cpp
+ ${CMAKE_BINARY_DIR}/yt/yt/client/arrow/fbs/SparseTensor.fbs.cpp
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/Message.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/Schema.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/Tensor.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
+target_fbs_source(client-arrow-fbs
+ PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/arrow/fbs/SparseTensor.fbs
+ -I
+ ${CMAKE_BINARY_DIR}
+ -I
+ ${CMAKE_SOURCE_DIR}
+)
diff --git a/yt/yt/client/arrow/fbs/CMakeLists.txt b/yt/yt/client/arrow/fbs/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/client/arrow/fbs/Message.fbs b/yt/yt/client/arrow/fbs/Message.fbs
new file mode 100644
index 0000000000..ed43187a6c
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/Message.fbs
@@ -0,0 +1,140 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+include "Schema.fbs";
+include "SparseTensor.fbs";
+include "Tensor.fbs";
+
+namespace org.apache.arrow.flatbuf;
+
+/// ----------------------------------------------------------------------
+/// Data structures for describing a table row batch (a collection of
+/// equal-length Arrow arrays)
+
+/// Metadata about a field at some level of a nested type tree (but not
+/// its children).
+///
+/// For example, a List<Int16> with values [[1, 2, 3], null, [4], [5, 6], null]
+/// would have {length: 5, null_count: 2} for its List node, and {length: 6,
+/// null_count: 0} for its Int16 node, as separate FieldNode structs
+struct FieldNode {
+ /// The number of value slots in the Arrow array at this level of a nested
+ /// tree
+ length: long;
+
+ /// The number of observed nulls. Fields with null_count == 0 may choose not
+ /// to write their physical validity bitmap out as a materialized buffer,
+ /// instead setting the length of the bitmap buffer to 0.
+ null_count: long;
+}
+
+enum CompressionType:byte {
+ // LZ4 frame format, for portability, as provided by lz4frame.h or wrappers
+ // thereof. Not to be confused with "raw" (also called "block") format
+ // provided by lz4.h
+ LZ4_FRAME,
+
+ // Zstandard
+ ZSTD
+}
+
+/// Provided for forward compatibility in case we need to support different
+/// strategies for compressing the IPC message body (like whole-body
+/// compression rather than buffer-level) in the future
+enum BodyCompressionMethod:byte {
+ /// Each constituent buffer is first compressed with the indicated
+ /// compressor, and then written with the uncompressed length in the first 8
+ /// bytes as a 64-bit little-endian signed integer followed by the compressed
+ /// buffer bytes (and then padding as required by the protocol). The
+ /// uncompressed length may be set to -1 to indicate that the data that
+ /// follows is not compressed, which can be useful for cases where
+ /// compression does not yield appreciable savings.
+ BUFFER
+}
+
+/// Optional compression for the memory buffers constituting IPC message
+/// bodies. Intended for use with RecordBatch but could be used for other
+/// message types
+table BodyCompression {
+ /// Compressor library
+ codec: CompressionType = LZ4_FRAME;
+
+ /// Indicates the way the record batch body was compressed
+ method: BodyCompressionMethod = BUFFER;
+}
+
+/// A data header describing the shared memory layout of a "record" or "row"
+/// batch. Some systems call this a "row batch" internally and others a "record
+/// batch".
+table RecordBatch {
+ /// number of records / rows. The arrays in the batch should all have this
+ /// length
+ length: long;
+
+ /// Nodes correspond to the pre-ordered flattened logical schema
+ nodes: [FieldNode];
+
+ /// Buffers correspond to the pre-ordered flattened buffer tree
+ ///
+ /// The number of buffers appended to this list depends on the schema. For
+ /// example, most primitive arrays will have 2 buffers, 1 for the validity
+ /// bitmap and 1 for the values. For struct arrays, there will only be a
+ /// single buffer for the validity (nulls) bitmap
+ buffers: [Buffer];
+
+ /// Optional compression of the message body
+ compression: BodyCompression;
+}
+
+/// For sending dictionary encoding information. Any Field can be
+/// dictionary-encoded, but in this case none of its children may be
+/// dictionary-encoded.
+/// There is one vector / column per dictionary, but that vector / column
+/// may be spread across multiple dictionary batches by using the isDelta
+/// flag
+
+table DictionaryBatch {
+ id: long;
+ data: RecordBatch;
+
+ /// If isDelta is true the values in the dictionary are to be appended to a
+ /// dictionary with the indicated id. If isDelta is false this dictionary
+ /// should replace the existing dictionary.
+ isDelta: bool = false;
+}
+
+/// ----------------------------------------------------------------------
+/// The root Message type
+
+/// This union enables us to easily send different message types without
+/// redundant storage, and in the future we can easily add new message types.
+///
+/// Arrow implementations do not need to implement all of the message types,
+/// which may include experimental metadata types. For maximum compatibility,
+/// it is best to send data using RecordBatch
+union MessageHeader {
+ Schema, DictionaryBatch, RecordBatch, Tensor, SparseTensor
+}
+
+table Message {
+ version: org.apache.arrow.flatbuf.MetadataVersion;
+ header: MessageHeader;
+ bodyLength: long;
+ custom_metadata: [ KeyValue ];
+}
+
+root_type Message; \ No newline at end of file
diff --git a/yt/yt/client/arrow/fbs/Schema.fbs b/yt/yt/client/arrow/fbs/Schema.fbs
new file mode 100644
index 0000000000..48c6d8789e
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/Schema.fbs
@@ -0,0 +1,353 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/// Logical types, vector layouts, and schemas
+
+namespace org.apache.arrow.flatbuf;
+
+enum MetadataVersion:short {
+ /// 0.1.0
+ V1,
+
+ /// 0.2.0
+ V2,
+
+ /// 0.3.0 -> 0.7.1
+ V3,
+
+ /// >= 0.8.0
+ V4,
+}
+
+/// These are stored in the flatbuffer in the Type union below
+
+table Null {
+}
+
+/// A Struct_ in the flatbuffer metadata is the same as an Arrow Struct
+/// (according to the physical memory layout). We used Struct_ here as
+/// Struct is a reserved word in Flatbuffers
+table Struct_ {
+}
+
+table List {
+}
+
+/// Same as List, but with 64-bit offsets, allowing to represent
+/// extremely large data values.
+table LargeList {
+}
+
+table FixedSizeList {
+ /// Number of list items per value
+ listSize: int;
+}
+
+/// A Map is a logical nested type that is represented as
+///
+/// List<entries: Struct<key: K, value: V>>
+///
+/// In this layout, the keys and values are each respectively contiguous. We do
+/// not constrain the key and value types, so the application is responsible
+/// for ensuring that the keys are hashable and unique. Whether the keys are sorted
+/// may be set in the metadata for this field.
+///
+/// In a field with Map type, the field has a child Struct field, which then
+/// has two children: key type and the second the value type. The names of the
+/// child fields may be respectively "entries", "key", and "value", but this is
+/// not enforced.
+///
+/// Map
+/// - child[0] entries: Struct
+/// - child[0] key: K
+/// - child[1] value: V
+///
+/// Neither the "entries" field nor the "key" field may be nullable.
+///
+/// The metadata is structured so that Arrow systems without special handling
+/// for Map can make Map an alias for List. The "layout" attribute for the Map
+/// field must have the same contents as a List.
+table Map {
+ /// Set to true if the keys within each value are sorted
+ keysSorted: bool;
+}
+
+enum UnionMode:short { Sparse, Dense }
+
+/// A union is a complex type with children in Field
+/// By default ids in the type vector refer to the offsets in the children
+/// optionally typeIds provides an indirection between the child offset and the type id
+/// for each child typeIds[offset] is the id used in the type vector
+table Union {
+ mode: UnionMode;
+ typeIds: [ int ]; // optional, describes typeid of each child.
+}
+
+table Int {
+ bitWidth: int; // restricted to 8, 16, 32, and 64 in v1
+ is_signed: bool;
+}
+
+enum Precision:short {HALF, SINGLE, DOUBLE}
+
+table FloatingPoint {
+ precision: Precision;
+}
+
+/// Unicode with UTF-8 encoding
+table Utf8 {
+}
+
+/// Opaque binary data
+table Binary {
+}
+
+/// Same as Utf8, but with 64-bit offsets, allowing to represent
+/// extremely large data values.
+table LargeUtf8 {
+}
+
+/// Same as Binary, but with 64-bit offsets, allowing to represent
+/// extremely large data values.
+table LargeBinary {
+}
+
+table FixedSizeBinary {
+ /// Number of bytes per value
+ byteWidth: int;
+}
+
+table Bool {
+}
+
+table Decimal {
+ /// Total number of decimal digits
+ precision: int;
+ /// Number of digits after the decimal point "."
+ scale: int;
+}
+
+enum DateUnit: short {
+ DAY,
+ MILLISECOND
+}
+
+/// Date is either a 32-bit or 64-bit type representing elapsed time since UNIX
+/// epoch (1970-01-01), stored in either of two units:
+///
+/// * Milliseconds (64 bits) indicating UNIX time elapsed since the epoch (no
+/// leap seconds), where the values are evenly divisible by 86400000
+/// * Days (32 bits) since the UNIX epoch
+table Date {
+ unit: DateUnit = MILLISECOND;
+}
+
+enum TimeUnit: short { SECOND, MILLISECOND, MICROSECOND, NANOSECOND }
+
+/// Time type. The physical storage type depends on the unit
+/// - SECOND and MILLISECOND: 32 bits
+/// - MICROSECOND and NANOSECOND: 64 bits
+table Time {
+ unit: TimeUnit = MILLISECOND;
+ bitWidth: int = 32;
+}
+
+/// Time elapsed from the Unix epoch, 00:00:00.000 on 1 January 1970, excluding
+/// leap seconds, as a 64-bit integer. Note that UNIX time does not include
+/// leap seconds.
+///
+/// The Timestamp metadata supports both "time zone naive" and "time zone
+/// aware" timestamps. Read about the timezone attribute for more detail
+table Timestamp {
+ unit: TimeUnit;
+
+ /// The time zone is a string indicating the name of a time zone, one of:
+ ///
+ /// * As used in the Olson time zone database (the "tz database" or
+ /// "tzdata"), such as "America/New_York"
+ /// * An absolute time zone offset of the form +XX:XX or -XX:XX, such as +07:30
+ ///
+ /// Whether a timezone string is present indicates different semantics about
+ /// the data:
+ ///
+ /// * If the time zone is null or equal to an empty string, the data is "time
+ /// zone naive" and shall be displayed *as is* to the user, not localized
+ /// to the locale of the user. This data can be though of as UTC but
+ /// without having "UTC" as the time zone, it is not considered to be
+ /// localized to any time zone
+ ///
+ /// * If the time zone is set to a valid value, values can be displayed as
+ /// "localized" to that time zone, even though the underlying 64-bit
+ /// integers are identical to the same data stored in UTC. Converting
+ /// between time zones is a metadata-only operation and does not change the
+ /// underlying values
+ timezone: string;
+}
+
+enum IntervalUnit: short { YEAR_MONTH, DAY_TIME}
+// A "calendar" interval which models types that don't necessarily
+// have a precise duration without the context of a base timestamp (e.g.
+// days can differ in length during day light savings time transitions).
+// YEAR_MONTH - Indicates the number of elapsed whole months, stored as
+// 4-byte integers.
+// DAY_TIME - Indicates the number of elapsed days and milliseconds,
+// stored as 2 contiguous 32-bit integers (8-bytes in total). Support
+// of this IntervalUnit is not required for full arrow compatibility.
+table Interval {
+ unit: IntervalUnit;
+}
+
+// An absolute length of time unrelated to any calendar artifacts.
+//
+// For the purposes of Arrow Implementations, adding this value to a Timestamp
+// ("t1") naively (i.e. simply summing the two number) is acceptable even
+// though in some cases the resulting Timestamp (t2) would not account for
+// leap-seconds during the elapsed time between "t1" and "t2". Similarly,
+// representing the difference between two Unix timestamp is acceptable, but
+// would yield a value that is possibly a few seconds off from the true elapsed
+// time.
+//
+// The resolution defaults to millisecond, but can be any of the other
+// supported TimeUnit values as with Timestamp and Time types. This type is
+// always represented as an 8-byte integer.
+table Duration {
+ unit: TimeUnit = MILLISECOND;
+}
+
+/// ----------------------------------------------------------------------
+/// Top-level Type value, enabling extensible type-specific metadata. We can
+/// add new logical types to Type without breaking backwards compatibility
+
+union Type {
+ Null,
+ Int,
+ FloatingPoint,
+ Binary,
+ Utf8,
+ Bool,
+ Decimal,
+ Date,
+ Time,
+ Timestamp,
+ Interval,
+ List,
+ Struct_,
+ Union,
+ FixedSizeBinary,
+ FixedSizeList,
+ Map,
+ Duration,
+ LargeBinary,
+ LargeUtf8,
+ LargeList,
+}
+
+/// ----------------------------------------------------------------------
+/// user defined key value pairs to add custom metadata to arrow
+/// key namespacing is the responsibility of the user
+
+table KeyValue {
+ key: string;
+ value: string;
+}
+
+/// ----------------------------------------------------------------------
+/// Dictionary encoding metadata
+/// Maintained for forwards compatibility, in the future
+/// Dictionaries might be explicit maps between integers and values
+/// allowing for non-contiguous index values
+enum DictionaryKind : short { DenseArray }
+table DictionaryEncoding {
+ /// The known dictionary id in the application where this data is used. In
+ /// the file or streaming formats, the dictionary ids are found in the
+ /// DictionaryBatch messages
+ id: long;
+
+ /// The dictionary indices are constrained to be positive integers. If this
+ /// field is null, the indices must be signed int32
+ indexType: Int;
+
+ /// By default, dictionaries are not ordered, or the order does not have
+ /// semantic meaning. In some statistical, applications, dictionary-encoding
+ /// is used to represent ordered categorical data, and we provide a way to
+ /// preserve that metadata here
+ isOrdered: bool;
+
+ dictionaryKind: DictionaryKind;
+}
+
+/// ----------------------------------------------------------------------
+/// A field represents a named column in a record / row batch or child of a
+/// nested type.
+
+table Field {
+ /// Name is not required, in i.e. a List
+ name: string;
+
+ /// Whether or not this field can contain nulls. Should be true in general.
+ nullable: bool;
+
+ /// This is the type of the decoded value if the field is dictionary encoded.
+ type: Type;
+
+ /// Present only if the field is dictionary encoded.
+ dictionary: DictionaryEncoding;
+
+ /// children apply only to nested data types like Struct, List and Union. For
+ /// primitive types children will have length 0.
+ children: [ Field ];
+
+ /// User-defined metadata
+ custom_metadata: [ KeyValue ];
+}
+
+/// ----------------------------------------------------------------------
+/// Endianness of the platform producing the data
+
+enum Endianness:short { Little, Big }
+
+/// ----------------------------------------------------------------------
+/// A Buffer represents a single contiguous memory segment
+struct Buffer {
+ /// The relative offset into the shared memory page where the bytes for this
+ /// buffer starts
+ offset: long;
+
+ /// The absolute length (in bytes) of the memory buffer. The memory is found
+ /// from offset (inclusive) to offset + length (non-inclusive). When building
+ /// messages using the encapsulated IPC message, padding bytes may be written
+ /// after a buffer, but such padding bytes do not need to be accounted for in
+ /// the size here.
+ length: long;
+}
+
+/// ----------------------------------------------------------------------
+/// A Schema describes the columns in a row batch
+
+table Schema {
+
+ /// endianness of the buffer
+ /// it is Little Endian by default
+ /// if endianness doesn't match the underlying system then the vectors need to be converted
+ endianness: Endianness=Little;
+
+ fields: [Field];
+ // User-defined metadata
+ custom_metadata: [ KeyValue ];
+}
+
+root_type Schema; \ No newline at end of file
diff --git a/yt/yt/client/arrow/fbs/SparseTensor.fbs b/yt/yt/client/arrow/fbs/SparseTensor.fbs
new file mode 100644
index 0000000000..38ae498a8e
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/SparseTensor.fbs
@@ -0,0 +1,218 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/// EXPERIMENTAL: Metadata for n-dimensional sparse arrays, aka "sparse tensors".
+/// Arrow implementations in general are not required to implement this type
+
+include "Tensor.fbs";
+
+namespace org.apache.arrow.flatbuf;
+
+/// ----------------------------------------------------------------------
+/// EXPERIMENTAL: Data structures for sparse tensors
+
+/// Coordinate (COO) format of sparse tensor index.
+///
+/// COO's index list are represented as a NxM matrix,
+/// where N is the number of non-zero values,
+/// and M is the number of dimensions of a sparse tensor.
+///
+/// indicesBuffer stores the location and size of the data of this indices
+/// matrix. The value type and the stride of the indices matrix is
+/// specified in indicesType and indicesStrides fields.
+///
+/// For example, let X be a 2x3x4x5 tensor, and it has the following
+/// 6 non-zero values:
+///
+/// X[0, 1, 2, 0] := 1
+/// X[1, 1, 2, 3] := 2
+/// X[0, 2, 1, 0] := 3
+/// X[0, 1, 3, 0] := 4
+/// X[0, 1, 2, 1] := 5
+/// X[1, 2, 0, 4] := 6
+///
+/// In COO format, the index matrix of X is the following 4x6 matrix:
+///
+/// [[0, 0, 0, 0, 1, 1],
+/// [1, 1, 1, 2, 1, 2],
+/// [2, 2, 3, 1, 2, 0],
+/// [0, 1, 0, 0, 3, 4]]
+///
+/// Note that the indices are sorted in lexicographical order.
+table SparseTensorIndexCOO {
+ /// The type of values in indicesBuffer
+ indicesType: Int (required);
+
+ /// Non-negative byte offsets to advance one value cell along each dimension
+ /// If omitted, default to row-major order (C-like).
+ indicesStrides: [long];
+
+ /// The location and size of the indices matrix's data
+ indicesBuffer: Buffer (required);
+}
+
+enum SparseMatrixCompressedAxis: short { Row, Column }
+
+/// Compressed Sparse format, that is matrix-specific.
+table SparseMatrixIndexCSX {
+ /// Which axis, row or column, is compressed
+ compressedAxis: SparseMatrixCompressedAxis;
+
+ /// The type of values in indptrBuffer
+ indptrType: Int (required);
+
+ /// indptrBuffer stores the location and size of indptr array that
+ /// represents the range of the rows.
+ /// The i-th row spans from indptr[i] to indptr[i+1] in the data.
+ /// The length of this array is 1 + (the number of rows), and the type
+ /// of index value is long.
+ ///
+ /// For example, let X be the following 6x4 matrix:
+ ///
+ /// X := [[0, 1, 2, 0],
+ /// [0, 0, 3, 0],
+ /// [0, 4, 0, 5],
+ /// [0, 0, 0, 0],
+ /// [6, 0, 7, 8],
+ /// [0, 9, 0, 0]].
+ ///
+ /// The array of non-zero values in X is:
+ ///
+ /// values(X) = [1, 2, 3, 4, 5, 6, 7, 8, 9].
+ ///
+ /// And the indptr of X is:
+ ///
+ /// indptr(X) = [0, 2, 3, 5, 5, 8, 10].
+ indptrBuffer: Buffer (required);
+
+ /// The type of values in indicesBuffer
+ indicesType: Int (required);
+
+ /// indicesBuffer stores the location and size of the array that
+ /// contains the column indices of the corresponding non-zero values.
+ /// The type of index value is long.
+ ///
+ /// For example, the indices of the above X is:
+ ///
+ /// indices(X) = [1, 2, 2, 1, 3, 0, 2, 3, 1].
+ ///
+ /// Note that the indices are sorted in lexicographical order for each row.
+ indicesBuffer: Buffer (required);
+}
+
+/// Compressed Sparse Fiber (CSF) sparse tensor index.
+table SparseTensorIndexCSF {
+ /// CSF is a generalization of compressed sparse row (CSR) index.
+ /// See [smith2017knl]: http://shaden.io/pub-files/smith2017knl.pdf
+ ///
+ /// CSF index recursively compresses each dimension of a tensor into a set
+ /// of prefix trees. Each path from a root to leaf forms one tensor
+ /// non-zero index. CSF is implemented with two arrays of buffers and one
+ /// arrays of integers.
+ ///
+ /// For example, let X be a 2x3x4x5 tensor and let it have the following
+ /// 8 non-zero values:
+ ///
+ /// X[0, 0, 0, 1] := 1
+ /// X[0, 0, 0, 2] := 2
+ /// X[0, 1, 0, 0] := 3
+ /// X[0, 1, 0, 2] := 4
+ /// X[0, 1, 1, 0] := 5
+ /// X[1, 1, 1, 0] := 6
+ /// X[1, 1, 1, 1] := 7
+ /// X[1, 1, 1, 2] := 8
+ ///
+ /// As a prefix tree this would be represented as:
+ ///
+ /// 0 1
+ /// / \ |
+ /// 0 1 1
+ /// / / \ |
+ /// 0 0 1 1
+ /// /| /| | /| |
+ /// 1 2 0 2 0 0 1 2
+
+ /// The type of values in indptrBuffers
+ indptrType: Int (required);
+
+ /// indptrBuffers stores the sparsity structure.
+ /// Each two consecutive dimensions in a tensor correspond to a buffer in
+ /// indptrBuffers. A pair of consecutive values at indptrBuffers[dim][i]
+ /// and indptrBuffers[dim][i + 1] signify a range of nodes in
+ /// indicesBuffers[dim + 1] who are children of indicesBuffers[dim][i] node.
+ ///
+ /// For example, the indptrBuffers for the above X is:
+ ///
+ /// indptrBuffer(X) = [
+ /// [0, 2, 3],
+ /// [0, 1, 3, 4],
+ /// [0, 2, 4, 5, 8]
+ /// ].
+ ///
+ indptrBuffers: [Buffer] (required);
+
+ /// The type of values in indicesBuffers
+ indicesType: Int (required);
+
+ /// indicesBuffers stores values of nodes.
+ /// Each tensor dimension corresponds to a buffer in indicesBuffers.
+ /// For example, the indicesBuffers for the above X is:
+ ///
+ /// indicesBuffer(X) = [
+ /// [0, 1],
+ /// [0, 1, 1],
+ /// [0, 0, 1, 1],
+ /// [1, 2, 0, 2, 0, 0, 1, 2]
+ /// ].
+ ///
+ indicesBuffers: [Buffer] (required);
+
+ /// axisOrder stores the sequence in which dimensions were traversed to
+ /// produce the prefix tree.
+ /// For example, the axisOrder for the above X is:
+ ///
+ /// axisOrder(X) = [0, 1, 2, 3].
+ ///
+ axisOrder: [int] (required);
+}
+
+union SparseTensorIndex {
+ SparseTensorIndexCOO,
+ SparseMatrixIndexCSX,
+ SparseTensorIndexCSF
+}
+
+table SparseTensor {
+ /// The type of data contained in a value cell.
+ /// Currently only fixed-width value types are supported,
+ /// no strings or nested types.
+ type: Type (required);
+
+ /// The dimensions of the tensor, optionally named.
+ shape: [TensorDim] (required);
+
+ /// The number of non-zero values in a sparse tensor.
+ non_zero_length: long;
+
+ /// Sparse tensor index
+ sparseIndex: SparseTensorIndex (required);
+
+ /// The location and size of the tensor's data
+ data: Buffer (required);
+}
+
+root_type SparseTensor; \ No newline at end of file
diff --git a/yt/yt/client/arrow/fbs/Tensor.fbs b/yt/yt/client/arrow/fbs/Tensor.fbs
new file mode 100644
index 0000000000..3384196057
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/Tensor.fbs
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/// EXPERIMENTAL: Metadata for n-dimensional arrays, aka "tensors" or
+/// "ndarrays". Arrow implementations in general are not required to implement
+/// this type
+
+include "Schema.fbs";
+
+namespace org.apache.arrow.flatbuf;
+
+/// ----------------------------------------------------------------------
+/// Data structures for dense tensors
+
+/// Shape data for a single axis in a tensor
+table TensorDim {
+ /// Length of dimension
+ size: long;
+
+ /// Name of the dimension, optional
+ name: string;
+}
+
+table Tensor {
+ /// The type of data contained in a value cell. Currently only fixed-width
+ /// value types are supported, no strings or nested types
+ type: Type (required);
+
+ /// The dimensions of the tensor, optionally named
+ shape: [TensorDim] (required);
+
+ /// Non-negative byte offsets to advance one value cell along each dimension
+ /// If omitted, default to row-major order (C-like).
+ strides: [long];
+
+ /// The location and size of the tensor's data
+ data: Buffer (required);
+}
+
+root_type Tensor; \ No newline at end of file
diff --git a/yt/yt/client/arrow/fbs/ya.make b/yt/yt/client/arrow/fbs/ya.make
new file mode 100644
index 0000000000..be8fde9f09
--- /dev/null
+++ b/yt/yt/client/arrow/fbs/ya.make
@@ -0,0 +1,14 @@
+LIBRARY()
+
+SRCS(
+ Message.fbs
+ Schema.fbs
+ Tensor.fbs
+ SparseTensor.fbs
+)
+
+PEERDIR(
+ contrib/libs/flatbuffers
+)
+
+END()
diff --git a/yt/yt/client/arrow/public.cpp b/yt/yt/client/arrow/public.cpp
new file mode 100644
index 0000000000..2470d419c2
--- /dev/null
+++ b/yt/yt/client/arrow/public.cpp
@@ -0,0 +1,10 @@
+#include "public.h"
+
+namespace NYT::NArrow {
+
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NArrow
+
diff --git a/yt/yt/client/arrow/public.h b/yt/yt/client/arrow/public.h
new file mode 100644
index 0000000000..8830264345
--- /dev/null
+++ b/yt/yt/client/arrow/public.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NArrow {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger ArrowLogger("Arrow");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NArrow \ No newline at end of file
diff --git a/yt/yt/client/arrow/ya.make b/yt/yt/client/arrow/ya.make
new file mode 100644
index 0000000000..40d27d8e07
--- /dev/null
+++ b/yt/yt/client/arrow/ya.make
@@ -0,0 +1,16 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ arrow_row_stream_encoder.cpp
+ arrow_row_stream_decoder.cpp
+ public.cpp
+)
+
+PEERDIR(
+ yt/yt/client
+ yt/yt/client/arrow/fbs
+)
+
+END()
diff --git a/yt/yt/client/chaos_client/config.h b/yt/yt/client/chaos_client/config.h
new file mode 100644
index 0000000000..bc284e856d
--- /dev/null
+++ b/yt/yt/client/chaos_client/config.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/cache_config.h>
+
+#include <yt/yt/core/rpc/config.h>
+
+namespace NYT::NChaosClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReplicationCardCacheConfig
+ : public TAsyncExpiringCacheConfig
+ , public NRpc::TBalancingChannelConfig
+ , public NRpc::TRetryingChannelConfig
+{
+ REGISTER_YSON_STRUCT(TReplicationCardCacheConfig);
+
+ static void Register(TRegistrar)
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TReplicationCardCacheConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
+
diff --git a/yt/yt/client/chaos_client/helpers.cpp b/yt/yt/client/chaos_client/helpers.cpp
new file mode 100644
index 0000000000..d6ddbb0c01
--- /dev/null
+++ b/yt/yt/client/chaos_client/helpers.cpp
@@ -0,0 +1,95 @@
+#include "public.h"
+#include "replication_card_serialization.h"
+
+#include <yt/yt/client/object_client/helpers.h>
+
+#include <library/cpp/yt/assert/assert.h>
+
+namespace NYT::NChaosClient {
+
+using namespace NObjectClient;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReplicationCardId MakeReplicationCardId(TObjectId randomId)
+{
+ return MakeId(
+ EObjectType::ReplicationCard,
+ CellTagFromId(randomId),
+ CounterFromId(randomId),
+ HashFromId(randomId) & 0xffff0000);
+}
+
+TReplicaId MakeReplicaId(TReplicationCardId replicationCardId, TReplicaIdIndex index)
+{
+ return MakeId(
+ EObjectType::ChaosTableReplica,
+ CellTagFromId(replicationCardId),
+ CounterFromId(replicationCardId),
+ HashFromId(replicationCardId) | index);
+}
+
+TReplicationCardId ReplicationCardIdFromReplicaId(TReplicaId replicaId)
+{
+ return MakeId(
+ EObjectType::ReplicationCard,
+ CellTagFromId(replicaId),
+ CounterFromId(replicaId),
+ HashFromId(replicaId) & 0xffff0000);
+}
+
+TReplicationCardId ReplicationCardIdFromUpstreamReplicaIdOrNull(TReplicaId upstreamReplicaId)
+{
+ return IsChaosTableReplicaType(TypeFromId(upstreamReplicaId))
+ ? ReplicationCardIdFromReplicaId(upstreamReplicaId)
+ : TReplicationCardId();
+}
+
+TReplicationCardId MakeReplicationCardCollocationId(TObjectId randomId)
+{
+ return MakeId(
+ EObjectType::ReplicationCardCollocation,
+ CellTagFromId(randomId),
+ CounterFromId(randomId),
+ HashFromId(randomId) & 0xffff0000);
+}
+
+TCellTag GetSiblingChaosCellTag(TCellTag cellTag)
+{
+ return TCellTag(cellTag.Underlying() ^ 1);
+}
+
+bool IsOrderedTabletReplicationProgress(const TReplicationProgress& progress)
+{
+ const auto& segments = progress.Segments;
+ const auto& upper = progress.UpperKey;
+
+ if (segments.size() != 1) {
+ return false;
+ }
+
+ if (segments[0].LowerKey.GetCount() != 0 &&
+ (segments[0].LowerKey.GetCount() != 1 || segments[0].LowerKey[0].Type != EValueType::Int64))
+ {
+ return false;
+ }
+
+ if (upper.GetCount() != 1 || (upper[0].Type != EValueType::Int64 && upper[0].Type != EValueType::Max)) {
+ return false;
+ }
+
+ return true;
+}
+
+void ValidateOrderedTabletReplicationProgress(const TReplicationProgress& progress)
+{
+ if (!IsOrderedTabletReplicationProgress(progress)) {
+ THROW_ERROR_EXCEPTION("Invalid replication progress for ordered table")
+ << TErrorAttribute("replication_progress", progress);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
diff --git a/yt/yt/client/chaos_client/public.h b/yt/yt/client/chaos_client/public.h
new file mode 100644
index 0000000000..73baf9edd7
--- /dev/null
+++ b/yt/yt/client/chaos_client/public.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/client/hydra/public.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+namespace NYT::NChaosClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TReplicationCardId = NObjectClient::TObjectId;
+using TReplicationCardCollocationId = NObjectClient::TObjectId;
+using TReplicaId = NObjectClient::TObjectId;
+using TReplicationEra = ui64;
+using TReplicaIdIndex = ui16;
+
+constexpr TReplicationEra InvalidReplicationEra = static_cast<TReplicationEra>(-1);
+constexpr TReplicationEra InitialReplicationEra = 0;
+
+constexpr int MaxReplicasPerReplicationCard = 128;
+
+DECLARE_REFCOUNTED_STRUCT(TReplicationCard)
+
+DECLARE_REFCOUNTED_STRUCT(IReplicationCardCache)
+DECLARE_REFCOUNTED_CLASS(TReplicationCardCacheConfig)
+
+struct TReplicationProgress;
+struct TReplicaHistoryItem;
+struct TReplicaInfo;
+struct TReplicationCardFecthOptions;
+
+YT_DEFINE_ERROR_ENUM(
+ ((ReplicationCardNotKnown) (3200))
+ ((ReplicationCardMigrated) (3201))
+ ((ChaosCellSuspended) (3202))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/chaos_client/replication_card.cpp b/yt/yt/client/chaos_client/replication_card.cpp
new file mode 100644
index 0000000000..6e32041b2e
--- /dev/null
+++ b/yt/yt/client/chaos_client/replication_card.cpp
@@ -0,0 +1,590 @@
+#include "replication_card.h"
+
+#include <yt/yt/core/misc/guid.h>
+#include <yt/yt/core/misc/serialize.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <util/digest/multi.h>
+
+#include <algorithm>
+
+namespace NYT::NChaosClient {
+
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NTransactionClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReplicationCardFetchOptions::operator size_t() const
+{
+ return MultiHash(
+ IncludeCoordinators,
+ IncludeProgress,
+ IncludeHistory);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationCardFetchOptions& options, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("{IncludeCoordinators: %v, IncludeProgress: %v, IncludeHistory: %v}",
+ options.IncludeCoordinators,
+ options.IncludeProgress,
+ options.IncludeHistory);
+}
+
+TString ToString(const TReplicationCardFetchOptions& options)
+{
+ return ToStringViaBuilder(options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationProgress& replicationProgress, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("{Segments: %v, UpperKey: %v}",
+ MakeFormattableView(replicationProgress.Segments, [] (auto* builder, const auto& segment) {
+ builder->AppendFormat("<%v, %x>", segment.LowerKey, segment.Timestamp);
+ }),
+ replicationProgress.UpperKey);
+}
+
+TString ToString(const TReplicationProgress& replicationProgress)
+{
+ return ToStringViaBuilder(replicationProgress);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TReplicaHistoryItem& replicaHistoryItem, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("{Era: %v, Timestamp: %v, Mode: %v, State: %v}",
+ replicaHistoryItem.Era,
+ replicaHistoryItem.Timestamp,
+ replicaHistoryItem.Mode,
+ replicaHistoryItem.State);
+}
+
+TString ToString(const TReplicaHistoryItem& replicaHistoryItem)
+{
+ return ToStringViaBuilder(replicaHistoryItem);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TReplicaInfo& replicaInfo, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("{ClusterName: %v, ReplicaPath: %v, ContentType: %v, Mode: %v, State: %v, Progress: %v, History: %v}",
+ replicaInfo.ClusterName,
+ replicaInfo.ReplicaPath,
+ replicaInfo.ContentType,
+ replicaInfo.Mode,
+ replicaInfo.State,
+ replicaInfo.ReplicationProgress,
+ replicaInfo.History);
+}
+
+TString ToString(const TReplicaInfo& replicaInfo)
+{
+ return ToStringViaBuilder(replicaInfo);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationCard& replicationCard, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("{Era: %v, Replicas: %v, CoordinatorCellIds: %v, TableId: %v, TablePath: %v, TableClusterName: %v, CurrentTimestamp: %v, CollocationId: %v}",
+ replicationCard.Era,
+ replicationCard.Replicas,
+ replicationCard.CoordinatorCellIds,
+ replicationCard.TableId,
+ replicationCard.TablePath,
+ replicationCard.TableClusterName,
+ replicationCard.CurrentTimestamp,
+ replicationCard.ReplicationCardCollocationId);
+}
+
+TString ToString(const TReplicationCard& replicationCard)
+{
+ return ToStringViaBuilder(replicationCard);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TReplicationProgress::TSegment::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+
+ Persist(context, LowerKey);
+ Persist(context, Timestamp);
+}
+
+void TReplicationProgress::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+
+ Persist(context, Segments);
+ Persist(context, UpperKey);
+}
+
+void TReplicaHistoryItem::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+
+ Persist(context, Era);
+ Persist(context, Timestamp);
+ Persist(context, Mode);
+ Persist(context, State);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int TReplicaInfo::FindHistoryItemIndex(TTimestamp timestamp) const
+{
+ auto it = std::upper_bound(
+ History.begin(),
+ History.end(),
+ timestamp,
+ [] (TTimestamp lhs, const TReplicaHistoryItem& rhs) {
+ return lhs < rhs.Timestamp;
+ });
+ return std::distance(History.begin(), it) - 1;
+}
+
+TReplicaInfo* TReplicationCard::FindReplica(TReplicaId replicaId)
+{
+ auto it = Replicas.find(replicaId);
+ return it == Replicas.end() ? nullptr : &it->second;
+}
+
+TReplicaInfo* TReplicationCard::GetReplicaOrThrow(TReplicaId replicaId, TReplicationCardId replicationCardId)
+{
+ auto* replicaInfo = FindReplica(replicaId);
+ if (!replicaInfo) {
+ THROW_ERROR_EXCEPTION(
+ NYTree::EErrorCode::ResolveError,
+ "Replication card %v does not contain replica %v",
+ replicationCardId,
+ replicaId);
+ }
+ return replicaInfo;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsReplicaSync(ETableReplicaMode mode)
+{
+ return mode == ETableReplicaMode::Sync || mode == ETableReplicaMode::SyncToAsync;
+}
+
+bool IsReplicaAsync(ETableReplicaMode mode)
+{
+ return mode == ETableReplicaMode::Async || mode == ETableReplicaMode::AsyncToSync;
+}
+
+bool IsReplicaEnabled(ETableReplicaState state)
+{
+ return state == ETableReplicaState::Enabled || state == ETableReplicaState::Disabling;
+}
+
+bool IsReplicaDisabled(ETableReplicaState state)
+{
+ return state == ETableReplicaState::Disabled || state == ETableReplicaState::Enabling;
+}
+
+bool IsReplicaReallySync(ETableReplicaMode mode, ETableReplicaState state)
+{
+ return IsReplicaSync(mode) && IsReplicaEnabled(state);
+}
+
+ETableReplicaMode GetTargetReplicaMode(ETableReplicaMode mode)
+{
+ return (mode == ETableReplicaMode::Sync || mode == ETableReplicaMode::AsyncToSync)
+ ? ETableReplicaMode::Sync
+ : ETableReplicaMode::Async;
+}
+
+ETableReplicaState GetTargetReplicaState(ETableReplicaState state)
+{
+ return (state == ETableReplicaState::Enabled || state == ETableReplicaState::Enabling)
+ ? ETableReplicaState::Enabled
+ : ETableReplicaState::Disabled;
+}
+
+void UpdateReplicationProgress(TReplicationProgress* progress, const TReplicationProgress& update)
+{
+ std::vector<TReplicationProgress::TSegment> segments;
+ auto progressIt = progress->Segments.begin();
+ auto progressEnd = progress->Segments.end();
+ auto updateIt = update.Segments.begin();
+ auto updateEnd = update.Segments.end();
+ auto progressTimestamp = NullTimestamp;
+ auto updateTimestamp = NullTimestamp;
+
+ auto append = [&] (TUnversionedOwningRow key) {
+ auto timestamp = std::max(progressTimestamp, updateTimestamp);
+ if (segments.empty() || segments.back().Timestamp != timestamp) {
+ segments.push_back({std::move(key), timestamp});
+ }
+ };
+
+ bool upper = false;
+ auto processUpperKey = [&] (const TUnversionedOwningRow& key) {
+ if (upper || updateIt != updateEnd) {
+ return;
+ }
+
+ auto cmpResult = CompareRows(key, update.UpperKey);
+ if (cmpResult >= 0) {
+ updateTimestamp = NullTimestamp;
+ upper = true;
+ }
+ if (cmpResult > 0) {
+ append(update.UpperKey);
+ }
+ };
+
+ while (progressIt < progressEnd || updateIt < updateEnd) {
+ int cmpResult;
+ if (updateIt == updateEnd) {
+ cmpResult = -1;
+ } else if (progressIt == progressEnd) {
+ cmpResult = 1;
+ } else {
+ cmpResult = CompareRows(progressIt->LowerKey, updateIt->LowerKey);
+ }
+
+ if (cmpResult < 0) {
+ if (updateIt == updateEnd) {
+ processUpperKey(progressIt->LowerKey);
+ }
+ progressTimestamp = progressIt->Timestamp;
+ append(std::move(progressIt->LowerKey));
+ ++progressIt;
+ } else if (cmpResult > 0) {
+ updateTimestamp = updateIt->Timestamp;
+ append(updateIt->LowerKey);
+ ++updateIt;
+ } else {
+ updateTimestamp = updateIt->Timestamp;
+ progressTimestamp = progressIt->Timestamp;
+ append(std::move(progressIt->LowerKey));
+ ++progressIt;
+ ++updateIt;
+ }
+ }
+
+ processUpperKey(progress->UpperKey);
+ progress->Segments = std::move(segments);
+}
+
+bool IsReplicationProgressGreaterOrEqual(const TReplicationProgress& progress, const TReplicationProgress& other)
+{
+ auto progressIt = progress.Segments.begin();
+ auto otherIt = std::upper_bound(
+ other.Segments.begin(),
+ other.Segments.end(),
+ progressIt->LowerKey,
+ [] (const auto& lhs, const auto& rhs) {
+ return CompareRows(lhs, rhs.LowerKey) < 0;
+ });
+
+ auto progressEnd = progress.Segments.end();
+ auto otherEnd = other.Segments.end();
+ auto progressTimestamp = MaxTimestamp;
+ auto otherTimestamp = otherIt == other.Segments.begin()
+ ? NullTimestamp
+ : (otherIt - 1)->Timestamp;
+
+ while (progressIt < progressEnd || otherIt < otherEnd) {
+ int cmpResult;
+ if (otherIt == otherEnd) {
+ if (CompareRows(progressIt->LowerKey, other.UpperKey) >= 0) {
+ return true;
+ }
+ cmpResult = -1;
+ } else if (progressIt == progressEnd) {
+ if (CompareRows(progress.UpperKey, otherIt->LowerKey) <= 0) {
+ return true;
+ }
+ cmpResult = 1;
+ } else {
+ cmpResult = CompareRows(progressIt->LowerKey, otherIt->LowerKey);
+ }
+
+ if (cmpResult < 0) {
+ progressTimestamp = progressIt->Timestamp;
+ ++progressIt;
+ } else if (cmpResult > 0) {
+ otherTimestamp = otherIt->Timestamp;
+ ++otherIt;
+ } else {
+ progressTimestamp = progressIt->Timestamp;
+ otherTimestamp = otherIt->Timestamp;
+ ++progressIt;
+ ++otherIt;
+ }
+
+ if (progressTimestamp < otherTimestamp) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IsReplicationProgressEqual(const TReplicationProgress& progress, const TReplicationProgress& other)
+{
+ if (progress.Segments.size() != other.Segments.size() || progress.UpperKey != other.UpperKey) {
+ return false;
+ }
+ for (int index = 0; index < std::ssize(progress.Segments); ++index) {
+ const auto& segment = progress.Segments[index];
+ const auto& otherSegment = other.Segments[index];
+ if (segment.LowerKey != otherSegment.LowerKey || segment.Timestamp != otherSegment.Timestamp) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool IsReplicationProgressGreaterOrEqual(const TReplicationProgress& progress, TTimestamp timestamp)
+{
+ for (const auto& segment : progress.Segments) {
+ if (segment.Timestamp < timestamp) {
+ return false;
+ }
+ }
+ return true;
+}
+
+TReplicationProgress ExtractReplicationProgress(
+ const TReplicationProgress& progress,
+ TLegacyKey lower,
+ TLegacyKey upper)
+{
+ TReplicationProgress extracted;
+ extracted.UpperKey = TUnversionedOwningRow(upper);
+
+ auto it = std::upper_bound(
+ progress.Segments.begin(),
+ progress.Segments.end(),
+ lower,
+ [] (const auto& lhs, const auto& rhs) {
+ return CompareRows(lhs, rhs.LowerKey) < 0;
+ });
+
+ YT_VERIFY(it != progress.Segments.begin());
+ --it;
+
+ extracted.Segments.push_back({TUnversionedOwningRow(lower), it->Timestamp});
+
+ for (++it; it < progress.Segments.end() && it->LowerKey < upper; ++it) {
+ extracted.Segments.push_back(*it);
+ }
+
+ return extracted;
+}
+
+TReplicationProgress AdvanceReplicationProgress(const TReplicationProgress& progress, TTimestamp timestamp)
+{
+ TReplicationProgress result;
+ result.UpperKey = progress.UpperKey;
+
+ for (const auto& segment : progress.Segments) {
+ if (segment.Timestamp > timestamp) {
+ result.Segments.push_back(segment);
+ } else if (result.Segments.empty() || result.Segments.back().Timestamp > timestamp) {
+ result.Segments.push_back({segment.LowerKey, timestamp});
+ }
+ }
+
+ return result;
+}
+
+TReplicationProgress LimitReplicationProgressByTimestamp(const TReplicationProgress& progress, TTimestamp timestamp)
+{
+ TReplicationProgress result;
+ result.UpperKey = progress.UpperKey;
+
+ for (const auto& segment : progress.Segments) {
+ if (segment.Timestamp < timestamp) {
+ result.Segments.push_back(segment);
+ } else if (result.Segments.empty() || result.Segments.back().Timestamp < timestamp) {
+ result.Segments.push_back({segment.LowerKey, timestamp});
+ }
+ }
+
+ return result;
+}
+
+void CanonizeReplicationProgress(TReplicationProgress* progress)
+{
+ int current = 0;
+ for (int index = 1; index < std::ssize(progress->Segments); ++index) {
+ if (progress->Segments[current].Timestamp != progress->Segments[index].Timestamp) {
+ ++current;
+ if (current != index) {
+ progress->Segments[current] = std::move(progress->Segments[index]);
+ }
+ }
+ }
+ progress->Segments.resize(current + 1);
+}
+
+TTimestamp GetReplicationProgressMinTimestamp(const TReplicationProgress& progress)
+{
+ auto minTimestamp = MaxTimestamp;
+ for (const auto& segment : progress.Segments) {
+ minTimestamp = std::min(segment.Timestamp, minTimestamp);
+ }
+ return minTimestamp;
+}
+
+TTimestamp GetReplicationProgressMaxTimestamp(const TReplicationProgress& progress)
+{
+ auto maxTimestamp = MinTimestamp;
+ for (const auto& segment : progress.Segments) {
+ maxTimestamp = std::max(segment.Timestamp, maxTimestamp);
+ }
+ return maxTimestamp;
+}
+
+std::optional<TTimestamp> FindReplicationProgressTimestampForKey(
+ const TReplicationProgress& progress,
+ TUnversionedValueRange key)
+{
+ if (CompareValueRanges(progress.UpperKey.Elements(), key) <= 0 ||
+ CompareValueRanges(progress.Segments[0].LowerKey.Elements(), key) > 0)
+ {
+ return {};
+ }
+
+ auto it = std::upper_bound(
+ progress.Segments.begin(),
+ progress.Segments.end(),
+ key,
+ [&] (const auto& /*key*/, const auto& segment) {
+ return CompareValueRanges(key, segment.LowerKey.Elements()) < 0;
+ });
+ YT_VERIFY(it > progress.Segments.begin());
+
+ return (it - 1)->Timestamp;
+}
+
+TTimestamp GetReplicationProgressTimestampForKeyOrThrow(
+ const TReplicationProgress& progress,
+ TUnversionedRow key)
+{
+ auto timestamp = FindReplicationProgressTimestampForKey(progress, key.Elements());
+ if (!timestamp) {
+ THROW_ERROR_EXCEPTION("Key %v is out or replication progress range", key);
+ }
+ return *timestamp;
+}
+
+TTimestamp GetReplicationProgressMinTimestamp(
+ const TReplicationProgress& progress,
+ TLegacyKey lower,
+ TLegacyKey upper)
+{
+ auto it = std::upper_bound(
+ progress.Segments.begin(),
+ progress.Segments.end(),
+ lower,
+ [] (const auto& lhs, const auto& rhs) {
+ return CompareRows(lhs, rhs.LowerKey) < 0;
+ });
+
+ YT_VERIFY(it != progress.Segments.begin());
+ --it;
+
+ auto minTimestamp = MaxTimestamp;
+ for (; it < progress.Segments.end() && it->LowerKey < upper; ++it) {
+ minTimestamp = std::min(it->Timestamp, minTimestamp);
+ }
+ return minTimestamp;
+}
+
+TReplicationProgress GatherReplicationProgress(
+ std::vector<TReplicationProgress> progresses,
+ const std::vector<TUnversionedRow>& pivotKeys,
+ TUnversionedRow upperKey)
+{
+ YT_VERIFY(progresses.size() == pivotKeys.size());
+ YT_VERIFY(!pivotKeys.empty() && pivotKeys.back() < upperKey);
+
+ TReplicationProgress progress;
+ for (int index = 0; index < std::ssize(progresses); ++index) {
+ auto& segments = progresses[index].Segments;
+ auto lowerKey = pivotKeys[index];
+ if (segments.empty()) {
+ progress.Segments.push_back({TUnversionedOwningRow(lowerKey), MinTimestamp});
+ } else {
+ YT_VERIFY(lowerKey == segments[0].LowerKey);
+ progress.Segments.insert(
+ progress.Segments.end(),
+ std::make_move_iterator(segments.begin()),
+ std::make_move_iterator(segments.end()));
+ }
+ }
+
+ YT_VERIFY(upperKey > progress.Segments.back().LowerKey);
+ progress.UpperKey = TUnversionedOwningRow(upperKey);
+ CanonizeReplicationProgress(&progress);
+ return progress;
+}
+
+std::vector<TReplicationProgress> ScatterReplicationProgress(
+ TReplicationProgress progress,
+ const std::vector<TUnversionedRow>& pivotKeys,
+ TUnversionedRow upperKey)
+{
+ YT_VERIFY(!pivotKeys.empty() && !progress.Segments.empty());
+ YT_VERIFY(pivotKeys[0] >= progress.Segments[0].LowerKey);
+ YT_VERIFY(progress.UpperKey.Get() >= upperKey);
+ YT_VERIFY(pivotKeys.back() < upperKey);
+
+ std::vector<TReplicationProgress> result;
+ auto& segments = progress.Segments;
+ int segmentIndex = 0;
+ int pivotIndex = 0;
+ auto previousTimestamp = MaxTimestamp;
+
+ while (pivotIndex < std::ssize(pivotKeys)) {
+ auto& pivotKey = pivotKeys[pivotIndex];
+ auto cmpResult = segmentIndex < std::ssize(segments)
+ ? CompareRows(pivotKey, segments[segmentIndex].LowerKey.Get())
+ : -1;
+
+ if (cmpResult <= 0) {
+ if (!result.empty()) {
+ result.back().UpperKey = TUnversionedOwningRow(pivotKey);
+ }
+
+ result.emplace_back();
+ YT_VERIFY(cmpResult == 0 || segmentIndex > 0);
+ auto timestamp = cmpResult == 0
+ ? segments[segmentIndex].Timestamp
+ : previousTimestamp;
+ result.back().Segments.push_back({TUnversionedOwningRow(pivotKey), timestamp});
+
+ ++pivotIndex;
+ if (cmpResult == 0) {
+ previousTimestamp = segments[segmentIndex].Timestamp;
+ ++segmentIndex;
+ }
+ } else {
+ previousTimestamp = segments[segmentIndex].Timestamp;
+ if (!result.empty()) {
+ result.back().Segments.push_back(std::move(segments[segmentIndex]));
+ }
+ ++segmentIndex;
+ }
+ }
+
+ for (; segmentIndex < std::ssize(segments) && segments[segmentIndex].LowerKey < upperKey; ++segmentIndex) {
+ result.back().Segments.push_back(std::move(segments[segmentIndex]));
+ }
+
+ result.back().UpperKey = TUnversionedOwningRow(upperKey);
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
diff --git a/yt/yt/client/chaos_client/replication_card.h b/yt/yt/client/chaos_client/replication_card.h
new file mode 100644
index 0000000000..364f4f6dd6
--- /dev/null
+++ b/yt/yt/client/chaos_client/replication_card.h
@@ -0,0 +1,160 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/chaos_client/public.h>
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+namespace NYT::NChaosClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TReplicationProgress
+{
+ struct TSegment
+ {
+ NTableClient::TUnversionedOwningRow LowerKey;
+ NTransactionClient::TTimestamp Timestamp;
+
+ void Persist(const TStreamPersistenceContext& context);
+ };
+
+ std::vector<TSegment> Segments;
+ NTableClient::TUnversionedOwningRow UpperKey;
+
+ void Persist(const TStreamPersistenceContext& context);
+};
+
+struct TReplicaHistoryItem
+{
+ NChaosClient::TReplicationEra Era;
+ NTransactionClient::TTimestamp Timestamp;
+ NTabletClient::ETableReplicaMode Mode;
+ NTabletClient::ETableReplicaState State;
+
+ void Persist(const TStreamPersistenceContext& context);
+};
+
+struct TReplicaInfo
+{
+ TString ClusterName;
+ NYPath::TYPath ReplicaPath;
+ NTabletClient::ETableReplicaContentType ContentType;
+ NTabletClient::ETableReplicaMode Mode;
+ NTabletClient::ETableReplicaState State;
+ TReplicationProgress ReplicationProgress;
+ std::vector<TReplicaHistoryItem> History;
+ bool EnableReplicatedTableTracker = true;
+
+ //! Returns index of history item corresponding to timestamp, -1 if none.
+ int FindHistoryItemIndex(NTransactionClient::TTimestamp timestamp) const;
+};
+
+struct TReplicationCard
+ : public TRefCounted
+{
+ THashMap<TReplicaId, TReplicaInfo> Replicas;
+ std::vector<NObjectClient::TCellId> CoordinatorCellIds;
+ TReplicationEra Era = InvalidReplicationEra;
+ NTableClient::TTableId TableId;
+ NYPath::TYPath TablePath;
+ TString TableClusterName;
+ NTransactionClient::TTimestamp CurrentTimestamp = NTransactionClient::NullTimestamp;
+ NTabletClient::TReplicatedTableOptionsPtr ReplicatedTableOptions;
+ TReplicationCardCollocationId ReplicationCardCollocationId;
+
+ //! Returns pointer to replica with a given id, nullptr if none.
+ TReplicaInfo* FindReplica(TReplicaId replicaId);
+ TReplicaInfo* GetReplicaOrThrow(TReplicaId replicaId, TReplicationCardId replicationCardId);
+};
+
+DEFINE_REFCOUNTED_TYPE(TReplicationCard)
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct TReplicationCardFetchOptions
+{
+ bool IncludeCoordinators = false;
+ bool IncludeProgress = false;
+ bool IncludeHistory = false;
+ bool IncludeReplicatedTableOptions = false;
+
+ operator size_t() const;
+ bool operator == (const TReplicationCardFetchOptions& other) const = default;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationCardFetchOptions& options, TStringBuf /*spec*/);
+TString ToString(const TReplicationCardFetchOptions& options);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationProgress& replicationProgress, TStringBuf /*spec*/);
+TString ToString(const TReplicationProgress& replicationProgress);
+
+void FormatValue(TStringBuilderBase* builder, const TReplicaHistoryItem& replicaHistoryItem, TStringBuf /*spec*/);
+TString ToString(const TReplicaHistoryItem& replicaHistoryItem);
+
+void FormatValue(TStringBuilderBase* builder, const TReplicaInfo& replicaInfo, TStringBuf /*spec*/);
+TString ToString(const TReplicaInfo& replicaInfo);
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationCard& replicationCard, TStringBuf /*spec*/);
+TString ToString(const TReplicationCard& replicationCard);
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsReplicaSync(NTabletClient::ETableReplicaMode mode);
+bool IsReplicaAsync(NTabletClient::ETableReplicaMode mode);
+bool IsReplicaEnabled(NTabletClient::ETableReplicaState state);
+bool IsReplicaDisabled(NTabletClient::ETableReplicaState state);
+bool IsReplicaReallySync(NTabletClient::ETableReplicaMode mode, NTabletClient::ETableReplicaState state);
+NTabletClient::ETableReplicaMode GetTargetReplicaMode(NTabletClient::ETableReplicaMode mode);
+NTabletClient::ETableReplicaState GetTargetReplicaState(NTabletClient::ETableReplicaState state);
+
+void UpdateReplicationProgress(TReplicationProgress* progress, const TReplicationProgress& update);
+
+bool IsReplicationProgressEqual(const TReplicationProgress& progress, const TReplicationProgress& other);
+bool IsReplicationProgressGreaterOrEqual(const TReplicationProgress& progress, const TReplicationProgress& other);
+bool IsReplicationProgressGreaterOrEqual(const TReplicationProgress& progress, NTransactionClient::TTimestamp timestamp);
+
+TReplicationProgress ExtractReplicationProgress(
+ const TReplicationProgress& progress,
+ NTableClient::TLegacyKey lower,
+ NTableClient::TLegacyKey upper);
+TReplicationProgress AdvanceReplicationProgress(const TReplicationProgress& progress, NTransactionClient::TTimestamp timestamp);
+TReplicationProgress LimitReplicationProgressByTimestamp(const TReplicationProgress& progress, NTransactionClient::TTimestamp timestamp);
+void CanonizeReplicationProgress(TReplicationProgress* progress);
+
+NTransactionClient::TTimestamp GetReplicationProgressMinTimestamp(const TReplicationProgress& progress);
+NTransactionClient::TTimestamp GetReplicationProgressMaxTimestamp(const TReplicationProgress& progress);
+NTransactionClient::TTimestamp GetReplicationProgressMinTimestamp(
+ const TReplicationProgress& progress,
+ NTableClient::TLegacyKey lower,
+ NTableClient::TLegacyKey upper);
+
+std::optional<NTransactionClient::TTimestamp> FindReplicationProgressTimestampForKey(
+ const TReplicationProgress& progress,
+ NTableClient::TUnversionedValueRange key);
+NTransactionClient::TTimestamp GetReplicationProgressTimestampForKeyOrThrow(
+ const TReplicationProgress& progress,
+ NTableClient::TUnversionedRow key);
+
+// Gathers replication progresses into a single one.
+// Pivot key should match first segment lower keys of a corresponding progress.
+// If source progress is empty it is is considered to span corresponding pivot keys range and have MinTimestamp timestamp,
+TReplicationProgress GatherReplicationProgress(
+ std::vector<TReplicationProgress> progresses,
+ const std::vector<NTableClient::TUnversionedRow>& pivotKeys,
+ NTableClient::TUnversionedRow upperKey);
+
+// Splits replication progress into ranges [pivotKeys[0]: pivotKeys[1]], ..., [pivotKeys[-1]: upperKey].
+std::vector<TReplicationProgress> ScatterReplicationProgress(
+ TReplicationProgress progress,
+ const std::vector<NTableClient::TUnversionedRow>& pivotKeys,
+ NTableClient::TUnversionedRow upperKey);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
diff --git a/yt/yt/client/chaos_client/replication_card_cache.cpp b/yt/yt/client/chaos_client/replication_card_cache.cpp
new file mode 100644
index 0000000000..28f660ec6e
--- /dev/null
+++ b/yt/yt/client/chaos_client/replication_card_cache.cpp
@@ -0,0 +1,33 @@
+#include "replication_card_cache.h"
+
+#include <library/cpp/yt/string/format.h>
+#include <library/cpp/yt/string/guid.h>
+
+#include <util/digest/multi.h>
+
+namespace NYT::NChaosClient {
+
+///////////////////////////////////////////////////////////////////////////////
+
+TReplicationCardCacheKey::operator size_t() const
+{
+ return MultiHash(
+ CardId,
+ FetchOptions);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationCardCacheKey& key, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("{CardId: %v, FetchOptions: %v}",
+ key.CardId,
+ key.FetchOptions);
+}
+
+TString ToString(const TReplicationCardCacheKey& key)
+{
+ return ToStringViaBuilder(key);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
diff --git a/yt/yt/client/chaos_client/replication_card_cache.h b/yt/yt/client/chaos_client/replication_card_cache.h
new file mode 100644
index 0000000000..e7b5872067
--- /dev/null
+++ b/yt/yt/client/chaos_client/replication_card_cache.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "public.h"
+#include "replication_card.h"
+
+namespace NYT::NChaosClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TReplicationCardCacheKey
+{
+ TReplicationCardId CardId;
+ TReplicationCardFetchOptions FetchOptions;
+ TReplicationEra RefreshEra = InvalidReplicationEra;
+
+ operator size_t() const;
+ bool operator == (const TReplicationCardCacheKey& other) const = default;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TReplicationCardCacheKey& key, TStringBuf /*spec*/);
+TString ToString(const TReplicationCardCacheKey& key);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IReplicationCardCache
+ : public virtual TRefCounted
+{
+ virtual TFuture<TReplicationCardPtr> GetReplicationCard(const TReplicationCardCacheKey& key) = 0;
+ virtual void ForceRefresh(const TReplicationCardCacheKey& key, const TReplicationCardPtr& replicationCard) = 0;
+ virtual void Clear() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IReplicationCardCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
+
diff --git a/yt/yt/client/chaos_client/replication_card_serialization.cpp b/yt/yt/client/chaos_client/replication_card_serialization.cpp
new file mode 100644
index 0000000000..cb2ca23516
--- /dev/null
+++ b/yt/yt/client/chaos_client/replication_card_serialization.cpp
@@ -0,0 +1,465 @@
+#include "replication_card_serialization.h"
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/tablet_client/config.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+#include <yt/yt/core/misc/collection_helpers.h>
+
+#include <yt/yt/core/yson/string.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT::NChaosClient {
+
+using namespace NTransactionClient;
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NYTree;
+using namespace NYson;
+
+using NYT::ToProto;
+using NYT::FromProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSerializableSegment
+ : public TYsonStruct
+{
+ TUnversionedOwningRow LowerKey;
+ TTimestamp Timestamp;
+
+ REGISTER_YSON_STRUCT(TSerializableSegment);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.Parameter("lower_key", &TThis::LowerKey)
+ .Default();
+ registrar.Parameter("timestamp", &TThis::Timestamp)
+ .Default();
+ }
+};
+
+DECLARE_REFCOUNTED_STRUCT(TSerializableReplicationProgress)
+
+struct TSerializableReplicationProgress
+ : public TYsonStruct
+{
+ std::vector<TIntrusivePtr<TSerializableSegment>> Segments;
+ TUnversionedOwningRow UpperKey;
+
+ REGISTER_YSON_STRUCT(TSerializableReplicationProgress);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.Parameter("segments", &TThis::Segments)
+ .Default();
+ registrar.Parameter("upper_key", &TThis::UpperKey)
+ .Default();
+ }
+};
+
+DEFINE_REFCOUNTED_TYPE(TSerializableReplicationProgress)
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(TSerializableReplicaInfo)
+
+struct TSerializableReplicaInfo
+ : public TYsonStruct
+{
+ TString ClusterName;
+ NYPath::TYPath ReplicaPath;
+ NTabletClient::ETableReplicaContentType ContentType;
+ NTabletClient::ETableReplicaMode Mode;
+ NTabletClient::ETableReplicaState State;
+ TReplicationProgress ReplicationProgress;
+ bool EnableReplicatedTableTracker;
+
+ REGISTER_YSON_STRUCT(TSerializableReplicaInfo);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.Parameter("cluster_name", &TThis::ClusterName)
+ .NonEmpty();
+ registrar.Parameter("replica_path", &TThis::ReplicaPath)
+ .NonEmpty();
+ registrar.Parameter("content_type", &TThis::ContentType);
+ registrar.Parameter("mode", &TThis::Mode)
+ .Default(ETableReplicaMode::Async);
+ registrar.Parameter("state", &TThis::State)
+ .Default(ETableReplicaState::Disabled);
+ registrar.Parameter("replication_progress", &TThis::ReplicationProgress)
+ .Default();
+ registrar.Parameter("enable_replicated_table_tracker", &TThis::EnableReplicatedTableTracker)
+ .Default(false);
+ }
+};
+
+DEFINE_REFCOUNTED_TYPE(TSerializableReplicaInfo)
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(TSerializableReplicationCard)
+
+struct TSerializableReplicationCard
+ : public NYTree::TYsonStruct
+{
+ THashMap<TString, TReplicaInfo> Replicas;
+ std::vector<NObjectClient::TCellId> CoordinatorCellIds;
+ TReplicationEra Era;
+ TTableId TableId;
+ TYPath TablePath;
+ TString TableClusterName;
+ NTransactionClient::TTimestamp CurrentTimestamp;
+ NTabletClient::TReplicatedTableOptionsPtr ReplicatedTableOptions;
+ TReplicationCardCollocationId ReplicationCardCollocationId;
+
+ REGISTER_YSON_STRUCT(TSerializableReplicationCard);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.Parameter("replicas", &TThis::Replicas);
+ registrar.Parameter("coordinator_cell_ids", &TThis::CoordinatorCellIds)
+ .Default();
+ registrar.Parameter("era", &TThis::Era)
+ .Default(0);
+ registrar.Parameter("table_id", &TThis::TableId)
+ .Default();
+ registrar.Parameter("table_path", &TThis::TablePath)
+ .Default();
+ registrar.Parameter("table_cluster_name", &TThis::TableClusterName)
+ .Default();
+ registrar.Parameter("current_timestamp", &TThis::CurrentTimestamp)
+ .Default();
+ registrar.Parameter("replicated_table_options", &TThis::ReplicatedTableOptions)
+ .Default();
+ registrar.Parameter("replication_card_collocation_id", &TThis::ReplicationCardCollocationId)
+ .Default();
+ }
+};
+
+DEFINE_REFCOUNTED_TYPE(TSerializableReplicationCard)
+
+////////////////////////////////////////////////////////////////////////////////
+
+void DeserializeImpl(TReplicationProgress& replicationProgress, TSerializableReplicationProgressPtr serializable)
+{
+ replicationProgress.UpperKey = std::move(serializable->UpperKey);
+ replicationProgress.Segments.reserve(serializable->Segments.size());
+
+ for (auto& segment : serializable->Segments) {
+ replicationProgress.Segments.push_back({
+ .LowerKey = std::move(segment->LowerKey),
+ .Timestamp = segment->Timestamp
+ });
+ }
+}
+
+void DeserializeImpl(TReplicaInfo& replicaInfo, TSerializableReplicaInfoPtr serializable)
+{
+ replicaInfo.ClusterName = serializable->ClusterName;
+ replicaInfo.ReplicaPath = serializable->ReplicaPath;
+ replicaInfo.ContentType = serializable->ContentType;
+ replicaInfo.Mode = serializable->Mode;
+ replicaInfo.State = serializable->State;
+ replicaInfo.ReplicationProgress = std::move(serializable->ReplicationProgress);
+}
+
+void DeserializeImpl(TReplicationCard& replicationCard, TSerializableReplicationCardPtr serializable)
+{
+ replicationCard.Replicas.clear();
+ for (const auto& [replicaId, replicaInfo] : serializable->Replicas) {
+ EmplaceOrCrash(replicationCard.Replicas, TReplicaId::FromString(replicaId), replicaInfo);
+ }
+ replicationCard.CoordinatorCellIds = std::move(serializable->CoordinatorCellIds);
+ replicationCard.Era = serializable->Era;
+ replicationCard.TableId = serializable->TableId;
+ replicationCard.TablePath = serializable->TablePath;
+ replicationCard.TableClusterName = serializable->TableClusterName;
+ replicationCard.CurrentTimestamp = serializable->CurrentTimestamp;
+ replicationCard.ReplicatedTableOptions = serializable->ReplicatedTableOptions;
+ replicationCard.ReplicationCardCollocationId = serializable->ReplicationCardCollocationId;
+}
+
+void Deserialize(TReplicationProgress& replicationProgress, INodePtr node)
+{
+ DeserializeImpl(replicationProgress, ConvertTo<TSerializableReplicationProgressPtr>(node));
+}
+
+void Deserialize(TReplicaInfo& replicaInfo, INodePtr node)
+{
+ DeserializeImpl(replicaInfo, ConvertTo<TSerializableReplicaInfoPtr>(node));
+}
+
+void Deserialize(TReplicationCard& replicationCard, INodePtr node)
+{
+ DeserializeImpl(replicationCard, ConvertTo<TSerializableReplicationCardPtr>(node));
+}
+
+void Deserialize(TReplicationProgress& replicationProgress, TYsonPullParserCursor* cursor)
+{
+ DeserializeImpl(replicationProgress, ExtractTo<TSerializableReplicationProgressPtr>(cursor));
+}
+
+void Deserialize(TReplicaInfo& replicaInfo, TYsonPullParserCursor* cursor)
+{
+ DeserializeImpl(replicaInfo, ExtractTo<TSerializableReplicaInfoPtr>(cursor));
+}
+
+void Deserialize(TReplicationCard& replicationCard, TYsonPullParserCursor* cursor)
+{
+ DeserializeImpl(replicationCard, ExtractTo<TSerializableReplicationCardPtr>(cursor));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TReplicationProgress& replicationProgress, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("segments").DoListFor(replicationProgress.Segments, [] (auto fluent, const auto& segment) {
+ fluent
+ .Item().BeginMap()
+ .Item("lower_key").Value(segment.LowerKey ? segment.LowerKey : EmptyKey())
+ .Item("timestamp").Value(segment.Timestamp)
+ .EndMap();
+ })
+ .Item("upper_key").Value(replicationProgress.UpperKey ? replicationProgress.UpperKey : EmptyKey())
+ .EndMap();
+}
+
+void Serialize(const TReplicaHistoryItem& replicaHistoryItem, NYson::IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("era").Value(replicaHistoryItem.Era)
+ .Item("timestamp").Value(replicaHistoryItem.Timestamp)
+ .Item("mode").Value(replicaHistoryItem.Mode)
+ .Item("state").Value(replicaHistoryItem.State)
+ .EndMap();
+}
+
+void Serialize(
+ const TReplicaInfo& replicaInfo,
+ TFluentMap fluent,
+ const TReplicationCardFetchOptions& options)
+{
+ fluent
+ .Item("cluster_name").Value(replicaInfo.ClusterName)
+ .Item("replica_path").Value(replicaInfo.ReplicaPath)
+ .Item("content_type").Value(replicaInfo.ContentType)
+ .Item("mode").Value(replicaInfo.Mode)
+ .Item("state").Value(replicaInfo.State)
+ .Item("enable_replicated_table_tracker").Value(replicaInfo.EnableReplicatedTableTracker)
+ .DoIf(options.IncludeProgress, [&] (auto fluent) {
+ fluent
+ .Item("replication_progress").Value(replicaInfo.ReplicationProgress);
+ })
+ .DoIf(options.IncludeHistory, [&] (auto fluent) {
+ fluent
+ .Item("history").Value(replicaInfo.History);
+ });
+}
+
+void Serialize(
+ const TReplicaInfo& replicaInfo,
+ IYsonConsumer* consumer,
+ const TReplicationCardFetchOptions& options)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Do([&] (auto fluent) {
+ Serialize(replicaInfo, fluent, options);
+ })
+ .EndMap();
+}
+
+void Serialize(
+ const TReplicationCard& replicationCard,
+ IYsonConsumer* consumer,
+ const TReplicationCardFetchOptions& options)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Do([&] (auto fluent) {
+ Serialize(replicationCard, fluent, options);
+ })
+ .EndMap();
+}
+
+void Serialize(
+ const TReplicationCard& replicationCard,
+ TFluentMap fluent,
+ const TReplicationCardFetchOptions& options)
+{
+ fluent
+ .Item("replicas").DoMapFor(replicationCard.Replicas, [&] (auto fluent, const auto& pair) {
+ fluent
+ .Item(ToString(pair.first)).Do([&] (auto fluent) {
+ Serialize(pair.second, fluent.GetConsumer(), options);
+ });
+ })
+ .DoIf(options.IncludeCoordinators, [&] (auto fluent) {
+ fluent
+ .Item("coordinator_cell_ids").Value(replicationCard.CoordinatorCellIds);
+ })
+ .DoIf(options.IncludeReplicatedTableOptions && replicationCard.ReplicatedTableOptions, [&] (auto fluent) {
+ fluent
+ .Item("replicated_table_options").Value(replicationCard.ReplicatedTableOptions);
+ })
+ .Item("era").Value(replicationCard.Era)
+ .Item("table_id").Value(replicationCard.TableId)
+ .Item("table_path").Value(replicationCard.TablePath)
+ .Item("table_cluster_name").Value(replicationCard.TableClusterName)
+ .Item("current_timestamp").Value(replicationCard.CurrentTimestamp)
+ .Item("replication_card_collocation_id").Value(replicationCard.ReplicationCardCollocationId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(NChaosClient::NProto::TReplicationProgress::TSegment* protoSegment, const TReplicationProgress::TSegment& segment)
+{
+ ToProto(protoSegment->mutable_lower_key(), segment.LowerKey);
+ protoSegment->set_timestamp(segment.Timestamp);
+}
+
+void FromProto(TReplicationProgress::TSegment* segment, const NChaosClient::NProto::TReplicationProgress::TSegment& protoSegment)
+{
+ segment->LowerKey = FromProto<TUnversionedOwningRow>(protoSegment.lower_key());
+ segment->Timestamp = protoSegment.timestamp();
+}
+
+void ToProto(NChaosClient::NProto::TReplicationProgress* protoReplicationProgress, const TReplicationProgress& replicationProgress)
+{
+ ToProto(protoReplicationProgress->mutable_segments(), replicationProgress.Segments);
+ ToProto(protoReplicationProgress->mutable_upper_key(), replicationProgress.UpperKey);
+}
+
+void FromProto(TReplicationProgress* replicationProgress, const NChaosClient::NProto::TReplicationProgress& protoReplicationProgress)
+{
+ FromProto(&replicationProgress->Segments, protoReplicationProgress.segments());
+ FromProto(&replicationProgress->UpperKey, protoReplicationProgress.upper_key());
+}
+
+void ToProto(NChaosClient::NProto::TReplicaHistoryItem* protoHistoryItem, const TReplicaHistoryItem& historyItem)
+{
+ protoHistoryItem->set_era(historyItem.Era);
+ protoHistoryItem->set_timestamp(ToProto<ui64>(historyItem.Timestamp));
+ protoHistoryItem->set_mode(ToProto<i32>(historyItem.Mode));
+ protoHistoryItem->set_state(ToProto<i32>(historyItem.State));
+}
+
+void FromProto(TReplicaHistoryItem* historyItem, const NChaosClient::NProto::TReplicaHistoryItem& protoHistoryItem)
+{
+ historyItem->Era = protoHistoryItem.era();
+ historyItem->Timestamp = FromProto<TTimestamp>(protoHistoryItem.timestamp());
+ historyItem->Mode = FromProto<ETableReplicaMode>(protoHistoryItem.mode());
+ historyItem->State = FromProto<ETableReplicaState>(protoHistoryItem.state());
+}
+
+void ToProto(
+ NChaosClient::NProto::TReplicaInfo* protoReplicaInfo,
+ const TReplicaInfo& replicaInfo,
+ const TReplicationCardFetchOptions& options)
+{
+ protoReplicaInfo->set_cluster_name(replicaInfo.ClusterName);
+ protoReplicaInfo->set_replica_path(replicaInfo.ReplicaPath);
+ protoReplicaInfo->set_content_type(ToProto<i32>(replicaInfo.ContentType));
+ protoReplicaInfo->set_mode(ToProto<i32>(replicaInfo.Mode));
+ protoReplicaInfo->set_state(ToProto<i32>(replicaInfo.State));
+ protoReplicaInfo->set_enable_replicated_table_tracker(replicaInfo.EnableReplicatedTableTracker);
+ if (options.IncludeProgress) {
+ ToProto(protoReplicaInfo->mutable_progress(), replicaInfo.ReplicationProgress);
+ }
+ if (options.IncludeHistory) {
+ ToProto(protoReplicaInfo->mutable_history(), replicaInfo.History);
+ }
+}
+
+void FromProto(TReplicaInfo* replicaInfo, const NChaosClient::NProto::TReplicaInfo& protoReplicaInfo)
+{
+ replicaInfo->ClusterName = protoReplicaInfo.cluster_name();
+ replicaInfo->ReplicaPath = protoReplicaInfo.replica_path();
+ replicaInfo->ContentType = FromProto<ETableReplicaContentType>(protoReplicaInfo.content_type());
+ replicaInfo->Mode = FromProto<ETableReplicaMode>(protoReplicaInfo.mode());
+ replicaInfo->State = FromProto<ETableReplicaState>(protoReplicaInfo.state());
+ if (protoReplicaInfo.has_progress()) {
+ FromProto(&replicaInfo->ReplicationProgress, protoReplicaInfo.progress());
+ }
+ FromProto(&replicaInfo->History, protoReplicaInfo.history());
+ if (protoReplicaInfo.has_enable_replicated_table_tracker()) {
+ replicaInfo->EnableReplicatedTableTracker = protoReplicaInfo.enable_replicated_table_tracker();
+ }
+}
+
+void ToProto(
+ NChaosClient::NProto::TReplicationCard* protoReplicationCard,
+ const TReplicationCard& replicationCard,
+ const TReplicationCardFetchOptions& options)
+{
+ for (const auto& [replicaId, replicaInfo] : SortHashMapByKeys(replicationCard.Replicas)) {
+ auto* protoReplicaEntry = protoReplicationCard->add_replicas();
+ ToProto(protoReplicaEntry->mutable_id(), replicaId);
+ ToProto(protoReplicaEntry->mutable_info(), replicaInfo, options);
+ }
+ if (options.IncludeCoordinators) {
+ ToProto(protoReplicationCard->mutable_coordinator_cell_ids(), replicationCard.CoordinatorCellIds);
+ }
+ if (options.IncludeReplicatedTableOptions && replicationCard.ReplicatedTableOptions) {
+ protoReplicationCard->set_replicated_table_options(ConvertToYsonString(replicationCard.ReplicatedTableOptions).ToString());
+ }
+ protoReplicationCard->set_era(replicationCard.Era);
+ ToProto(protoReplicationCard->mutable_table_id(), replicationCard.TableId);
+ protoReplicationCard->set_table_path(replicationCard.TablePath);
+ protoReplicationCard->set_table_cluster_name(replicationCard.TableClusterName);
+ protoReplicationCard->set_current_timestamp(replicationCard.CurrentTimestamp);
+ ToProto(protoReplicationCard->mutable_replication_card_collocation_id(), replicationCard.ReplicationCardCollocationId);
+}
+
+void FromProto(TReplicationCard* replicationCard, const NChaosClient::NProto::TReplicationCard& protoReplicationCard)
+{
+ for (const auto& protoEntry : protoReplicationCard.replicas()) {
+ auto replicaId = FromProto<TReplicaId>(protoEntry.id());
+ auto& replicaInfo = EmplaceOrCrash(replicationCard->Replicas, replicaId, TReplicaInfo())->second;
+ FromProto(&replicaInfo, protoEntry.info());
+ }
+ FromProto(&replicationCard->CoordinatorCellIds, protoReplicationCard.coordinator_cell_ids());
+ replicationCard->Era = protoReplicationCard.era();
+ replicationCard->TableId = FromProto<TTableId>(protoReplicationCard.table_id());
+ replicationCard->TablePath = protoReplicationCard.table_path();
+ replicationCard->TableClusterName = protoReplicationCard.table_cluster_name();
+ replicationCard->CurrentTimestamp = protoReplicationCard.current_timestamp();
+ if (protoReplicationCard.has_replicated_table_options()) {
+ replicationCard->ReplicatedTableOptions = ConvertTo<TReplicatedTableOptionsPtr>(TYsonString(protoReplicationCard.replicated_table_options()));
+ }
+ if (protoReplicationCard.has_replication_card_collocation_id()) {
+ FromProto(&replicationCard->ReplicationCardCollocationId, protoReplicationCard.replication_card_collocation_id());
+ }
+}
+
+void ToProto(
+ NChaosClient::NProto::TReplicationCardFetchOptions* protoOptions,
+ const TReplicationCardFetchOptions& options)
+{
+ protoOptions->set_include_coordinators(options.IncludeCoordinators);
+ protoOptions->set_include_progress(options.IncludeProgress);
+ protoOptions->set_include_history(options.IncludeHistory);
+ protoOptions->set_include_replicated_table_options(options.IncludeReplicatedTableOptions);
+}
+
+void FromProto(
+ TReplicationCardFetchOptions* options,
+ const NChaosClient::NProto::TReplicationCardFetchOptions& protoOptions)
+{
+ options->IncludeCoordinators = protoOptions.include_coordinators();
+ options->IncludeProgress = protoOptions.include_progress();
+ options->IncludeHistory = protoOptions.include_history();
+ options->IncludeReplicatedTableOptions = protoOptions.include_replicated_table_options();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
diff --git a/yt/yt/client/chaos_client/replication_card_serialization.h b/yt/yt/client/chaos_client/replication_card_serialization.h
new file mode 100644
index 0000000000..7e69b2c43a
--- /dev/null
+++ b/yt/yt/client/chaos_client/replication_card_serialization.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include "replication_card.h"
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt_proto/yt/client/chaos_client/proto/replication_card.pb.h>
+
+namespace NYT::NChaosClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TReplicationProgress& replicationProgress, NYson::IYsonConsumer* consumer);
+void Serialize(const TReplicaHistoryItem& replicaHistoryItem, NYson::IYsonConsumer* consumer);
+void Serialize(
+ const TReplicaInfo& replicaInfo,
+ NYTree::TFluentMap fluent,
+ const TReplicationCardFetchOptions& options = {});
+void Serialize(
+ const TReplicaInfo& replicaInfo,
+ NYson::IYsonConsumer* consumer,
+ const TReplicationCardFetchOptions& options = {});
+void Serialize(
+ const TReplicationCard& replicationCard,
+ NYson::IYsonConsumer* consumer,
+ const TReplicationCardFetchOptions& options = {});
+void Serialize(
+ const TReplicationCard& replicationCard,
+ NYTree::TFluentMap fluent,
+ const TReplicationCardFetchOptions& options = {});
+
+void Deserialize(TReplicationProgress& replicationProgress, NYTree::INodePtr node);
+void Deserialize(TReplicaInfo& replicaInfo, NYTree::INodePtr node);
+void Deserialize(TReplicationCard& replicationCard, NYTree::INodePtr node);
+
+void Deserialize(TReplicationProgress& replicationProgress, NYson::TYsonPullParserCursor* cursor);
+void Deserialize(TReplicaInfo& replicaInfo, NYson::TYsonPullParserCursor* cursor);
+void Deserialize(TReplicationCard& replicationCard, NYson::TYsonPullParserCursor* cursor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(
+ NChaosClient::NProto::TReplicationProgress* protoReplicationProgress,
+ const TReplicationProgress& replicationProgress);
+void FromProto(
+ TReplicationProgress* replicationProgress,
+ const NChaosClient::NProto::TReplicationProgress& protoReplicationProgress);
+
+void ToProto(
+ NChaosClient::NProto::TReplicaInfo* protoReplicaInfo,
+ const TReplicaInfo& replicaInfo,
+ const TReplicationCardFetchOptions& options = {});
+void FromProto(
+ TReplicaInfo* replicaInfo,
+ const NChaosClient::NProto::TReplicaInfo& protoReplicaInfo);
+
+void ToProto(
+ NChaosClient::NProto::TReplicationCard* protoReplicationCard,
+ const TReplicationCard& replicationCard,
+ const TReplicationCardFetchOptions& options = {});
+void FromProto(
+ TReplicationCard* replicationCard,
+ const NChaosClient::NProto::TReplicationCard& protoReplicationCard);
+
+void ToProto(
+ NChaosClient::NProto::TReplicationCardFetchOptions* protoOptions,
+ const TReplicationCardFetchOptions& options);
+void FromProto(
+ TReplicationCardFetchOptions* options,
+ const NChaosClient::NProto::TReplicationCardFetchOptions& protoOptions);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChaosClient
diff --git a/yt/yt/client/chunk_client/chunk_replica-inl.h b/yt/yt/client/chunk_client/chunk_replica-inl.h
new file mode 100644
index 0000000000..27b745feaf
--- /dev/null
+++ b/yt/yt/client/chunk_client/chunk_replica-inl.h
@@ -0,0 +1,301 @@
+#ifndef CHUNK_REPLICA_INL_H_
+#error "Direct inclusion of this file is not allowed, include chunk_replica.h"
+// For the sake of sane code completion.
+#include "chunk_replica.h"
+#endif
+
+#include <yt/yt/client/object_client/helpers.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static_assert(
+ ChunkReplicaIndexBound <= (1LL << 5),
+ "Replica index must fit into 5 bits.");
+static_assert(
+ MediumIndexBound <= (1LL << 7),
+ "Medium index must fit into 7 bits.");
+
+Y_FORCE_INLINE TChunkReplicaWithMedium::TChunkReplicaWithMedium()
+ : Value_(NNodeTrackerClient::InvalidNodeId.Underlying())
+{ }
+
+Y_FORCE_INLINE TChunkReplicaWithMedium::TChunkReplicaWithMedium(ui64 value)
+ : Value_(value)
+{ }
+
+Y_FORCE_INLINE TChunkReplicaWithMedium::TChunkReplicaWithMedium(
+ NNodeTrackerClient::TNodeId nodeId,
+ int replicaIndex,
+ int mediumIndex)
+ : Value_(
+ static_cast<ui64>(nodeId.Underlying()) |
+ (static_cast<ui64>(replicaIndex) << 24) |
+ (static_cast<ui64>(mediumIndex) << 29))
+{
+ YT_ASSERT(nodeId.Underlying() >= 0 && nodeId.Underlying() <= NNodeTrackerClient::MaxNodeId.Underlying());
+ YT_ASSERT(replicaIndex >= 0 && replicaIndex < ChunkReplicaIndexBound);
+ YT_ASSERT(mediumIndex >= 0 && mediumIndex < MediumIndexBound);
+}
+
+Y_FORCE_INLINE TChunkReplicaWithMedium::TChunkReplicaWithMedium(
+ TChunkReplica replica)
+ : TChunkReplicaWithMedium(
+ replica.GetNodeId(),
+ replica.GetReplicaIndex(),
+ GenericMediumIndex)
+{ }
+
+Y_FORCE_INLINE NNodeTrackerClient::TNodeId TChunkReplicaWithMedium::GetNodeId() const
+{
+ return NNodeTrackerClient::TNodeId(Value_ & 0x00ffffff);
+}
+
+Y_FORCE_INLINE int TChunkReplicaWithMedium::GetReplicaIndex() const
+{
+ return (Value_ & 0x1f000000) >> 24;
+}
+
+Y_FORCE_INLINE int TChunkReplicaWithMedium::GetMediumIndex() const
+{
+ return Value_ >> 29;
+}
+
+Y_FORCE_INLINE TChunkReplica TChunkReplicaWithMedium::ToChunkReplica() const
+{
+ return TChunkReplica(GetNodeId(), GetReplicaIndex());
+}
+
+Y_FORCE_INLINE void ToProto(ui64* protoReplica, TChunkReplicaWithMedium replica)
+{
+ *protoReplica = replica.Value_;
+}
+
+Y_FORCE_INLINE void FromProto(TChunkReplicaWithMedium* replica, ui64 protoReplica)
+{
+ replica->Value_ = protoReplica;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_FORCE_INLINE TChunkReplicaWithLocation::TChunkReplicaWithLocation()
+ : TChunkReplicaWithMedium()
+ , ChunkLocationUuid_(InvalidChunkLocationUuid)
+{ }
+
+Y_FORCE_INLINE TChunkReplicaWithLocation::TChunkReplicaWithLocation(
+ TChunkReplicaWithMedium replica,
+ TChunkLocationUuid locationUuid)
+ : TChunkReplicaWithMedium(replica)
+ , ChunkLocationUuid_(locationUuid)
+{ }
+
+Y_FORCE_INLINE TChunkReplicaWithLocation::TChunkReplicaWithLocation(
+ NNodeTrackerClient::TNodeId nodeId,
+ int replicaIndex,
+ int mediumIndex,
+ TChunkLocationUuid locationUuid)
+ : TChunkReplicaWithMedium(nodeId, replicaIndex, mediumIndex)
+ , ChunkLocationUuid_(locationUuid)
+{ }
+
+Y_FORCE_INLINE TChunkLocationUuid TChunkReplicaWithLocation::GetChunkLocationUuid() const
+{
+ return ChunkLocationUuid_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_FORCE_INLINE TChunkReplica::TChunkReplica()
+ : Value_(NNodeTrackerClient::InvalidNodeId.Underlying())
+{ }
+
+Y_FORCE_INLINE TChunkReplica::TChunkReplica(ui32 value)
+ : Value_(value)
+{ }
+
+Y_FORCE_INLINE TChunkReplica::TChunkReplica(NNodeTrackerClient::TNodeId nodeId, int replicaIndex)
+ : Value_(static_cast<ui64>(nodeId.Underlying()) | (static_cast<ui64>(replicaIndex) << 24))
+{
+ YT_ASSERT(nodeId.Underlying() >= 0 && nodeId.Underlying() <= NNodeTrackerClient::MaxNodeId.Underlying());
+ YT_ASSERT(replicaIndex >= 0 && replicaIndex < ChunkReplicaIndexBound);
+}
+
+Y_FORCE_INLINE TChunkReplica::TChunkReplica(const TChunkReplicaWithMedium& replica)
+ : Value_(static_cast<ui64>(replica.GetNodeId().Underlying()) | (static_cast<ui64>(replica.GetReplicaIndex()) << 24))
+{ }
+
+Y_FORCE_INLINE NNodeTrackerClient::TNodeId TChunkReplica::GetNodeId() const
+{
+ return NNodeTrackerClient::TNodeId(Value_ & 0x00ffffff);
+}
+
+Y_FORCE_INLINE int TChunkReplica::GetReplicaIndex() const
+{
+ return (Value_ & 0x1f000000) >> 24;
+}
+
+Y_FORCE_INLINE void ToProto(ui32* value, TChunkReplica replica)
+{
+ *value = replica.Value_;
+}
+
+Y_FORCE_INLINE void FromProto(TChunkReplica* replica, ui32 value)
+{
+ replica->Value_ = value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_FORCE_INLINE TChunkIdWithIndex::TChunkIdWithIndex()
+ : ReplicaIndex(GenericChunkReplicaIndex)
+{ }
+
+Y_FORCE_INLINE TChunkIdWithIndex::TChunkIdWithIndex(TChunkId id, int replicaIndex)
+ : Id(id)
+ , ReplicaIndex(replicaIndex)
+{ }
+
+Y_FORCE_INLINE bool operator==(const TChunkIdWithIndex& lhs, const TChunkIdWithIndex& rhs)
+{
+ return lhs.Id == rhs.Id && lhs.ReplicaIndex == rhs.ReplicaIndex;
+}
+
+Y_FORCE_INLINE bool operator!=(const TChunkIdWithIndex& lhs, const TChunkIdWithIndex& rhs)
+{
+ return !(lhs == rhs);
+}
+
+Y_FORCE_INLINE bool operator<(const TChunkIdWithIndex& lhs, const TChunkIdWithIndex& rhs)
+{
+ if (lhs.Id == rhs.Id) {
+ return lhs.ReplicaIndex < rhs.ReplicaIndex;
+ }
+ return lhs.Id < rhs.Id;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_FORCE_INLINE TChunkIdWithIndexes::TChunkIdWithIndexes()
+ : TChunkIdWithIndex()
+ , MediumIndex(DefaultStoreMediumIndex)
+{ }
+
+Y_FORCE_INLINE TChunkIdWithIndexes::TChunkIdWithIndexes(const TChunkIdWithIndex& chunkIdWithIndex, int mediumIndex)
+ : TChunkIdWithIndex(chunkIdWithIndex)
+ , MediumIndex(mediumIndex)
+{ }
+
+Y_FORCE_INLINE TChunkIdWithIndexes::TChunkIdWithIndexes(TChunkId id, int replicaIndex, int mediumIndex)
+ : TChunkIdWithIndex(id, replicaIndex)
+ , MediumIndex(mediumIndex)
+{ }
+
+Y_FORCE_INLINE bool operator==(const TChunkIdWithIndexes& lhs, const TChunkIdWithIndexes& rhs)
+{
+ return static_cast<const TChunkIdWithIndex&>(lhs) == static_cast<const TChunkIdWithIndex&>(rhs) &&
+ lhs.MediumIndex == rhs.MediumIndex;
+}
+
+Y_FORCE_INLINE bool operator!=(const TChunkIdWithIndexes& lhs, const TChunkIdWithIndexes& rhs)
+{
+ return !(lhs == rhs);
+}
+
+Y_FORCE_INLINE bool operator<(const TChunkIdWithIndexes& lhs, const TChunkIdWithIndexes& rhs)
+{
+ const auto& lhs_ = static_cast<const TChunkIdWithIndex&>(lhs);
+ const auto& rhs_ = static_cast<const TChunkIdWithIndex&>(rhs);
+ if (lhs_ == rhs_) {
+ return lhs.MediumIndex < rhs.MediumIndex;
+ }
+ return lhs_ < rhs_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline bool IsArtifactChunkId(TChunkId id)
+{
+ return NObjectClient::TypeFromId(id) == NObjectClient::EObjectType::Artifact;
+}
+
+inline bool IsPhysicalChunkType(NObjectClient::EObjectType type)
+{
+ return
+ type == NObjectClient::EObjectType::Chunk ||
+ type == NObjectClient::EObjectType::ErasureChunk ||
+ type == NObjectClient::EObjectType::JournalChunk ||
+ type == NObjectClient::EObjectType::ErasureJournalChunk;
+}
+
+inline bool IsJournalChunkType(NObjectClient::EObjectType type)
+{
+ return
+ type == NObjectClient::EObjectType::JournalChunk ||
+ type == NObjectClient::EObjectType::ErasureJournalChunk;
+}
+
+inline bool IsJournalChunkId(TChunkId id)
+{
+ return IsJournalChunkType(NObjectClient::TypeFromId(id));
+}
+
+inline bool IsBlobChunkType(NObjectClient::EObjectType type)
+{
+ return
+ type == NObjectClient::EObjectType::Chunk ||
+ type == NObjectClient::EObjectType::ErasureChunk;
+}
+
+inline bool IsBlobChunkId(TChunkId id)
+{
+ return IsBlobChunkType(NObjectClient::TypeFromId(id));
+}
+
+inline bool IsErasureChunkType(NObjectClient::EObjectType type)
+{
+ return
+ type == NObjectClient::EObjectType::ErasureChunk ||
+ type == NObjectClient::EObjectType::ErasureJournalChunk;
+}
+
+inline bool IsErasureChunkId(TChunkId id)
+{
+ return IsErasureChunkType(NObjectClient::TypeFromId(id));
+}
+
+inline bool IsErasureChunkPartId(TChunkId id)
+{
+ auto type = NObjectClient::TypeFromId(id);
+ return
+ type >= NObjectClient::MinErasureChunkPartType && type <= NObjectClient::MaxErasureChunkPartType ||
+ type >= NObjectClient::MinErasureJournalChunkPartType && type <= NObjectClient::MaxErasureJournalChunkPartType;
+}
+
+inline bool IsRegularChunkType(NObjectClient::EObjectType type)
+{
+ return
+ type == NObjectClient::EObjectType::Chunk ||
+ type == NObjectClient::EObjectType::JournalChunk;
+}
+
+inline bool IsRegularChunkId(TChunkId id)
+{
+ return IsRegularChunkType(NObjectClient::TypeFromId(id));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
+
+inline size_t THash<NYT::NChunkClient::TChunkIdWithIndex>::operator()(const NYT::NChunkClient::TChunkIdWithIndex& value) const
+{
+ return THash<NYT::NChunkClient::TChunkId>()(value.Id) * 497 + value.ReplicaIndex;
+}
+
+inline size_t THash<NYT::NChunkClient::TChunkIdWithIndexes>::operator()(const NYT::NChunkClient::TChunkIdWithIndexes& value) const
+{
+ return THash<NYT::NChunkClient::TChunkId>()(value.Id) * 497 +
+ value.ReplicaIndex + value.MediumIndex * 8;
+}
diff --git a/yt/yt/client/chunk_client/chunk_replica.cpp b/yt/yt/client/chunk_client/chunk_replica.cpp
new file mode 100644
index 0000000000..a2262c04f3
--- /dev/null
+++ b/yt/yt/client/chunk_client/chunk_replica.cpp
@@ -0,0 +1,272 @@
+#include "chunk_replica.h"
+
+#include <yt/yt/client/node_tracker_client/node_directory.h>
+
+#include <yt/yt/client/object_client/helpers.h>
+
+#include <yt/yt/library/erasure/public.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/confirm_chunk_replica_info.pb.h>
+
+namespace NYT::NChunkClient {
+
+using namespace NNodeTrackerClient;
+using namespace NObjectClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkIdWithIndex::Save(TStreamSaveContext& context) const
+{
+ using NYT::Save;
+
+ Save(context, Id);
+ Save(context, ReplicaIndex);
+}
+
+void TChunkIdWithIndex::Load(TStreamLoadContext& context)
+{
+ using NYT::Load;
+
+ Load(context, Id);
+ Load(context, ReplicaIndex);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkIdWithIndexes::Save(TStreamSaveContext& context) const
+{
+ using NYT::Save;
+
+ Save(context, Id);
+ Save(context, ReplicaIndex);
+ Save(context, MediumIndex);
+}
+
+void TChunkIdWithIndexes::Load(TStreamLoadContext& context)
+{
+ using NYT::Load;
+
+ Load(context, Id);
+ Load(context, ReplicaIndex);
+ Load(context, MediumIndex);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(NProto::TConfirmChunkReplicaInfo* value, TChunkReplicaWithLocation replica)
+{
+ using NYT::ToProto;
+
+ value->set_replica(replica.Value_);
+ ToProto(value->mutable_location_uuid(), replica.ChunkLocationUuid_);
+}
+
+void FromProto(TChunkReplicaWithLocation* replica, NProto::TConfirmChunkReplicaInfo value)
+{
+ using NYT::FromProto;
+
+ replica->Value_ = value.replica();
+ replica->ChunkLocationUuid_ = FromProto<TGuid>(value.location_uuid());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithLocation replica, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%v", replica.GetNodeId());
+ if (replica.GetReplicaIndex() != GenericChunkReplicaIndex) {
+ builder->AppendFormat("/%v", replica.GetReplicaIndex());
+ }
+ builder->AppendFormat("@%v", replica.GetChunkLocationUuid());
+}
+
+TString ToString(TChunkReplicaWithLocation replica)
+{
+ return ToStringViaBuilder(replica);
+}
+
+void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithMedium replica, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%v", replica.GetNodeId());
+ if (replica.GetReplicaIndex() != GenericChunkReplicaIndex) {
+ builder->AppendFormat("/%v", replica.GetReplicaIndex());
+ }
+ if (replica.GetMediumIndex() == AllMediaIndex) {
+ builder->AppendString("@all");
+ } else if (replica.GetMediumIndex() != GenericMediumIndex) {
+ builder->AppendFormat("@%v", replica.GetMediumIndex());
+ }
+}
+
+TString ToString(TChunkReplicaWithMedium replica)
+{
+ return ToStringViaBuilder(replica);
+}
+
+void FormatValue(TStringBuilderBase* builder, TChunkReplica replica, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%v", replica.GetNodeId());
+ if (replica.GetReplicaIndex() != GenericChunkReplicaIndex) {
+ builder->AppendFormat("/%v", replica.GetReplicaIndex());
+ }
+}
+
+TString ToString(TChunkReplica replica)
+{
+ return ToStringViaBuilder(replica);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndex& id, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%v", id.Id);
+ if (id.ReplicaIndex != GenericChunkReplicaIndex) {
+ builder->AppendFormat("/%v", id.ReplicaIndex);
+ }
+}
+
+TString ToString(const TChunkIdWithIndex& id)
+{
+ return ToStringViaBuilder(id);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndexes& id, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%v", id.Id);
+ if (id.ReplicaIndex != GenericChunkReplicaIndex) {
+ builder->AppendFormat("/%v", id.ReplicaIndex);
+ }
+ if (id.MediumIndex == AllMediaIndex) {
+ builder->AppendString("@all");
+ } else if (id.MediumIndex != GenericMediumIndex) {
+ builder->AppendFormat("@%v", id.MediumIndex);
+ }
+}
+
+TString ToString(const TChunkIdWithIndexes& id)
+{
+ return ToStringViaBuilder(id);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TChunkReplicaAddressFormatter::TChunkReplicaAddressFormatter(TNodeDirectoryPtr nodeDirectory)
+ : NodeDirectory_(std::move(nodeDirectory))
+{ }
+
+void TChunkReplicaAddressFormatter::operator()(TStringBuilderBase* builder, TChunkReplicaWithMedium replica) const
+{
+ if (const auto* descriptor = NodeDirectory_->FindDescriptor(replica.GetNodeId())) {
+ builder->AppendFormat("%v", *descriptor);
+ } else {
+ builder->AppendFormat("<unresolved-%v>", replica.GetNodeId());
+ }
+ if (replica.GetReplicaIndex() != GenericChunkReplicaIndex) {
+ builder->AppendFormat("/%v", replica.GetReplicaIndex());
+ }
+ if (replica.GetMediumIndex() == AllMediaIndex) {
+ builder->AppendString("@all");
+ } else if (replica.GetMediumIndex() != GenericMediumIndex) {
+ builder->AppendFormat("@%v", replica.GetMediumIndex());
+ }
+}
+
+void TChunkReplicaAddressFormatter::operator()(TStringBuilderBase* builder, TChunkReplica replica) const
+{
+ const auto* descriptor = NodeDirectory_->FindDescriptor(replica.GetNodeId());
+ if (descriptor) {
+ builder->AppendFormat("%v", descriptor->GetDefaultAddress());
+ } else {
+ builder->AppendFormat("<unresolved-%v>", replica.GetNodeId());
+ }
+ if (replica.GetReplicaIndex() != GenericChunkReplicaIndex) {
+ builder->AppendFormat("/%v", replica.GetReplicaIndex());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TChunkReplicaList TChunkReplicaWithMedium::ToChunkReplicas(const TChunkReplicaWithMediumList& replicasWithMedia)
+{
+ TChunkReplicaList replicas;
+ replicas.reserve(replicasWithMedia.size());
+ for (auto replicaWithMedium : replicasWithMedia) {
+ replicas.push_back(replicaWithMedium.ToChunkReplica());
+ }
+ return replicas;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+EObjectType BaseErasurePartTypeFromPartId(TChunkId id)
+{
+ auto type = TypeFromId(id);
+ if (type >= MinErasureChunkPartType && type <= MaxErasureChunkPartType) {
+ return EObjectType::ErasureChunkPart_0;
+ } else if (type >= MinErasureJournalChunkPartType && type <= MaxErasureJournalChunkPartType) {
+ return EObjectType::ErasureJournalChunkPart_0;
+ } else {
+ YT_ABORT();
+ }
+}
+
+EObjectType BaseErasurePartTypeFromWholeId(TChunkId id)
+{
+ switch (TypeFromId(id)) {
+ case EObjectType::ErasureChunk: return EObjectType::ErasureChunkPart_0;
+ case EObjectType::ErasureJournalChunk: return EObjectType::ErasureJournalChunkPart_0;
+ default: YT_ABORT();
+ }
+}
+
+EObjectType WholeErasureTypeFromPartId(TChunkId id)
+{
+ auto type = TypeFromId(id);
+ if (type >= MinErasureChunkPartType && type <= MaxErasureChunkPartType) {
+ return EObjectType::ErasureChunk;
+ } else if (type >= MinErasureJournalChunkPartType && type <= MaxErasureJournalChunkPartType) {
+ return EObjectType::ErasureJournalChunk;
+ } else {
+ YT_ABORT();
+ }
+}
+
+} // namespace
+
+TChunkId ErasurePartIdFromChunkId(TChunkId id, int index)
+{
+ return ReplaceTypeInId(id, static_cast<EObjectType>(static_cast<int>(BaseErasurePartTypeFromWholeId(id)) + index));
+}
+
+TChunkId ErasureChunkIdFromPartId(TChunkId id)
+{
+ return ReplaceTypeInId(id, WholeErasureTypeFromPartId(id));
+}
+
+int ReplicaIndexFromErasurePartId(TChunkId id)
+{
+ int index = static_cast<int>(TypeFromId(id)) - static_cast<int>(BaseErasurePartTypeFromPartId(id));
+ YT_VERIFY(index >= 0 && index < ChunkReplicaIndexBound);
+ return index;
+}
+
+TChunkId EncodeChunkId(const TChunkIdWithIndex& idWithIndex)
+{
+ return IsErasureChunkId(idWithIndex.Id)
+ ? ErasurePartIdFromChunkId(idWithIndex.Id, idWithIndex.ReplicaIndex)
+ : idWithIndex.Id;
+}
+
+TChunkIdWithIndex DecodeChunkId(TChunkId id)
+{
+ return IsErasureChunkPartId(id)
+ ? TChunkIdWithIndex(ErasureChunkIdFromPartId(id), ReplicaIndexFromErasurePartId(id))
+ : TChunkIdWithIndex(id, GenericChunkReplicaIndex);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/chunk_replica.h b/yt/yt/client/chunk_client/chunk_replica.h
new file mode 100644
index 0000000000..081bf1f8f8
--- /dev/null
+++ b/yt/yt/client/chunk_client/chunk_replica.h
@@ -0,0 +1,250 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/node_tracker_client/public.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(ui64* protoReplica, TChunkReplicaWithMedium replica);
+void FromProto(TChunkReplicaWithMedium* replica, ui64 protoReplica);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A compact representation of |(nodeId, replicaIndex, mediumIndex)| triplet.
+class TChunkReplicaWithMedium
+{
+public:
+ TChunkReplicaWithMedium();
+ TChunkReplicaWithMedium(
+ NNodeTrackerClient::TNodeId nodeId,
+ int replicaIndex,
+ int mediumIndex);
+ // NB: Will be assigned to generic medium.
+ explicit TChunkReplicaWithMedium(TChunkReplica replica);
+
+ NNodeTrackerClient::TNodeId GetNodeId() const;
+ int GetReplicaIndex() const;
+ int GetMediumIndex() const;
+
+ TChunkReplica ToChunkReplica() const;
+ static TChunkReplicaList ToChunkReplicas(const TChunkReplicaWithMediumList& replicasWithMedia);
+
+private:
+ /*!
+ * Bits:
+ * 0-23: node id (24 bits)
+ * 24-28: replica index (5 bits)
+ * 29-37: medium index (7 bits)
+ */
+ ui64 Value_;
+
+ explicit TChunkReplicaWithMedium(ui64 value);
+
+ friend void ToProto(ui64* value, TChunkReplicaWithMedium replica);
+ friend void FromProto(TChunkReplicaWithMedium* replica, ui64 value);
+ friend void ToProto(NProto::TConfirmChunkReplicaInfo* value, TChunkReplicaWithLocation replica);
+ friend void FromProto(TChunkReplicaWithLocation* replica, NProto::TConfirmChunkReplicaInfo value);
+};
+
+// These protect from accidently serializing TChunkReplicaWithMedium as ui32.
+void ToProto(ui32* value, TChunkReplicaWithMedium replica) = delete;
+void FromProto(TChunkReplicaWithMedium* replica, ui32 value) = delete;
+
+void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithMedium replica, TStringBuf spec);
+TString ToString(TChunkReplicaWithMedium replica);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(NProto::TConfirmChunkReplicaInfo* value, TChunkReplicaWithLocation replica);
+void FromProto(TChunkReplicaWithLocation* replica, NProto::TConfirmChunkReplicaInfo value);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// COMPAT(kvk1920): Remove GetMediumIndex().
+class TChunkReplicaWithLocation
+ : public TChunkReplicaWithMedium
+{
+public:
+ TChunkReplicaWithLocation();
+ TChunkReplicaWithLocation(TChunkReplicaWithMedium replica, TChunkLocationUuid locationUuid);
+ TChunkReplicaWithLocation(
+ NNodeTrackerClient::TNodeId nodeId,
+ int replicaIndex,
+ int mediumIndex,
+ TChunkLocationUuid locationUuid);
+
+ TChunkLocationUuid GetChunkLocationUuid() const;
+
+ friend void ToProto(NProto::TConfirmChunkReplicaInfo* value, TChunkReplicaWithLocation replica);
+ friend void FromProto(TChunkReplicaWithLocation* replica, NProto::TConfirmChunkReplicaInfo value);
+
+private:
+ TChunkLocationUuid ChunkLocationUuid_;
+};
+
+void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithLocation replica, TStringBuf spec);
+TString ToString(TChunkReplicaWithLocation replica);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkReplica
+{
+public:
+ TChunkReplica();
+ TChunkReplica(NNodeTrackerClient::TNodeId nodeId, int replicaIndex);
+ TChunkReplica(const TChunkReplicaWithMedium& replica);
+
+ NNodeTrackerClient::TNodeId GetNodeId() const;
+ int GetReplicaIndex() const;
+
+private:
+ /*!
+ * Bits:
+ * 0-23: node id (24 bits)
+ * 24-28: replica index (5 bits)
+ */
+ ui32 Value_;
+
+ explicit TChunkReplica(ui32 value);
+
+ friend void ToProto(ui32* value, TChunkReplica replica);
+ friend void FromProto(TChunkReplica* replica, ui32 value);
+};
+
+void FormatValue(TStringBuilderBase* builder, TChunkReplica replica, TStringBuf spec);
+TString ToString(TChunkReplica replica);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TChunkIdWithIndex
+{
+ TChunkIdWithIndex();
+ TChunkIdWithIndex(TChunkId id, int replicaIndex);
+
+ TChunkId Id;
+ int ReplicaIndex;
+
+ void Save(TStreamSaveContext& context) const;
+ void Load(TStreamLoadContext& context);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TChunkIdWithIndexes
+ : public TChunkIdWithIndex
+{
+ TChunkIdWithIndexes();
+ TChunkIdWithIndexes(const TChunkIdWithIndex& chunkIdWithIndex, int mediumIndex);
+ TChunkIdWithIndexes(TChunkId id, int replicaIndex, int mediumIndex);
+
+ int MediumIndex;
+
+ void Save(TStreamSaveContext& context) const;
+ void Load(TStreamLoadContext& context);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TChunkIdWithIndex& lhs, const TChunkIdWithIndex& rhs);
+bool operator!=(const TChunkIdWithIndex& lhs, const TChunkIdWithIndex& rhs);
+bool operator<(const TChunkIdWithIndex& lhs, const TChunkIdWithIndex& rhs);
+
+TString ToString(const TChunkIdWithIndex& id);
+
+bool operator==(const TChunkIdWithIndexes& lhs, const TChunkIdWithIndexes& rhs);
+bool operator!=(const TChunkIdWithIndexes& lhs, const TChunkIdWithIndexes& rhs);
+bool operator<(const TChunkIdWithIndexes& lhs, const TChunkIdWithIndexes& rhs);
+
+TString ToString(const TChunkIdWithIndexes& id);
+
+//! Returns |true| iff this is an artifact chunk.
+bool IsArtifactChunkId(TChunkId id);
+
+//! Returns |true| iff this is a chunk or any type (journal or blob, replicated or erasure-coded).
+bool IsPhysicalChunkType(NObjectClient::EObjectType type);
+
+//! Returns |true| iff this is a journal chunk type.
+bool IsJournalChunkType(NObjectClient::EObjectType type);
+
+//! Returns |true| iff this is a journal chunk.
+bool IsJournalChunkId(TChunkId id);
+
+//! Returns |true| iff this is a blob chunk (regular or erasure).
+bool IsBlobChunkType(NObjectClient::EObjectType type);
+
+//! Returns |true| iff this is a blob chunk (regular or erasure).
+bool IsBlobChunkId(TChunkId id);
+
+//! Returns |true| iff this is an erasure chunk.
+bool IsErasureChunkType(NObjectClient::EObjectType type);
+
+//! Returns |true| iff this is an erasure chunk.
+bool IsErasureChunkId(TChunkId id);
+
+//! Returns |true| iff this is an erasure chunk part.
+bool IsErasureChunkPartId(TChunkId id);
+
+//! Returns |true| iff this is a regular (not erasure) chunk.
+inline bool IsRegularChunkType(NObjectClient::EObjectType type);
+
+//! Returns |true| iff this is a regular (not erasure) chunk.
+inline bool IsRegularChunkId(TChunkId id);
+
+//! Returns id for a part of a given erasure chunk.
+TChunkId ErasurePartIdFromChunkId(TChunkId id, int index);
+
+//! Returns the whole chunk id for a given erasure chunk part id.
+TChunkId ErasureChunkIdFromPartId(TChunkId id);
+
+//! Returns part index for a given erasure chunk part id.
+int ReplicaIndexFromErasurePartId(TChunkId id);
+
+//! For usual chunks, preserves the id.
+//! For erasure chunks, constructs the part id using the given replica index.
+TChunkId EncodeChunkId(const TChunkIdWithIndex& idWithIndex);
+
+//! For regular chunks, preserves the id and returns #GenericChunkReplicaIndex.
+//! For erasure chunk parts, constructs the whole chunk id and extracts part index.
+TChunkIdWithIndex DecodeChunkId(TChunkId id);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkReplicaAddressFormatter
+{
+public:
+ explicit TChunkReplicaAddressFormatter(NNodeTrackerClient::TNodeDirectoryPtr nodeDirectory);
+
+ void operator()(TStringBuilderBase* builder, TChunkReplicaWithMedium replica) const;
+
+ void operator()(TStringBuilderBase* builder, TChunkReplica replica) const;
+
+private:
+ NNodeTrackerClient::TNodeDirectoryPtr NodeDirectory_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
+
+//! A hasher for TChunkIdWithIndex.
+template <>
+struct THash<NYT::NChunkClient::TChunkIdWithIndex>
+{
+ size_t operator()(const NYT::NChunkClient::TChunkIdWithIndex& value) const;
+};
+
+//! A hasher for TChunkIdWithIndexes.
+template <>
+struct THash<NYT::NChunkClient::TChunkIdWithIndexes>
+{
+ size_t operator()(const NYT::NChunkClient::TChunkIdWithIndexes& value) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define CHUNK_REPLICA_INL_H_
+#include "chunk_replica-inl.h"
+#undef CHUNK_REPLICA_INL_H_
diff --git a/yt/yt/client/chunk_client/config.cpp b/yt/yt/client/chunk_client/config.cpp
new file mode 100644
index 0000000000..67a492aba1
--- /dev/null
+++ b/yt/yt/client/chunk_client/config.cpp
@@ -0,0 +1,430 @@
+#include "config.h"
+
+#include <yt/yt/core/rpc/config.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TFetchChunkSpecConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_chunks_per_fetch", &TThis::MaxChunksPerFetch)
+ .GreaterThan(0)
+ .Default(100000);
+ registrar.Parameter("max_chunks_per_locate_request", &TThis::MaxChunksPerLocateRequest)
+ .GreaterThan(0)
+ .Default(10000);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TFetcherConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("node_rpc_timeout", &TThis::NodeRpcTimeout)
+ .Default(TDuration::Seconds(30));
+
+ registrar.Parameter("node_ban_duration", &TThis::NodeBanDuration)
+ .Default(TDuration::Seconds(5));
+
+ registrar.Parameter("backoff_time", &TThis::BackoffTime)
+ .Default(TDuration::MilliSeconds(100));
+
+ registrar.Parameter("max_chunks_per_node_fetch", &TThis::MaxChunksPerNodeFetch)
+ .Default(500);
+
+ registrar.Parameter("node_directory_synchronization_timeout", &TThis::NodeDirectorySynchronizationTimeout)
+ .Default(TDuration::Minutes(5));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TBlockReordererConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_block_reordering", &TThis::EnableBlockReordering)
+ .Default(false);
+
+ registrar.Parameter("shuffle_blocks", &TThis::ShuffleBlocks)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkSliceFetcherConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_slices_per_fetch", &TThis::MaxSlicesPerFetch)
+ .GreaterThan(0)
+ .Default(10'000);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TEncodingWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("encode_window_size", &TThis::EncodeWindowSize)
+ .Default(16_MB)
+ .GreaterThan(0);
+ registrar.Parameter("default_compression_ratio", &TThis::DefaultCompressionRatio)
+ .Default(0.2);
+ registrar.Parameter("verify_compression", &TThis::VerifyCompression)
+ .Default(true);
+ registrar.Parameter("compute_checksum", &TThis::ComputeChecksum)
+ .Default(true);
+ registrar.Parameter("compression_concurrency", &TThis::CompressionConcurrency)
+ .Default(1)
+ .GreaterThan(0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TRemoteReaderConfigBase::Register(TRegistrar registrar)
+{
+ registrar.Parameter("disk_queue_size_factor", &TThis::DiskQueueSizeFactor)
+ .Default(1.0);
+ registrar.Parameter("net_queue_size_factor", &TThis::NetQueueSizeFactor)
+ .Default(0.5);
+
+ registrar.Parameter("suspicious_node_grace_period", &TThis::SuspiciousNodeGracePeriod)
+ .Default();
+
+ registrar.Parameter("use_direct_io", &TThis::UseDirectIO)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TReplicationReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("block_rpc_timeout", &TThis::BlockRpcTimeout)
+ .Default(TDuration::Seconds(120));
+ registrar.Parameter("block_rpc_hedging_delay", &TThis::BlockRpcHedgingDelay)
+ .Default();
+ registrar.Parameter("lookup_rpc_hedging_delay", &TThis::LookupRpcHedgingDelay)
+ .Default();
+ registrar.Parameter("cancel_primary_block_rpc_request_on_hedging", &TThis::CancelPrimaryBlockRpcRequestOnHedging)
+ .Default(false);
+ registrar.Parameter("cancel_primary_lookup_rpc_request_on_hedging", &TThis::CancelPrimaryLookupRpcRequestOnHedging)
+ .Default(false);
+ registrar.Parameter("lookup_rpc_timeout", &TThis::LookupRpcTimeout)
+ .Default(TDuration::Seconds(30));
+ registrar.Parameter("meta_rpc_timeout", &TThis::MetaRpcTimeout)
+ .Default(TDuration::Seconds(30));
+ registrar.Parameter("meta_rpc_hedging_delay", &TThis::MetaRpcHedgingDelay)
+ .Default();
+ registrar.Parameter("probe_rpc_timeout", &TThis::ProbeRpcTimeout)
+ .Default(TDuration::Seconds(5));
+ registrar.Parameter("probe_peer_count", &TThis::ProbePeerCount)
+ .Default(3)
+ .GreaterThan(0);
+ registrar.Parameter("retry_count", &TThis::RetryCount)
+ .Default(20);
+ registrar.Parameter("fail_on_no_seeds", &TThis::FailOnNoSeeds)
+ .Default(false);
+ registrar.Parameter("min_backoff_time", &TThis::MinBackoffTime)
+ .Default(TDuration::Seconds(3));
+ registrar.Parameter("max_backoff_time", &TThis::MaxBackoffTime)
+ .Default(TDuration::Seconds(60));
+ registrar.Parameter("backoff_time_multiplier", &TThis::BackoffTimeMultiplier)
+ .GreaterThan(1)
+ .Default(1.5);
+ registrar.Parameter("pass_count", &TThis::PassCount)
+ .Default(500);
+ registrar.Parameter("fetch_from_peers", &TThis::FetchFromPeers)
+ .Default(true);
+ registrar.Parameter("peer_expiration_timeout", &TThis::PeerExpirationTimeout)
+ .Default(TDuration::Seconds(300));
+ registrar.Parameter("populate_cache", &TThis::PopulateCache)
+ .Default(true);
+ registrar.Parameter("prefer_local_host", &TThis::PreferLocalHost)
+ .Default(false);
+ registrar.Parameter("prefer_local_rack", &TThis::PreferLocalRack)
+ .Default(false);
+ registrar.Parameter("prefer_local_data_center", &TThis::PreferLocalDataCenter)
+ .Default(true);
+ registrar.Parameter("max_ban_count", &TThis::MaxBanCount)
+ .Default(5);
+ registrar.Parameter("enable_workload_fifo_scheduling", &TThis::EnableWorkloadFifoScheduling)
+ .Default(true);
+ registrar.Parameter("retry_timeout", &TThis::RetryTimeout)
+ .Default(TDuration::Minutes(3));
+ registrar.Parameter("session_timeout", &TThis::SessionTimeout)
+ .Default(TDuration::Minutes(20));
+ registrar.Parameter("lookup_request_pass_count", &TThis::LookupRequestPassCount)
+ .GreaterThan(0)
+ .Default(10);
+ registrar.Parameter("lookup_request_retry_count", &TThis::LookupRequestRetryCount)
+ .GreaterThan(0)
+ .Default(5);
+ registrar.Parameter("use_async_block_cache", &TThis::UseAsyncBlockCache)
+ .Default(false);
+ registrar.Parameter("use_block_cache", &TThis::UseBlockCache)
+ .Default(true);
+ registrar.Parameter("prolonged_discard_seeds_delay", &TThis::ProlongedDiscardSeedsDelay)
+ .Default(TDuration::Minutes(1));
+ registrar.Parameter("enable_chunk_meta_cache", &TThis::EnableChunkMetaCache)
+ .Default(true);
+ registrar.Parameter("ban_peers_permanently", &TThis::BanPeersPermanently)
+ .Default(true);
+ registrar.Parameter("enable_local_throttling", &TThis::EnableLocalThrottling)
+ .Default(false);
+ registrar.Parameter("chunk_meta_cache_failure_probability", &TThis::ChunkMetaCacheFailureProbability)
+ .Default();
+
+ registrar.Postprocessor([] (TThis* config) {
+ // Seems unreasonable to make backoff greater than half of total session timeout.
+ config->MaxBackoffTime = std::min(config->MaxBackoffTime, config->SessionTimeout / 2);
+ config->RetryTimeout = std::min(config->RetryTimeout, config->SessionTimeout);
+
+ // Rpc timeout should not exceed session timeout.
+ config->BlockRpcTimeout = std::min(config->BlockRpcTimeout, config->RetryTimeout);
+ config->LookupRpcTimeout = std::min(config->LookupRpcTimeout, config->RetryTimeout);
+ config->MetaRpcTimeout = std::min(config->MetaRpcTimeout, config->RetryTimeout);
+ config->ProbeRpcTimeout = std::min(config->ProbeRpcTimeout, config->RetryTimeout);
+
+ // These are supposed to be not greater than PassCount and RetryCount.
+ config->LookupRequestPassCount = std::min(config->LookupRequestPassCount, config->PassCount);
+ config->LookupRequestRetryCount = std::min(config->LookupRequestRetryCount, config->RetryCount);
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TBlockFetcherConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("window_size", &TThis::WindowSize)
+ .Default(20_MB)
+ .GreaterThan(0);
+ registrar.Parameter("group_size", &TThis::GroupSize)
+ .Default(15_MB)
+ .GreaterThan(0);
+
+ registrar.Parameter("use_uncompressed_block_cache", &TThis::UseUncompressedBlockCache)
+ .Default(true);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->GroupSize > config->WindowSize) {
+ THROW_ERROR_EXCEPTION("\"group_size\" cannot be larger than \"window_size\"");
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TErasureReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_auto_repair", &TThis::EnableAutoRepair)
+ .Default(true);
+ registrar.Parameter("replication_reader_speed_limit_per_sec", &TThis::ReplicationReaderSpeedLimitPerSec)
+ .Default(5_MB);
+ registrar.Parameter("slow_reader_expiration_timeout", &TThis::SlowReaderExpirationTimeout)
+ .Default(TDuration::Minutes(2));
+ registrar.Parameter("replication_reader_timeout", &TThis::ReplicationReaderTimeout)
+ .Default(TDuration::Seconds(60));
+ registrar.Parameter("replication_reader_failure_timeout", &TThis::ReplicationReaderFailureTimeout)
+ .Default(TDuration::Minutes(10));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TMultiChunkReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_buffer_size", &TThis::MaxBufferSize)
+ .GreaterThan(0L)
+ .LessThanOrEqual(10_GB)
+ .Default(100_MB);
+ registrar.Parameter("max_parallel_readers", &TThis::MaxParallelReaders)
+ .GreaterThanOrEqual(1)
+ .LessThanOrEqual(1000)
+ .Default(512);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->MaxBufferSize < 2 * config->WindowSize) {
+ THROW_ERROR_EXCEPTION("\"max_buffer_size\" cannot be less than twice \"window_size\"");
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TReplicationWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("send_window_size", &TThis::SendWindowSize)
+ .Default(32_MB)
+ .GreaterThan(0);
+ registrar.Parameter("group_size", &TThis::GroupSize)
+ .Default(10_MB)
+ .GreaterThan(0);
+ registrar.Parameter("node_channel", &TThis::NodeChannel)
+ .DefaultNew();
+ registrar.Parameter("node_rpc_timeout", &TThis::NodeRpcTimeout)
+ .Default(TDuration::Seconds(300));
+ registrar.Parameter("upload_replication_factor", &TThis::UploadReplicationFactor)
+ .GreaterThanOrEqual(1)
+ .Default(2);
+ registrar.Parameter("ban_failed_nodes", &TThis::BanFailedNodes)
+ .Default(true);
+ registrar.Parameter("min_upload_replication_factor", &TThis::MinUploadReplicationFactor)
+ .Default(2)
+ .GreaterThanOrEqual(1);
+ registrar.Parameter("direct_upload_node_count", &TThis::DirectUploadNodeCount)
+ .Default();
+ registrar.Parameter("prefer_local_host", &TThis::PreferLocalHost)
+ .Default(true);
+ registrar.Parameter("node_ping_interval", &TThis::NodePingPeriod)
+ .Default(TDuration::Seconds(10));
+ registrar.Parameter("populate_cache", &TThis::PopulateCache)
+ .Default(false);
+ registrar.Parameter("sync_on_close", &TThis::SyncOnClose)
+ .Default(true);
+ registrar.Parameter("enable_direct_io", &TThis::EnableDirectIO)
+ .Default(false);
+ registrar.Parameter("enable_early_finish", &TThis::EnableEarlyFinish)
+ .Default(false);
+ registrar.Parameter("allocate_write_targets_backoff_time", &TThis::AllocateWriteTargetsBackoffTime)
+ .Default(TDuration::Seconds(5));
+ registrar.Parameter("allocate_write_targets_retry_count", &TThis::AllocateWriteTargetsRetryCount)
+ .Default(10);
+
+ registrar.Parameter("testing_delay", &TThis::TestingDelay)
+ .Default();
+
+ registrar.Preprocessor([] (TThis* config) {
+ config->NodeChannel->RetryBackoffTime = TDuration::Seconds(10);
+ config->NodeChannel->RetryAttempts = 100;
+ });
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (!config->DirectUploadNodeCount) {
+ return;
+ }
+
+ if (*config->DirectUploadNodeCount < 1) {
+ THROW_ERROR_EXCEPTION("\"direct_upload_node_count\" cannot be less that 1");
+ }
+ });
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->SendWindowSize < config->GroupSize) {
+ THROW_ERROR_EXCEPTION("\"send_window_size\" cannot be less than \"group_size\"");
+ }
+ });
+}
+
+int TReplicationWriterConfig::GetDirectUploadNodeCount()
+{
+ auto replicationFactor = std::min(MinUploadReplicationFactor, UploadReplicationFactor);
+ if (DirectUploadNodeCount) {
+ return std::min(*DirectUploadNodeCount, replicationFactor);
+ }
+
+ return std::max(static_cast<int>(std::sqrt(replicationFactor)), 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TErasureWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_erasure_target_node_reallocation", &TThis::EnableErasureTargetNodeReallocation)
+ .Default(false);
+
+ registrar.Parameter("erasure_window_size", &TThis::ErasureWindowSize)
+ .Default(8_MB)
+ .GreaterThan(0);
+
+ registrar.Parameter("writer_window_size", &TThis::WriterWindowSize)
+ .Default(64_MB)
+ .GreaterThan(0);
+ registrar.Parameter("writer_group_size", &TThis::WriterGroupSize)
+ .Default(16_MB)
+ .GreaterThan(0);
+
+ registrar.Parameter("desired_segment_part_size", &TThis::DesiredSegmentPartSize)
+ .Default(0)
+ .GreaterThanOrEqual(0);
+
+ registrar.Parameter("erasure_store_original_block_checksums", &TThis::ErasureStoreOriginalBlockChecksums)
+ .Default(false);
+
+ registrar.Parameter("enable_striped_erasure", &TThis::EnableStripedErasure)
+ .Default(false);
+
+ registrar.Parameter("erasure_stripe_size", &TThis::ErasureStripeSize)
+ .Default()
+ .GreaterThan(0);
+
+ registrar.Parameter("use_effective_erasure_codecs", &TThis::UseEffectiveErasureCodecs)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TMultiChunkWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("desired_chunk_size", &TThis::DesiredChunkSize)
+ .GreaterThan(0)
+ .Default(2_GB);
+
+ registrar.Parameter("desired_chunk_weight", &TThis::DesiredChunkWeight)
+ .GreaterThan(0)
+ .Default(100_GB);
+
+ registrar.Parameter("max_meta_size", &TThis::MaxMetaSize)
+ .GreaterThan(0)
+ .LessThanOrEqual(64_MB)
+ .Default(30_MB);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TEncodingWriterOptions::Register(TRegistrar registrar)
+{
+ registrar.Parameter("compression_codec", &TThis::CompressionCodec)
+ .Default(NCompression::ECodec::None);
+ registrar.Parameter("chunks_eden", &TThis::ChunksEden)
+ .Default(false);
+ registrar.Parameter("set_chunk_creation_time", &TThis::SetChunkCreationTime)
+ .Default(true)
+ .DontSerializeDefault();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkFragmentReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("peer_info_expiration_timeout", &TThis::PeerInfoExpirationTimeout)
+ .Default(TDuration::Minutes(30));
+
+ registrar.Parameter("seeds_expiration_timeout", &TThis::SeedsExpirationTimeout)
+ .Default(TDuration::Seconds(3));
+
+ registrar.Parameter("periodic_update_delay", &TThis::PeriodicUpdateDelay)
+ .GreaterThan(TDuration::Zero())
+ .Default(TDuration::Seconds(10));
+
+ registrar.Parameter("probe_chunk_set_rpc_timeout", &TThis::ProbeChunkSetRpcTimeout)
+ .Default(TDuration::Seconds(5));
+ registrar.Parameter("get_chunk_fragment_set_rpc_timeout", &TThis::GetChunkFragmentSetRpcTimeout)
+ .Default(TDuration::Seconds(15));
+
+ registrar.Parameter("fragment_read_hedging_delay", &TThis::FragmentReadHedgingDelay)
+ .Default();
+
+ registrar.Parameter("retry_count_limit", &TThis::RetryCountLimit)
+ .GreaterThanOrEqual(1)
+ .Default(10);
+ registrar.Parameter("retry_backoff_time", &TThis::RetryBackoffTime)
+ .Default(TDuration::MilliSeconds(10));
+ registrar.Parameter("read_time_limit", &TThis::ReadTimeLimit)
+ .Default(TDuration::Seconds(15));
+
+ registrar.Parameter("chunk_info_cache_expiration_timeout", &TThis::ChunkInfoCacheExpirationTimeout)
+ .Default(TDuration::Seconds(30));
+
+ registrar.Parameter("max_inflight_fragment_length", &TThis::MaxInflightFragmentLength)
+ .Default(16_MB);
+ registrar.Parameter("max_inflight_fragment_count", &TThis::MaxInflightFragmentCount)
+ .Default(8192);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/config.h b/yt/yt/client/chunk_client/config.h
new file mode 100644
index 0000000000..2472692860
--- /dev/null
+++ b/yt/yt/client/chunk_client/config.h
@@ -0,0 +1,499 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/misc/config.h>
+
+#include <yt/yt/core/ytree/yson_serializable.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFetchChunkSpecConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ int MaxChunksPerFetch;
+ int MaxChunksPerLocateRequest;
+
+ REGISTER_YSON_STRUCT(TFetchChunkSpecConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TFetchChunkSpecConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFetcherConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ TDuration NodeRpcTimeout;
+
+ //! If node throttled fetch request, it becomes banned for this period of time.
+ TDuration NodeBanDuration;
+
+ //! Time to sleep before next fetching round if no requests were performed.
+ TDuration BackoffTime;
+
+ int MaxChunksPerNodeFetch;
+
+ //! Timeout when waiting for all replicas to appear in the given node directory.
+ TDuration NodeDirectorySynchronizationTimeout;
+
+ REGISTER_YSON_STRUCT(TFetcherConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TFetcherConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBlockReordererConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ bool EnableBlockReordering;
+
+ //! Instead of grouping blocks by column groups, shuffle them.
+ //! Used only for testing purposes.
+ bool ShuffleBlocks;
+
+ REGISTER_YSON_STRUCT(TBlockReordererConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TBlockReordererConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkSliceFetcherConfig
+ : public TFetcherConfig
+{
+public:
+ int MaxSlicesPerFetch;
+
+ REGISTER_YSON_STRUCT(TChunkSliceFetcherConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkSliceFetcherConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEncodingWriterConfig
+ : public virtual TWorkloadConfig
+ , public virtual TBlockReordererConfig
+{
+public:
+ i64 EncodeWindowSize;
+ double DefaultCompressionRatio;
+ bool VerifyCompression;
+ bool ComputeChecksum;
+ int CompressionConcurrency;
+
+ REGISTER_YSON_STRUCT(TEncodingWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TEncodingWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoteReaderConfigBase
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ //! Factors to calculate peer load as linear combination of disk queue and net queue.
+ double NetQueueSizeFactor;
+ double DiskQueueSizeFactor;
+
+ //! Will locate new replicas from master
+ //! if node was suspicious for at least the period (unless null).
+ std::optional<TDuration> SuspiciousNodeGracePeriod;
+
+ //! Will open and read with DirectIO (unless already opened w/o DirectIO or disabled via location config).
+ bool UseDirectIO;
+
+ REGISTER_YSON_STRUCT(TRemoteReaderConfigBase);
+
+ static void Register(TRegistrar registrar);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReplicationReaderConfig
+ : public virtual TRemoteReaderConfigBase
+{
+public:
+ //! Timeout for a block request.
+ TDuration BlockRpcTimeout;
+
+ //! Delay before sending a hedged block request. If null then hedging is disabled.
+ //! NB: Hedging policy may be overridden via hedging manager.
+ std::optional<TDuration> BlockRpcHedgingDelay;
+
+ //! Same as above but for a LookupRows rpc.
+ std::optional<TDuration> LookupRpcHedgingDelay;
+
+ //! Whether to cancel the primary block request when backup one is sent.
+ bool CancelPrimaryBlockRpcRequestOnHedging;
+
+ //! Same as above but for a LookupRows rpc.
+ bool CancelPrimaryLookupRpcRequestOnHedging;
+
+ //! Timeout for a lookup request.
+ TDuration LookupRpcTimeout;
+
+ //! Timeout for a meta request.
+ TDuration MetaRpcTimeout;
+
+ //! Delay before sending for a hedged meta request. If null then hedging is disabled.
+ //! NB: Hedging policy may be overridden via hedging manager.
+ std::optional<TDuration> MetaRpcHedgingDelay;
+
+ //! Timeout for a queue size probing request.
+ TDuration ProbeRpcTimeout;
+
+ //! Maximum number of peers to poll for queue length each round.
+ int ProbePeerCount;
+
+ //! Maximum number of attempts to fetch new seeds.
+ int RetryCount;
+
+ //! Fail read session immediately if master reports no seeds for chunk.
+ bool FailOnNoSeeds;
+
+ //! Time to wait before making another pass with same seeds.
+ //! Increases exponentially with every pass, from MinPassBackoffTime to MaxPassBackoffTime.
+ TDuration MinBackoffTime;
+ TDuration MaxBackoffTime;
+ double BackoffTimeMultiplier;
+
+ //! Maximum number of passes with same seeds.
+ int PassCount;
+
+ //! Enable fetching blocks from peers suggested by seeds.
+ bool FetchFromPeers;
+
+ //! Timeout after which a node forgets about the peer.
+ //! Only makes sense if the reader is equipped with peer descriptor.
+ TDuration PeerExpirationTimeout;
+
+ //! If |true| then fetched blocks are cached by the node.
+ bool PopulateCache;
+
+ //! If |true| then local data center replicas are unconditionally preferred to remote replicas.
+ bool PreferLocalDataCenter;
+
+ //! If |true| then local rack replicas are unconditionally preferred to remote replicas.
+ bool PreferLocalRack;
+
+ //! If |true| then local host replicas are unconditionally preferred to any other replicas.
+ bool PreferLocalHost;
+
+ //! If peer ban counter exceeds #MaxBanCount, peer is banned forever.
+ int MaxBanCount;
+
+ //! If |true|, then workload descriptors are annotated with the read session start time
+ //! and are thus scheduled in FIFO order.
+ bool EnableWorkloadFifoScheduling;
+
+ //! Total retry timeout, helps when we are doing too many passes.
+ TDuration RetryTimeout;
+
+ //! Total session timeout (for ReadBlocks and GetMeta calls).
+ TDuration SessionTimeout;
+
+ //! Maximum number of passes within single retry for lookup request.
+ int LookupRequestPassCount;
+
+ //! Maximum number of retries for lookup request.
+ int LookupRequestRetryCount;
+
+ //! If |true| block cache will be accessed via asynchronous interface, if |false|
+ //! synchronous interface will be used.
+ bool UseAsyncBlockCache;
+
+ //! If |true| replication reader will try to fetch blocks from local block cache.
+ bool UseBlockCache;
+
+ //! Is used to increase interval between Locates
+ //! that are called for discarding seeds that are suspicious.
+ TDuration ProlongedDiscardSeedsDelay;
+
+ //! If |true| GetMeta() will be performed via provided ChunkMetaCache.
+ //! If ChunkMetaCache is nullptr or partition tag is specified, this option has no effect.
+ bool EnableChunkMetaCache;
+
+ //! If |true| reader will retain a set of peers that will be banned for every session.
+ bool BanPeersPermanently;
+
+ //! For testing purposes.
+ //! If |true| network throttlers will be applied even in case of requests to local host.
+ bool EnableLocalThrottling;
+
+ //! For testing purposes.
+ //! Unless null, reader will simulate failure of accessing chunk meta cache with such probability.
+ std::optional<double> ChunkMetaCacheFailureProbability;
+
+ REGISTER_YSON_STRUCT(TReplicationReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TReplicationReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBlockFetcherConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ //! Prefetch window size (in bytes).
+ i64 WindowSize;
+
+ //! Maximum amount of data to be transferred via a single RPC request.
+ i64 GroupSize;
+
+ //! If |True| block fetcher will try to fetch block from local uncompressed block cache.
+ bool UseUncompressedBlockCache;
+
+ REGISTER_YSON_STRUCT(TBlockFetcherConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TBlockFetcherConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TErasureReaderConfig
+ : public virtual TReplicationReaderConfig
+ , public virtual TBlockFetcherConfig
+{
+public:
+ bool EnableAutoRepair;
+ double ReplicationReaderSpeedLimitPerSec;
+ TDuration SlowReaderExpirationTimeout;
+ TDuration ReplicationReaderTimeout;
+ TDuration ReplicationReaderFailureTimeout;
+
+ REGISTER_YSON_STRUCT(TErasureReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TErasureReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMultiChunkReaderConfig
+ : public virtual TErasureReaderConfig
+ , public virtual TBlockFetcherConfig
+ , public virtual TFetchChunkSpecConfig
+ , public virtual TWorkloadConfig
+{
+public:
+ i64 MaxBufferSize;
+ int MaxParallelReaders;
+
+ REGISTER_YSON_STRUCT(TMultiChunkReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TMultiChunkReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReplicationWriterConfig
+ : public virtual TWorkloadConfig
+ , public virtual TBlockReordererConfig
+{
+public:
+ //! Maximum window size (in bytes).
+ i64 SendWindowSize;
+
+ //! Maximum group size (in bytes).
+ i64 GroupSize;
+
+ //! RPC requests timeout.
+ /*!
+ * This timeout is especially useful for |PutBlocks| calls to ensure that
+ * uploading is not stalled.
+ */
+ TDuration NodeRpcTimeout;
+
+ NRpc::TRetryingChannelConfigPtr NodeChannel;
+
+ int UploadReplicationFactor;
+
+ int MinUploadReplicationFactor;
+
+ std::optional<int> DirectUploadNodeCount;
+
+ bool PreferLocalHost;
+
+ bool BanFailedNodes;
+
+ //! Interval between consecutive pings to Data Nodes.
+ TDuration NodePingPeriod;
+
+ //! If |true| then written blocks are cached by the node.
+ bool PopulateCache;
+
+ //! If |true| then the chunk is fsynced to disk upon closing.
+ bool SyncOnClose;
+
+ bool EnableDirectIO;
+
+ //! If |true| then the chunk is finished as soon as MinUploadReplicationFactor chunks are written.
+ bool EnableEarlyFinish;
+
+ TDuration AllocateWriteTargetsBackoffTime;
+
+ int AllocateWriteTargetsRetryCount;
+
+ std::optional<TDuration> TestingDelay;
+
+ int GetDirectUploadNodeCount();
+
+ REGISTER_YSON_STRUCT(TReplicationWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TReplicationWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TErasureWriterConfig
+ : public virtual TBlockReordererConfig
+{
+public:
+ i64 WriterWindowSize;
+ i64 WriterGroupSize;
+
+ // TODO(gritukan): Drop.
+ std::optional<i64> ErasureStripeSize;
+ i64 ErasureWindowSize;
+ bool ErasureStoreOriginalBlockChecksums;
+
+ i64 DesiredSegmentPartSize;
+
+ bool EnableErasureTargetNodeReallocation;
+
+ bool EnableStripedErasure;
+
+ bool UseEffectiveErasureCodecs;
+
+ REGISTER_YSON_STRUCT(TErasureWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TErasureWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMultiChunkWriterConfig
+ : public TReplicationWriterConfig
+ , public TErasureWriterConfig
+{
+public:
+ i64 DesiredChunkSize;
+ i64 DesiredChunkWeight;
+ i64 MaxMetaSize;
+
+ REGISTER_YSON_STRUCT(TMultiChunkWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TMultiChunkWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMemoryTrackedWriterOptions
+ : public NYTree::TYsonStruct
+{
+public:
+ IMemoryUsageTrackerPtr MemoryTracker;
+
+ IMemoryReferenceTrackerPtr MemoryReferenceTracker;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEncodingWriterOptions
+ : public virtual TMemoryTrackedWriterOptions
+{
+public:
+ NCompression::ECodec CompressionCodec;
+ bool ChunksEden;
+ bool SetChunkCreationTime;
+
+ REGISTER_YSON_STRUCT(TEncodingWriterOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TEncodingWriterOptions)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkFragmentReaderConfig
+ : public virtual TRemoteReaderConfigBase
+{
+public:
+ //! Expiration timeout of corresponding sync expiring cache.
+ TDuration PeerInfoExpirationTimeout;
+
+ //! Minimal delay between sequential chunk replica locations.
+ TDuration SeedsExpirationTimeout;
+
+ //! Delay between background cache updates.
+ TDuration PeriodicUpdateDelay;
+
+ //! RPC timeouts of ProbeChunkSet and GetChunkFragmentSet.
+ TDuration ProbeChunkSetRpcTimeout;
+ TDuration GetChunkFragmentSetRpcTimeout;
+
+ //! Delay before sending a hedged request. If null then hedging is disabled.
+ //! NB: This option may be overridden via hedging manager.
+ std::optional<TDuration> FragmentReadHedgingDelay;
+
+ //! Limit on retry count.
+ int RetryCountLimit;
+ //! Time between retries.
+ TDuration RetryBackoffTime;
+ //! Maximum time to serve fragments read request.
+ TDuration ReadTimeLimit;
+
+ //! Chunk that was not accessed for the time by user
+ //! will stop being accessed within periodic updates and then will be evicted via expiring cache logic.
+ TDuration ChunkInfoCacheExpirationTimeout;
+
+ //! Upper bound on length of simultaneously requested fragments withing a reading session.
+ i64 MaxInflightFragmentLength;
+
+ //! Upper bound on count of simultaneously requested fragments withing a reading session.
+ i64 MaxInflightFragmentCount;
+
+ REGISTER_YSON_STRUCT(TChunkFragmentReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkFragmentReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/data_statistics.cpp b/yt/yt/client/chunk_client/data_statistics.cpp
new file mode 100644
index 0000000000..6e72d1df97
--- /dev/null
+++ b/yt/yt/client/chunk_client/data_statistics.cpp
@@ -0,0 +1,217 @@
+#include "data_statistics.h"
+
+#include <yt/yt/client/chunk_client/data_statistics.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+namespace NYT::NChunkClient {
+
+using namespace NCompression;
+using namespace NYTree;
+using namespace NYson;
+
+using ::ToString;
+using ::FromString;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool HasInvalidDataWeight(const TDataStatistics& statistics)
+{
+ return statistics.has_data_weight() && statistics.data_weight() == -1;
+}
+
+bool HasInvalidUnmergedRowCount(const TDataStatistics& statistics)
+{
+ return statistics.has_unmerged_row_count() && statistics.unmerged_row_count() == -1;
+}
+
+bool HasInvalidUnmergedDataWeight(const TDataStatistics& statistics)
+{
+ return statistics.has_unmerged_data_weight() && statistics.unmerged_data_weight() == -1;
+}
+
+TDataStatistics& operator += (TDataStatistics& lhs, const TDataStatistics& rhs)
+{
+ lhs.set_uncompressed_data_size(lhs.uncompressed_data_size() + rhs.uncompressed_data_size());
+ lhs.set_compressed_data_size(lhs.compressed_data_size() + rhs.compressed_data_size());
+ lhs.set_chunk_count(lhs.chunk_count() + rhs.chunk_count());
+ lhs.set_row_count(lhs.row_count() + rhs.row_count());
+ lhs.set_regular_disk_space(lhs.regular_disk_space() + rhs.regular_disk_space());
+ lhs.set_erasure_disk_space(lhs.erasure_disk_space() + rhs.erasure_disk_space());
+
+ if (HasInvalidDataWeight(lhs) || HasInvalidDataWeight(rhs)) {
+ lhs.set_data_weight(-1);
+ } else {
+ lhs.set_data_weight(lhs.data_weight() + rhs.data_weight());
+ }
+
+ if (HasInvalidUnmergedRowCount(lhs) || HasInvalidUnmergedRowCount(rhs)) {
+ lhs.set_unmerged_row_count(-1);
+ } else {
+ lhs.set_unmerged_row_count(lhs.unmerged_row_count() + rhs.unmerged_row_count());
+ }
+
+ if (HasInvalidUnmergedDataWeight(lhs) || HasInvalidUnmergedDataWeight(rhs)) {
+ lhs.set_unmerged_data_weight(-1);
+ } else {
+ lhs.set_unmerged_data_weight(lhs.unmerged_data_weight() + rhs.unmerged_data_weight());
+ }
+
+ return lhs;
+}
+
+TDataStatistics operator + (const TDataStatistics& lhs, const TDataStatistics& rhs)
+{
+ auto result = lhs;
+ result += rhs;
+ return result;
+}
+
+bool operator == (const TDataStatistics& lhs, const TDataStatistics& rhs)
+{
+ return
+ lhs.uncompressed_data_size() == rhs.uncompressed_data_size() &&
+ lhs.compressed_data_size() == rhs.compressed_data_size() &&
+ lhs.row_count() == rhs.row_count() &&
+ lhs.chunk_count() == rhs.chunk_count() &&
+ lhs.regular_disk_space() == rhs.regular_disk_space() &&
+ lhs.erasure_disk_space() == rhs.erasure_disk_space() &&
+ (HasInvalidDataWeight(lhs) || HasInvalidDataWeight(rhs) || lhs.data_weight() == rhs.data_weight()) &&
+ (
+ HasInvalidUnmergedRowCount(lhs) ||
+ HasInvalidUnmergedRowCount(rhs) ||
+ lhs.unmerged_row_count() == rhs.unmerged_row_count()) &&
+ (
+ HasInvalidUnmergedDataWeight(lhs) ||
+ HasInvalidUnmergedDataWeight(rhs) ||
+ lhs.unmerged_data_weight() == rhs.unmerged_data_weight());
+}
+
+bool operator != (const TDataStatistics& lhs, const TDataStatistics& rhs)
+{
+ return !(lhs == rhs);
+}
+
+void Serialize(const TDataStatistics& statistics, NYson::IYsonConsumer* consumer)
+{
+ // TODO(max42): replace all Item with OptionalItem in order to expose only meaningful
+ // fields in each particular context. This would require fixing some tests or using
+ // manually constructed data statistics with some fields being explicitly set to zero
+ // as a neutral element.
+ BuildYsonFluently(consumer).BeginMap()
+ .Item("chunk_count").Value(statistics.chunk_count())
+ .Item("row_count").Value(statistics.row_count())
+ .Item("uncompressed_data_size").Value(statistics.uncompressed_data_size())
+ .Item("compressed_data_size").Value(statistics.compressed_data_size())
+ .Item("data_weight").Value(statistics.data_weight())
+ .Item("regular_disk_space").Value(statistics.regular_disk_space())
+ .Item("erasure_disk_space").Value(statistics.erasure_disk_space())
+ .Item("unmerged_row_count").Value(statistics.unmerged_row_count())
+ .Item("unmerged_data_weight").Value(statistics.unmerged_data_weight())
+ .EndMap();
+}
+
+void SetDataStatisticsField(TDataStatistics& statistics, TStringBuf key, i64 value)
+{
+ if (key == "chunk_count") {
+ statistics.set_chunk_count(value);
+ } else if (key == "row_count") {
+ statistics.set_row_count(value);
+ } else if (key == "uncompressed_data_size") {
+ statistics.set_uncompressed_data_size(value);
+ } else if (key == "compressed_data_size") {
+ statistics.set_compressed_data_size(value);
+ } else if (key == "data_weight") {
+ statistics.set_data_weight(value);
+ } else if (key == "regular_disk_space") {
+ statistics.set_regular_disk_space(value);
+ } else if (key == "erasure_disk_space") {
+ statistics.set_erasure_disk_space(value);
+ } else if (key == "unmerged_row_count") {
+ statistics.set_unmerged_row_count(value);
+ } else if (key == "unmerged_data_weight") {
+ statistics.set_unmerged_data_weight(value);
+ } // Else we have a strange situation on our hands but we intentionally ignore it.
+}
+
+void FormatValue(TStringBuilderBase* builder, const TDataStatistics& statistics, TStringBuf /*spec*/)
+{
+ builder->AppendFormat(
+ "{UncompressedDataSize: %v, CompressedDataSize: %v, DataWeight: %v, RowCount: %v, "
+ "ChunkCount: %v, RegularDiskSpace: %v, ErasureDiskSpace: %v, "
+ "UnmergedRowCount: %v, UnmergedDataWeight: %v}",
+ statistics.uncompressed_data_size(),
+ statistics.compressed_data_size(),
+ statistics.data_weight(),
+ statistics.row_count(),
+ statistics.chunk_count(),
+ statistics.regular_disk_space(),
+ statistics.erasure_disk_space(),
+ statistics.unmerged_row_count(),
+ statistics.unmerged_data_weight());
+}
+
+void FormatValue(TStringBuilderBase* builder, const TDataStatistics* statistics, TStringBuf spec)
+{
+ if (statistics) {
+ FormatValue(builder, *statistics, spec);
+ } else {
+ FormatValue(builder, std::nullopt, spec);
+ }
+}
+
+TString ToString(const TDataStatistics& statistics)
+{
+ return ToStringViaBuilder(statistics);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCodecStatistics& TCodecStatistics::Append(const TCodecDuration& codecTime)
+{
+ return Append(std::make_pair(codecTime.Codec, codecTime.CpuDuration));
+}
+
+TCodecStatistics& TCodecStatistics::Append(const std::pair<ECodec, TDuration>& codecTime)
+{
+ CodecToDuration_[codecTime.first] += codecTime.second;
+ TotalDuration_ += codecTime.second;
+ return *this;
+}
+
+TCodecStatistics& TCodecStatistics::operator+=(const TCodecStatistics& other)
+{
+ for (const auto& pair : other.CodecToDuration_) {
+ Append(pair);
+ }
+ return *this;
+}
+
+TDuration TCodecStatistics::GetTotalDuration() const
+{
+ return TotalDuration_;
+}
+
+void FormatValue(TStringBuilderBase* builder, const TCodecStatistics& statistics, TStringBuf /* spec */)
+{
+ FormatKeyValueRange(builder, statistics.CodecToDuration(), TDefaultFormatter());
+}
+
+TString ToString(const TCodecStatistics& statistics)
+{
+ return ToStringViaBuilder(statistics);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/data_statistics.h b/yt/yt/client/chunk_client/data_statistics.h
new file mode 100644
index 0000000000..4fc0b210c8
--- /dev/null
+++ b/yt/yt/client/chunk_client/data_statistics.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/data_statistics.pb.h>
+
+#include <yt/yt/core/compression/public.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <yt/yt/core/misc/property.h>
+
+#include <library/cpp/yt/small_containers/compact_flat_map.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+bool HasInvalidDataWeight(const TDataStatistics& statistics);
+
+TDataStatistics& operator += (TDataStatistics& lhs, const TDataStatistics& rhs);
+TDataStatistics operator + (const TDataStatistics& lhs, const TDataStatistics& rhs);
+
+bool operator == (const TDataStatistics& lhs, const TDataStatistics& rhs);
+bool operator != (const TDataStatistics& lhs, const TDataStatistics& rhs);
+
+void Serialize(const TDataStatistics& statistics, NYson::IYsonConsumer* consumer);
+
+void SetDataStatisticsField(TDataStatistics& statistics, TStringBuf key, i64 value);
+
+void FormatValue(TStringBuilderBase* builder, const TDataStatistics& statistics, TStringBuf spec);
+void FormatValue(TStringBuilderBase* builder, const TDataStatistics* statistics, TStringBuf spec);
+TString ToString(const TDataStatistics& statistics);
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TCodecDuration
+{
+ NCompression::ECodec Codec;
+ TDuration CpuDuration;
+};
+
+class TCodecStatistics
+{
+public:
+ using TCodecToDuration = TCompactFlatMap<
+ NCompression::ECodec,
+ TDuration,
+ 1>;
+ DEFINE_BYREF_RO_PROPERTY(TCodecToDuration, CodecToDuration);
+
+public:
+ TCodecStatistics& Append(const TCodecDuration& codecTime);
+
+ TCodecStatistics& operator+=(const TCodecStatistics& other);
+
+ TDuration GetTotalDuration() const;
+
+private:
+ TDuration TotalDuration_;
+
+ TCodecStatistics& Append(const std::pair<NCompression::ECodec, TDuration>& codecTime);
+};
+
+void FormatValue(TStringBuilderBase* builder, const TCodecStatistics& statistics, TStringBuf spec);
+TString ToString(const TCodecStatistics& statistics);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
+
diff --git a/yt/yt/client/chunk_client/helpers.cpp b/yt/yt/client/chunk_client/helpers.cpp
new file mode 100644
index 0000000000..690810950f
--- /dev/null
+++ b/yt/yt/client/chunk_client/helpers.cpp
@@ -0,0 +1,63 @@
+#include "helpers.h"
+
+#include "read_limit.h"
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.pb.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+namespace NYT::NChunkClient {
+
+using NYT::FromProto;
+using NYT::ToProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void PrintTo(const TReadRange& readRange, std::ostream* os)
+{
+ *os << ToString(readRange);
+}
+
+NObjectClient::TObjectId GetObjectIdFromChunkSpec(const NProto::TChunkSpec& chunkSpec)
+{
+ return FromProto<NObjectClient::TObjectId>(chunkSpec.chunk_id());
+}
+
+NObjectClient::TCellId GetCellIdFromChunkSpec(const NProto::TChunkSpec& chunkSpec)
+{
+ return FromProto<NObjectClient::TCellId>(chunkSpec.cell_id());
+}
+
+NObjectClient::TObjectId GetTabletIdFromChunkSpec(const NProto::TChunkSpec& chunkSpec)
+{
+ return FromProto<NTabletClient::TTabletId>(chunkSpec.tablet_id());
+}
+
+TChunkReplicaWithMediumList GetReplicasFromChunkSpec(const NProto::TChunkSpec& chunkSpec)
+{
+ if (chunkSpec.replicas_size() == 0) {
+ auto legacyReplicas = FromProto<TChunkReplicaList>(chunkSpec.legacy_replicas());
+ TChunkReplicaWithMediumList replicas;
+ replicas.reserve(legacyReplicas.size());
+ for (auto legacyReplica : legacyReplicas) {
+ replicas.emplace_back(legacyReplica);
+ }
+ return replicas;
+ } else {
+ return FromProto<TChunkReplicaWithMediumList>(chunkSpec.replicas());
+ }
+}
+
+void SetTabletId(NProto::TChunkSpec* chunkSpec, NTabletClient::TTabletId tabletId)
+{
+ ToProto(chunkSpec->mutable_tablet_id(), tabletId);
+}
+
+void SetObjectId(NProto::TChunkSpec* chunkSpec, NObjectClient::TObjectId objectId)
+{
+ ToProto(chunkSpec->mutable_chunk_id(), objectId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/helpers.h b/yt/yt/client/chunk_client/helpers.h
new file mode 100644
index 0000000000..5370c04120
--- /dev/null
+++ b/yt/yt/client/chunk_client/helpers.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "chunk_replica.h"
+
+#include <yt/yt/client/tablet_client/public.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void PrintTo(const TReadRange& readRange, std::ostream* os);
+
+NObjectClient::TObjectId GetObjectIdFromChunkSpec(const NProto::TChunkSpec& chunkSpec);
+NObjectClient::TCellId GetCellIdFromChunkSpec(const NProto::TChunkSpec& chunkSpec);
+NTabletClient::TTabletId GetTabletIdFromChunkSpec(const NProto::TChunkSpec& chunkSpec);
+TChunkReplicaWithMediumList GetReplicasFromChunkSpec(const NProto::TChunkSpec& chunkSpec);
+
+void SetTabletId(NProto::TChunkSpec* chunkSpec, NTabletClient::TTabletId tabletId);
+void SetObjectId(NProto::TChunkSpec* chunkSpec, NObjectClient::TObjectId objectId);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/public.cpp b/yt/yt/client/chunk_client/public.cpp
new file mode 100644
index 0000000000..f897ffa00d
--- /dev/null
+++ b/yt/yt/client/chunk_client/public.cpp
@@ -0,0 +1,23 @@
+#include "public.h"
+
+#include <yt/yt/client/misc/workload.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TChunkId NullChunkId = NObjectClient::NullObjectId;
+const TChunkViewId NullChunkViewId = NObjectClient::NullObjectId;
+const TChunkListId NullChunkListId = NObjectClient::NullObjectId;
+const TChunkTreeId NullChunkTreeId = NObjectClient::NullObjectId;
+
+const TString DefaultStoreAccountName("sys");
+const TString DefaultStoreMediumName("default");
+const TString DefaultCacheMediumName("cache");
+const TString DefaultSlotsMediumName("default");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/public.h b/yt/yt/client/chunk_client/public.h
new file mode 100644
index 0000000000..6cf877026e
--- /dev/null
+++ b/yt/yt/client/chunk_client/public.h
@@ -0,0 +1,203 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+#include <library/cpp/yt/small_containers/compact_flat_map.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+class TChunkInfo;
+class TChunkSpec;
+class TChunkMeta;
+class TBlocksExt;
+class TMiscExt;
+
+class TConfirmChunkReplicaInfo;
+
+class TDataStatistics;
+
+class TLegacyReadRange;
+
+class TMediumDirectory;
+
+} // namespace NProto
+
+YT_DEFINE_ERROR_ENUM(
+ ((AllTargetNodesFailed) (700))
+ ((SendBlocksFailed) (701))
+ ((NoSuchSession) (702))
+ ((SessionAlreadyExists) (703))
+ ((ChunkAlreadyExists) (704))
+ ((WindowError) (705))
+ ((BlockContentMismatch) (706))
+ ((NoSuchBlock) (707))
+ ((NoSuchChunk) (708))
+ ((NoLocationAvailable) (710))
+ ((IOError) (711))
+ ((MasterCommunicationFailed) (712))
+ ((NoSuchChunkTree) (713))
+ ((NoSuchChunkList) (717))
+ ((MasterNotConnected) (714))
+ ((ChunkUnavailable) (716))
+ ((WriteThrottlingActive) (718))
+ ((NoSuchMedium) (719))
+ ((OptimisticLockFailure) (720))
+ ((InvalidBlockChecksum) (721))
+ ((MalformedReadRequest) (722))
+ ((MissingExtension) (724))
+ ((ReaderThrottlingFailed) (725))
+ ((ReaderTimeout) (726))
+ ((NoSuchChunkView) (727))
+ ((IncorrectChunkFileChecksum) (728))
+ ((BrokenChunkFileMeta) (729))
+ ((IncorrectLayerFileSize) (730))
+ ((NoSpaceLeftOnDevice) (731))
+ ((ConcurrentChunkUpdate) (732))
+ ((InvalidInputChunk) (733))
+ ((UnsupportedChunkFeature) (734))
+ ((IncompatibleChunkMetas) (735))
+ ((AutoRepairFailed) (736))
+ ((ChunkBlockFetchFailed) (737))
+ ((ChunkMetaFetchFailed) (738))
+ ((RowsLookupFailed) (739))
+ ((BlockChecksumMismatch) (740))
+ ((NoChunkSeedsKnown) (741))
+ ((NoChunkSeedsGiven) (742))
+ ((ChunkIsLost) (743))
+ ((ChunkReadSessionSlow) (744))
+ ((NodeProbeFailed) (745))
+ ((UnrecoverableRepairError) (747))
+ ((MissingJournalChunkRecord) (748))
+ ((LocationDiskFailed) (749))
+ ((LocationCrashed) (750))
+ ((LocationDiskWaitingReplacement) (751))
+ ((ChunkMetaCacheFetchFailed) (752))
+);
+
+using TChunkId = NObjectClient::TObjectId;
+extern const TChunkId NullChunkId;
+
+using TChunkViewId = NObjectClient::TObjectId;
+extern const TChunkViewId NullChunkViewId;
+
+using TChunkListId = NObjectClient::TObjectId;
+extern const TChunkListId NullChunkListId;
+
+using TChunkTreeId = NObjectClient::TObjectId;
+extern const TChunkTreeId NullChunkTreeId;
+
+using TChunkLocationUuid = TGuid;
+constexpr auto EmptyChunkLocationUuid = TChunkLocationUuid(0, 0);
+constexpr auto InvalidChunkLocationUuid = TChunkLocationUuid(-1, -1);
+
+constexpr int MinReplicationFactor = 1;
+constexpr int MaxReplicationFactor = 20;
+constexpr int DefaultReplicationFactor = 3;
+
+constexpr int MaxMediumCount = 120; // leave some room for sentinels
+
+template <typename T>
+using TMediumMap = THashMap<int, T>;
+template <typename T>
+using TCompactMediumMap = TCompactFlatMap<int, T, 4>;
+
+//! Used as an expected upper bound in TCompactVector.
+/*
+ * Maximum regular number of replicas is 16 (for LRC codec).
+ * Additional +8 enables some flexibility during balancing.
+ */
+constexpr int TypicalReplicaCount = 24;
+constexpr int GenericChunkReplicaIndex = 16; // no specific replica; the default one for non-erasure chunks
+
+//! Valid indexes are in range |[0, ChunkReplicaIndexBound)|.
+constexpr int ChunkReplicaIndexBound = 32;
+
+constexpr int GenericMediumIndex = 126; // internal sentinel meaning "no specific medium"
+constexpr int AllMediaIndex = 127; // passed to various APIs to indicate that any medium is OK
+constexpr int DefaultStoreMediumIndex = 0;
+constexpr int DefaultSlotsMediumIndex = 0;
+
+//! Valid indexes (including sentinels) are in range |[0, MediumIndexBound)|.
+constexpr int MediumIndexBound = AllMediaIndex + 1;
+
+class TChunkReplicaWithMedium;
+using TChunkReplicaWithMediumList = TCompactVector<TChunkReplicaWithMedium, TypicalReplicaCount>;
+
+class TChunkReplicaWithLocation;
+using TChunkReplicaWithLocationList = TCompactVector<TChunkReplicaWithLocation, TypicalReplicaCount>;
+
+class TChunkReplica;
+using TChunkReplicaList = TCompactVector<TChunkReplica, TypicalReplicaCount>;
+
+extern const TString DefaultStoreAccountName;
+extern const TString DefaultStoreMediumName;
+extern const TString DefaultCacheMediumName;
+extern const TString DefaultSlotsMediumName;
+
+DECLARE_REFCOUNTED_STRUCT(IReaderBase)
+
+DECLARE_REFCOUNTED_CLASS(TFetchChunkSpecConfig)
+DECLARE_REFCOUNTED_CLASS(TFetcherConfig)
+DECLARE_REFCOUNTED_CLASS(TChunkSliceFetcherConfig)
+DECLARE_REFCOUNTED_CLASS(TEncodingWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TErasureReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TMultiChunkReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TBlockFetcherConfig)
+DECLARE_REFCOUNTED_CLASS(TReplicationReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TReplicationWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TErasureWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TMultiChunkWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TEncodingWriterOptions)
+DECLARE_REFCOUNTED_CLASS(TBlockReordererConfig)
+DECLARE_REFCOUNTED_CLASS(TChunkFragmentReaderConfig)
+
+struct TCodecDuration;
+class TCodecStatistics;
+
+class TLegacyReadLimit;
+class TLegacyReadRange;
+class TReadLimit;
+class TReadRange;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EChunkAvailabilityPolicy,
+ ((DataPartsAvailable) (0))
+ ((AllPartsAvailable) (1))
+ ((Repairable) (2))
+);
+
+// Keep in sync with NChunkServer::ETableChunkFormat.
+DEFINE_ENUM(EChunkFormat,
+ // Sentinels.
+ ((Unknown) (-1))
+
+ // File chunks.
+ ((FileDefault) (1))
+
+ // Table chunks.
+ ((TableUnversionedSchemaful) (3))
+ ((TableUnversionedSchemalessHorizontal) (4))
+ ((TableUnversionedColumnar) (6))
+ ((TableVersionedSimple) (2))
+ ((TableVersionedColumnar) (5))
+ ((TableVersionedIndexed) (8))
+ ((TableVersionedSlim) (9))
+
+ // Journal chunks.
+ ((JournalDefault) (0))
+
+ // Hunk chunks.
+ ((HunkDefault) (7))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/read_limit.cpp b/yt/yt/client/chunk_client/read_limit.cpp
new file mode 100644
index 0000000000..2195eda42b
--- /dev/null
+++ b/yt/yt/client/chunk_client/read_limit.cpp
@@ -0,0 +1,1037 @@
+#include "read_limit.h"
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <yt/yt/core/yson/protobuf_interop.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/node.h>
+
+namespace NYT::NChunkClient {
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NTableClient;
+
+using NYT::FromProto;
+using NYT::ToProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLegacyReadLimit::TLegacyReadLimit(const NProto::TReadLimit& protoLimit)
+{
+ InitCopy(protoLimit);
+}
+
+TLegacyReadLimit::TLegacyReadLimit(NProto::TReadLimit&& protoLimit)
+{
+ InitMove(std::move(protoLimit));
+}
+
+TLegacyReadLimit::TLegacyReadLimit(const std::unique_ptr<NProto::TReadLimit>& protoLimit)
+{
+ if (protoLimit) {
+ InitCopy(*protoLimit);
+ }
+}
+
+TLegacyReadLimit::TLegacyReadLimit(const TLegacyOwningKey& key)
+{
+ SetLegacyKey(key);
+}
+
+TLegacyReadLimit::TLegacyReadLimit(TLegacyOwningKey&& key)
+{
+ SetLegacyKey(std::move(key));
+}
+
+TLegacyReadLimit& TLegacyReadLimit::operator= (const NProto::TReadLimit& protoLimit)
+{
+ InitCopy(protoLimit);
+ return *this;
+}
+
+TLegacyReadLimit& TLegacyReadLimit::operator= (NProto::TReadLimit&& protoLimit)
+{
+ InitMove(std::move(protoLimit));
+ return *this;
+}
+
+TLegacyReadLimit TLegacyReadLimit::GetSuccessor() const
+{
+ TLegacyReadLimit result;
+ if (HasLegacyKey()) {
+ auto key = GetLegacyKey();
+ result.SetLegacyKey(GetKeyPrefixSuccessor(key, key.GetCount()));
+ }
+ if (HasRowIndex()) {
+ result.SetRowIndex(GetRowIndex() + 1);
+ }
+ if (HasChunkIndex()) {
+ result.SetChunkIndex(GetChunkIndex() + 1);
+ }
+ if (HasTabletIndex()) {
+ // We use tabletIndex in ordered dynamic tables, where indexing is over pairs (tabletIndex, rowIndex).
+ result.SetTabletIndex(GetTabletIndex());
+ }
+ return result;
+}
+
+const NProto::TReadLimit& TLegacyReadLimit::AsProto() const
+{
+ return ReadLimit_;
+}
+
+const TLegacyOwningKey& TLegacyReadLimit::GetLegacyKey() const
+{
+ YT_ASSERT(HasLegacyKey());
+ return Key_;
+}
+
+bool TLegacyReadLimit::HasLegacyKey() const
+{
+ return ReadLimit_.has_legacy_key();
+}
+
+TLegacyReadLimit& TLegacyReadLimit::SetLegacyKey(const TLegacyOwningKey& key)
+{
+ Key_ = key;
+ ToProto(ReadLimit_.mutable_legacy_key(), Key_);
+ return *this;
+}
+
+TLegacyReadLimit& TLegacyReadLimit::SetLegacyKey(TLegacyOwningKey&& key)
+{
+ swap(Key_, key);
+ ToProto(ReadLimit_.mutable_legacy_key(), Key_);
+ return *this;
+}
+
+i64 TLegacyReadLimit::GetRowIndex() const
+{
+ YT_ASSERT(HasRowIndex());
+ return ReadLimit_.row_index();
+}
+
+bool TLegacyReadLimit::HasRowIndex() const
+{
+ return ReadLimit_.has_row_index();
+}
+
+TLegacyReadLimit& TLegacyReadLimit::SetRowIndex(i64 rowIndex)
+{
+ ReadLimit_.set_row_index(rowIndex);
+ return *this;
+}
+
+i64 TLegacyReadLimit::GetOffset() const
+{
+ YT_ASSERT(HasOffset());
+ return ReadLimit_.offset();
+}
+
+bool TLegacyReadLimit::HasOffset() const
+{
+ return ReadLimit_.has_offset();
+}
+
+TLegacyReadLimit& TLegacyReadLimit::SetOffset(i64 offset)
+{
+ ReadLimit_.set_offset(offset);
+ return *this;
+}
+
+i64 TLegacyReadLimit::GetChunkIndex() const
+{
+ YT_ASSERT(HasChunkIndex());
+ return ReadLimit_.chunk_index();
+}
+
+bool TLegacyReadLimit::HasChunkIndex() const
+{
+ return ReadLimit_.has_chunk_index();
+}
+
+TLegacyReadLimit& TLegacyReadLimit::SetChunkIndex(i64 chunkIndex)
+{
+ ReadLimit_.set_chunk_index(chunkIndex);
+ return *this;
+}
+
+i32 TLegacyReadLimit::GetTabletIndex() const
+{
+ YT_ASSERT(HasTabletIndex());
+ return ReadLimit_.tablet_index();
+}
+
+bool TLegacyReadLimit::HasTabletIndex() const
+{
+ return ReadLimit_.has_tablet_index();
+}
+
+TLegacyReadLimit& TLegacyReadLimit::SetTabletIndex(i32 tabletIndex)
+{
+ ReadLimit_.set_tablet_index(tabletIndex);
+ return *this;
+}
+
+bool TLegacyReadLimit::IsTrivial() const
+{
+ return NChunkClient::IsTrivial(ReadLimit_);
+}
+
+void TLegacyReadLimit::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+ Persist(context, ReadLimit_);
+ Persist(context, Key_);
+}
+
+void TLegacyReadLimit::MergeLowerLegacyKey(const TLegacyOwningKey& key)
+{
+ if (!HasLegacyKey() || GetLegacyKey() < key) {
+ SetLegacyKey(key);
+ }
+}
+
+void TLegacyReadLimit::MergeUpperLegacyKey(const TLegacyOwningKey& key)
+{
+ if (!HasLegacyKey() || GetLegacyKey() > key) {
+ SetLegacyKey(key);
+ }
+}
+
+void TLegacyReadLimit::MergeLowerRowIndex(i64 rowIndex)
+{
+ if (!HasRowIndex() || GetRowIndex() < rowIndex) {
+ SetRowIndex(rowIndex);
+ }
+}
+
+void TLegacyReadLimit::MergeUpperRowIndex(i64 rowIndex)
+{
+ if (!HasRowIndex() || GetRowIndex() > rowIndex) {
+ SetRowIndex(rowIndex);
+ }
+}
+
+void TLegacyReadLimit::InitKey()
+{
+ if (ReadLimit_.has_legacy_key()) {
+ FromProto(&Key_, ReadLimit_.legacy_key());
+ }
+}
+
+void TLegacyReadLimit::InitCopy(const NProto::TReadLimit& readLimit)
+{
+ ReadLimit_.CopyFrom(readLimit);
+ InitKey();
+}
+
+void TLegacyReadLimit::InitMove(NProto::TReadLimit&& readLimit)
+{
+ ReadLimit_.Swap(&readLimit);
+ InitKey();
+}
+
+size_t TLegacyReadLimit::SpaceUsed() const
+{
+ return
+ sizeof(*this) +
+ ReadLimit_.SpaceUsed() - sizeof(ReadLimit_) +
+ Key_.GetSpaceUsed() - sizeof(Key_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TLegacyReadLimit& limit)
+{
+ using ::ToString;
+
+ TStringBuilder builder;
+ builder.AppendChar('{');
+
+ bool firstToken = true;
+ auto append = [&] (const char* label, TStringBuf value) {
+ if (!firstToken) {
+ builder.AppendString(", ");
+ }
+ firstToken = false;
+ builder.AppendString(label);
+ builder.AppendString(": ");
+ builder.AppendString(value);
+ };
+
+ if (limit.HasLegacyKey()) {
+ append("Key", ToString(limit.GetLegacyKey()));
+ }
+
+ if (limit.HasRowIndex()) {
+ append("RowIndex", ToString(limit.GetRowIndex()));
+ }
+
+ if (limit.HasOffset()) {
+ append("Offset", ToString(limit.GetOffset()));
+ }
+
+ if (limit.HasChunkIndex()) {
+ append("ChunkIndex", ToString(limit.GetChunkIndex()));
+ }
+
+ if (limit.HasTabletIndex()) {
+ append("TabletIndex", ToString(limit.GetTabletIndex()));
+ }
+
+ builder.AppendChar('}');
+ return builder.Flush();
+}
+
+bool IsTrivial(const TLegacyReadLimit& limit)
+{
+ return limit.IsTrivial();
+}
+
+bool IsTrivial(const NProto::TReadLimit& limit)
+{
+ return
+ !limit.has_row_index() &&
+ !limit.has_legacy_key() &&
+ !limit.has_offset() &&
+ !limit.has_chunk_index() &&
+ !limit.has_tablet_index();
+}
+
+void ToProto(NProto::TReadLimit* protoReadLimit, const TLegacyReadLimit& readLimit)
+{
+ protoReadLimit->CopyFrom(readLimit.AsProto());
+}
+
+void FromProto(TLegacyReadLimit* readLimit, const NProto::TReadLimit& protoReadLimit)
+{
+ *readLimit = protoReadLimit;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TLegacyReadLimit& readLimit, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .DoIf(readLimit.HasLegacyKey(), [&] (TFluentMap fluent) {
+ fluent.Item("key").Value(readLimit.GetLegacyKey());
+ })
+ .DoIf(readLimit.HasRowIndex(), [&] (TFluentMap fluent) {
+ fluent.Item("row_index").Value(readLimit.GetRowIndex());
+ })
+ .DoIf(readLimit.HasOffset(), [&] (TFluentMap fluent) {
+ fluent.Item("offset").Value(readLimit.GetOffset());
+ })
+ .DoIf(readLimit.HasChunkIndex(), [&] (TFluentMap fluent) {
+ fluent.Item("chunk_index").Value(readLimit.GetChunkIndex());
+ })
+ .DoIf(readLimit.HasTabletIndex(), [&] (TFluentMap fluent) {
+ fluent.Item("tablet_index").Value(readLimit.GetTabletIndex());
+ })
+ .EndMap();
+}
+
+namespace {
+
+template <class T>
+std::optional<T> FindReadLimitComponent(const IAttributeDictionaryPtr& attributes, const TString& key)
+{
+ try {
+ return attributes->Find<T>(key);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error parsing %Qv component of a read limit",
+ key)
+ << ex;
+ }
+}
+
+} // namespace
+
+void Deserialize(TLegacyReadLimit& readLimit, INodePtr node)
+{
+ if (node->GetType() != NYTree::ENodeType::Map) {
+ THROW_ERROR_EXCEPTION("Error parsing read limit: expected %Qlv, actual %Qlv",
+ NYTree::ENodeType::Map,
+ node->GetType());
+ }
+
+ readLimit = TLegacyReadLimit();
+ auto attributes = ConvertToAttributes(node);
+
+ auto optionalKey = FindReadLimitComponent<TLegacyOwningKey>(attributes, "key");
+ if (optionalKey) {
+ readLimit.SetLegacyKey(*optionalKey);
+ }
+
+ auto optionalRowIndex = FindReadLimitComponent<i64>(attributes, "row_index");
+ if (optionalRowIndex) {
+ readLimit.SetRowIndex(*optionalRowIndex);
+ }
+
+ auto optionalOffset = FindReadLimitComponent<i64>(attributes, "offset");
+ if (optionalOffset) {
+ readLimit.SetOffset(*optionalOffset);
+ }
+
+ auto optionalChunkIndex = FindReadLimitComponent<i64>(attributes, "chunk_index");
+ if (optionalChunkIndex) {
+ readLimit.SetChunkIndex(*optionalChunkIndex);
+ }
+
+ auto optionalTabletIndex = FindReadLimitComponent<i32>(attributes, "tablet_index");
+ if (optionalTabletIndex) {
+ readLimit.SetTabletIndex(*optionalTabletIndex);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLegacyReadRange::TLegacyReadRange(const TLegacyReadLimit& exact)
+ : LowerLimit_(exact)
+ , UpperLimit_(exact.GetSuccessor())
+{ }
+
+TLegacyReadRange::TLegacyReadRange(const TLegacyReadLimit& lowerLimit, const TLegacyReadLimit& upperLimit)
+ : LowerLimit_(lowerLimit)
+ , UpperLimit_(upperLimit)
+{ }
+
+TLegacyReadRange::TLegacyReadRange(const NProto::TReadRange& range)
+{
+ InitCopy(range);
+}
+
+TLegacyReadRange::TLegacyReadRange(NProto::TReadRange&& range)
+{
+ InitMove(std::move(range));
+}
+
+TLegacyReadRange& TLegacyReadRange::operator= (const NProto::TReadRange& range)
+{
+ InitCopy(range);
+ return *this;
+}
+
+TLegacyReadRange& TLegacyReadRange::operator= (NProto::TReadRange&& range)
+{
+ InitMove(std::move(range));
+ return *this;
+}
+
+void TLegacyReadRange::InitCopy(const NProto::TReadRange& range)
+{
+ LowerLimit_ = range.has_lower_limit() ? TLegacyReadLimit(range.lower_limit()) : TLegacyReadLimit();
+ UpperLimit_ = range.has_upper_limit() ? TLegacyReadLimit(range.upper_limit()) : TLegacyReadLimit();
+}
+
+void TLegacyReadRange::InitMove(NProto::TReadRange&& range)
+{
+ LowerLimit_ = range.has_lower_limit() ? TLegacyReadLimit(range.lower_limit()) : TLegacyReadLimit();
+ UpperLimit_ = range.has_upper_limit() ? TLegacyReadLimit(range.upper_limit()) : TLegacyReadLimit();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TLegacyReadRange& range)
+{
+ return Format("[<%v> : <%v>]", range.LowerLimit(), range.UpperLimit());
+}
+
+void ToProto(NProto::TReadRange* protoReadRange, const TLegacyReadRange& readRange)
+{
+ if (!readRange.LowerLimit().IsTrivial()) {
+ ToProto(protoReadRange->mutable_lower_limit(), readRange.LowerLimit());
+ }
+ if (!readRange.UpperLimit().IsTrivial()) {
+ ToProto(protoReadRange->mutable_upper_limit(), readRange.UpperLimit());
+ }
+}
+
+void FromProto(TLegacyReadRange* readRange, const NProto::TReadRange& protoReadRange)
+{
+ *readRange = TLegacyReadRange(protoReadRange);
+}
+
+void Serialize(const TLegacyReadRange& readRange, NYson::IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .DoIf(!readRange.LowerLimit().IsTrivial(), [&] (TFluentMap fluent) {
+ fluent.Item("lower_limit").Value(readRange.LowerLimit());
+ })
+ .DoIf(!readRange.UpperLimit().IsTrivial(), [&] (TFluentMap fluent) {
+ fluent.Item("upper_limit").Value(readRange.UpperLimit());
+ })
+ .EndMap();
+
+}
+
+namespace {
+
+template <class T>
+std::optional<T> FindReadRangeComponent(const IAttributeDictionaryPtr& attributes, const TString& key)
+{
+ try {
+ return attributes->Find<T>(key);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error parsing %Qv component of a read range",
+ key)
+ << ex;
+ }
+}
+
+} // namespace
+
+void Deserialize(TLegacyReadRange& readRange, NYTree::INodePtr node)
+{
+ if (node->GetType() != NYTree::ENodeType::Map) {
+ THROW_ERROR_EXCEPTION("Error parsing read range: expected %Qlv, actual %Qlv",
+ NYTree::ENodeType::Map,
+ node->GetType());
+ }
+
+ readRange = TLegacyReadRange();
+ auto attributes = ConvertToAttributes(node);
+ auto optionalExact = FindReadRangeComponent<TLegacyReadLimit>(attributes, "exact");
+ auto optionalLowerLimit = FindReadRangeComponent<TLegacyReadLimit>(attributes, "lower_limit");
+ auto optionalUpperLimit = FindReadRangeComponent<TLegacyReadLimit>(attributes, "upper_limit");
+
+ if (optionalExact) {
+ if (optionalLowerLimit || optionalUpperLimit) {
+ THROW_ERROR_EXCEPTION("\"lower_limit\" and \"upper_limit\" attributes cannot be specified "
+ "together with \"exact\" attribute");
+ }
+ readRange = TLegacyReadRange(*optionalExact);
+ }
+
+ if (optionalLowerLimit) {
+ readRange.LowerLimit() = *optionalLowerLimit;
+ }
+
+ if (optionalUpperLimit) {
+ readRange.UpperLimit() = *optionalUpperLimit;
+ }
+}
+
+void TLegacyReadRange::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+ Persist(context, LowerLimit_);
+ Persist(context, UpperLimit_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadLimit::TReadLimit(const TKeyBound& keyBound)
+ : KeyBound_(keyBound.ToOwning())
+{ }
+
+TReadLimit::TReadLimit(TOwningKeyBound keyBound)
+ : KeyBound_(std::move(keyBound))
+{ }
+
+TReadLimit::TReadLimit(
+ const NProto::TReadLimit& readLimit,
+ bool isUpper,
+ int keyLength)
+{
+ if (readLimit.has_key_bound_prefix()) {
+ NTableClient::FromProto(&KeyBound_.Prefix, readLimit.key_bound_prefix());
+ KeyBound_.IsUpper = isUpper;
+ KeyBound_.IsInclusive = readLimit.key_bound_is_inclusive();
+ } else if (readLimit.has_legacy_key()) {
+ YT_VERIFY(keyLength > 0);
+ TUnversionedOwningRow legacyKey;
+ FromProto(&legacyKey, readLimit.legacy_key());
+ KeyBound_ = KeyBoundFromLegacyRow(legacyKey, isUpper, keyLength);
+ }
+
+ if (readLimit.has_row_index()) {
+ RowIndex_ = readLimit.row_index();
+ }
+ if (readLimit.has_offset()) {
+ Offset_ = readLimit.offset();
+ }
+ if (readLimit.has_chunk_index()) {
+ ChunkIndex_ = readLimit.chunk_index();
+ }
+ if (readLimit.has_tablet_index()) {
+ TabletIndex_ = readLimit.tablet_index();
+ }
+}
+
+bool TReadLimit::IsTrivial() const
+{
+ return GetSelectorCount() == 0;
+}
+
+bool TReadLimit::HasIndependentSelectors() const
+{
+ if (GetSelectorCount() >= 3) {
+ return true;
+ }
+ if (GetSelectorCount() == 2) {
+ if (RowIndex_ && TabletIndex_) {
+ // Row index and tablet index are not independent.
+ return false;
+ } else {
+ return true;
+ }
+ }
+ // Having single or no selector at all means that there are no independent selectors.
+ return false;
+}
+
+int TReadLimit::GetSelectorCount() const
+{
+ int selectorCount = 0;
+
+ if (KeyBound_) {
+ ++selectorCount;
+ }
+
+ if (RowIndex_) {
+ ++selectorCount;
+ }
+
+ if (Offset_) {
+ ++selectorCount;
+ }
+
+ if (ChunkIndex_) {
+ ++selectorCount;
+ }
+
+ if (TabletIndex_) {
+ ++selectorCount;
+ }
+
+ return selectorCount;
+}
+
+TReadLimit TReadLimit::ToExactUpperCounterpart() const
+{
+ YT_VERIFY(!HasIndependentSelectors());
+
+ TReadLimit result = *this;
+
+ if (KeyBound_) {
+ YT_VERIFY(!KeyBound_.IsUpper);
+ // We are either a special >[] empty key bound, or a lower part of some
+ // key bound from an exact read limit, in which case we must be inclusive.
+ if (!KeyBound_.IsEmpty()) {
+ YT_VERIFY(KeyBound_.IsInclusive);
+ }
+ result.KeyBound_ = result.KeyBound_.Invert().ToggleInclusiveness();
+ }
+
+ // Ordered dyntables obey pretty tricky logic. All rows are ordered by
+ // (tablet_index, row_index) tuple lexicographically. Exact selector is
+ // transformed to lower+upper limits like following:
+ // - {tablet_index = 42} -> {tablet_index = 42}..{tablet_index = 43}
+ // - {tablet_index = 42; row_index = 123} ->
+ // {tablet_index = 42; row_index = 123}..{tablet_index = 42; row_index = 124}
+ // Thus, both for static and ordered dynamic tables we should increment (only)
+ // row index if it is present. Addititonally, for ordered dynamic tables,
+ // if row index is missing, we must increment tablet index if it is present.
+
+ if (RowIndex_) {
+ ++*result.RowIndex_;
+ } else if (TabletIndex_) {
+ ++*result.TabletIndex_;
+ }
+
+ if (Offset_) {
+ ++*result.Offset_;
+ }
+
+ if (ChunkIndex_) {
+ ++*result.ChunkIndex_;
+ }
+
+ return result;
+}
+
+TReadLimit TReadLimit::Invert() const
+{
+ YT_VERIFY(!HasIndependentSelectors());
+
+ auto result = *this;
+
+ if (KeyBound_) {
+ result.KeyBound() = result.KeyBound().Invert();
+ }
+
+ return result;
+}
+
+bool TReadLimit::operator == (const TReadLimit& other) const
+{
+ return
+ KeyBound_ == other.KeyBound_ &&
+ RowIndex_ == other.RowIndex_ &&
+ Offset_ == other.Offset_ &&
+ ChunkIndex_ == other.ChunkIndex_ &&
+ TabletIndex_ == other.TabletIndex_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TReadLimit& readLimit)
+{
+ using ::ToString;
+
+ TStringBuilder builder;
+ builder.AppendChar('{');
+
+ bool firstToken = true;
+ auto append = [&] (const char* label, TStringBuf value) {
+ if (!firstToken) {
+ builder.AppendString(", ");
+ }
+ firstToken = false;
+ builder.AppendString(label);
+ builder.AppendString(": ");
+ builder.AppendString(value);
+ };
+
+ if (readLimit.KeyBound()) {
+ append("Key", ToString(readLimit.KeyBound()));
+ }
+
+ if (readLimit.GetRowIndex()) {
+ append("RowIndex", ToString(readLimit.GetRowIndex()));
+ }
+
+ if (readLimit.GetOffset()) {
+ append("Offset", ToString(readLimit.GetOffset()));
+ }
+
+ if (readLimit.GetChunkIndex()) {
+ append("ChunkIndex", ToString(readLimit.GetChunkIndex()));
+ }
+
+ if (readLimit.GetTabletIndex()) {
+ append("TabletIndex", ToString(readLimit.GetTabletIndex()));
+ }
+
+ builder.AppendChar('}');
+ return builder.Flush();
+}
+
+void ToProto(NProto::TReadLimit* protoReadLimit, const TReadLimit& readLimit)
+{
+ if (readLimit.KeyBound()) {
+ ToProto(protoReadLimit->mutable_key_bound_prefix(), readLimit.KeyBound().Prefix);
+ protoReadLimit->set_key_bound_is_inclusive(readLimit.KeyBound().IsInclusive);
+ ToProto(protoReadLimit->mutable_legacy_key(), KeyBoundToLegacyRow(readLimit.KeyBound()));
+ }
+
+ if (readLimit.GetRowIndex()) {
+ protoReadLimit->set_row_index(*readLimit.GetRowIndex());
+ }
+ if (readLimit.GetOffset()) {
+ protoReadLimit->set_offset(*readLimit.GetOffset());
+ }
+ if (readLimit.GetChunkIndex()) {
+ protoReadLimit->set_chunk_index(*readLimit.GetChunkIndex());
+ }
+ if (readLimit.GetTabletIndex()) {
+ protoReadLimit->set_tablet_index(*readLimit.GetTabletIndex());
+ }
+}
+
+void FromProto(TReadLimit* readLimit, const NProto::TReadLimit& protoReadLimit, bool isUpper, int keyLength)
+{
+ // Formally speaking two exceptions in this method could be YT_VERIFY, but let's
+ // try to be more tolerant to possible bugs. After all, this code is used in
+ // master a lot.
+
+ auto validateKeyLengthIsPresent = [=] {
+ if (keyLength == 0) {
+ THROW_ERROR_EXCEPTION(
+ "Read limit contains key, but key length is not provided");
+ }
+ };
+
+ readLimit->KeyBound().IsUpper = isUpper;
+ if (protoReadLimit.has_key_bound_prefix()) {
+ validateKeyLengthIsPresent();
+
+ FromProto(&readLimit->KeyBound().Prefix, protoReadLimit.key_bound_prefix());
+ readLimit->KeyBound().IsInclusive = protoReadLimit.key_bound_is_inclusive();
+
+ if (readLimit->KeyBound().Prefix.GetCount() > keyLength) {
+ THROW_ERROR_EXCEPTION(
+ "Invalid key bound prefix length; expected no more than %v, actual %v",
+ keyLength,
+ readLimit->KeyBound().Prefix.GetCount());
+ }
+ } else if (protoReadLimit.has_legacy_key()) {
+ validateKeyLengthIsPresent();
+
+ TLegacyOwningKey legacyKey;
+ FromProto(&legacyKey, protoReadLimit.legacy_key());
+ readLimit->KeyBound() = KeyBoundFromLegacyRow(legacyKey, isUpper, keyLength);
+ }
+
+ if (protoReadLimit.has_row_index()) {
+ readLimit->SetRowIndex(protoReadLimit.row_index());
+ }
+ if (protoReadLimit.has_offset()) {
+ readLimit->SetOffset(protoReadLimit.offset());
+ }
+ if (protoReadLimit.has_chunk_index()) {
+ readLimit->SetChunkIndex(protoReadLimit.chunk_index());
+ }
+ if (protoReadLimit.has_tablet_index()) {
+ readLimit->SetTabletIndex(protoReadLimit.tablet_index());
+ }
+}
+
+void Serialize(const TReadLimit& readLimit, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .DoIf(static_cast<bool>(readLimit.KeyBound()), [&] (TFluentMap fluent) {
+ fluent
+ .Item("key_bound").Value(readLimit.KeyBound())
+ // COMPAT(max42): it is important to serialize also a key in order to keep
+ // backward compatibility with old clusters. Note that modern version of code
+ // should ignore "key" selector if "key_bound" is present.
+ .Item("key").Value(KeyBoundToLegacyRow(readLimit.KeyBound()));
+ })
+ .OptionalItem("row_index", readLimit.GetRowIndex())
+ .OptionalItem("offset", readLimit.GetOffset())
+ .OptionalItem("chunk_index", readLimit.GetChunkIndex())
+ .OptionalItem("tablet_index", readLimit.GetTabletIndex())
+ .EndMap();
+}
+
+void Deserialize(TReadLimit& readLimit, const INodePtr& node)
+{
+ if (node->GetType() != NYTree::ENodeType::Map) {
+ THROW_ERROR_EXCEPTION("Error parsing read limit: expected %Qlv node, actual %Qlv node",
+ NYTree::ENodeType::Map,
+ node->GetType());
+ }
+
+ readLimit = TReadLimit();
+ auto attributes = ConvertToAttributes(node);
+
+ if (auto optionalKeyBound = FindReadLimitComponent<TOwningKeyBound>(attributes, "key_bound")) {
+ readLimit.KeyBound() = *optionalKeyBound;
+ }
+
+ if (auto optionalRowIndex = FindReadLimitComponent<i64>(attributes, "row_index")) {
+ readLimit.SetRowIndex(*optionalRowIndex);
+ }
+
+ if (auto optionalOffset = FindReadLimitComponent<i64>(attributes, "offset")) {
+ readLimit.SetOffset(*optionalOffset);
+ }
+
+ if (auto optionalChunkIndex = FindReadLimitComponent<i64>(attributes, "chunk_index")) {
+ readLimit.SetChunkIndex(*optionalChunkIndex);
+ }
+
+ if (auto optionalTabletIndex = FindReadLimitComponent<i32>(attributes, "tablet_index")) {
+ readLimit.SetTabletIndex(*optionalTabletIndex);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadRange::TReadRange(TReadLimit lowerLimit, TReadLimit upperLimit)
+ : LowerLimit_(std::move(lowerLimit))
+ , UpperLimit_(std::move(upperLimit))
+{
+ if (LowerLimit_.KeyBound()) {
+ YT_VERIFY(!LowerLimit_.KeyBound().IsUpper);
+ }
+ if (UpperLimit_.KeyBound()) {
+ YT_VERIFY(UpperLimit_.KeyBound().IsUpper);
+ }
+}
+
+TReadRange::TReadRange(
+ const NProto::TReadRange& range,
+ int keyLength)
+{
+ if (range.has_lower_limit()) {
+ LowerLimit_ = TReadLimit(range.lower_limit(), /* isUpper */false, keyLength);
+ }
+ if (range.has_upper_limit()) {
+ UpperLimit_ = TReadLimit(range.upper_limit(), /* isUpper */true, keyLength);
+ }
+}
+
+bool TReadRange::operator == (const TReadRange& other) const
+{
+ return LowerLimit_ == other.LowerLimit_ && UpperLimit_ == other.UpperLimit_;
+}
+
+TReadRange TReadRange::MakeEmpty()
+{
+ TReadRange result;
+ result.LowerLimit_.SetRowIndex(0);
+ result.UpperLimit_.SetRowIndex(0);
+ YT_ASSERT(result.LowerLimit() == result.UpperLimit() && !result.LowerLimit().IsTrivial());
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TReadRange& readRange)
+{
+ return Format("[<%v> : <%v>]", readRange.LowerLimit(), readRange.UpperLimit());
+}
+
+void ToProto(NProto::TReadRange* protoReadRange, const TReadRange& readRange)
+{
+ if (!readRange.LowerLimit().IsTrivial()) {
+ if (readRange.LowerLimit().KeyBound()) {
+ YT_VERIFY(!readRange.LowerLimit().KeyBound().IsUpper);
+ }
+ ToProto(protoReadRange->mutable_lower_limit(), readRange.LowerLimit());
+ }
+ if (!readRange.UpperLimit().IsTrivial()) {
+ if (readRange.UpperLimit().KeyBound()) {
+ YT_VERIFY(readRange.UpperLimit().KeyBound().IsUpper);
+ }
+ ToProto(protoReadRange->mutable_upper_limit(), readRange.UpperLimit());
+ }
+}
+
+void FromProto(TReadRange* readRange, const NProto::TReadRange& protoReadRange, int keyLength)
+{
+ if (protoReadRange.has_lower_limit()) {
+ FromProto(&readRange->LowerLimit(), protoReadRange.lower_limit(), /* isUpper */ false, keyLength);
+ }
+ if (protoReadRange.has_upper_limit()) {
+ FromProto(&readRange->UpperLimit(), protoReadRange.upper_limit(), /* isUpper */ true, keyLength);
+ }
+}
+
+void Serialize(const TReadRange& readLimit, NYson::IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("lower_limit").Value(readLimit.LowerLimit())
+ .Item("upper_limit").Value(readLimit.UpperLimit())
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+REGISTER_INTERMEDIATE_PROTO_INTEROP_BYTES_FIELD_REPRESENTATION(NProto::TReadLimit, /*key*/4, TUnversionedOwningRow)
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadLimit ReadLimitFromLegacyReadLimit(const TLegacyReadLimit& legacyReadLimit, bool isUpper, int keyLength)
+{
+ TReadLimit result;
+ if (legacyReadLimit.HasRowIndex()) {
+ result.SetRowIndex(legacyReadLimit.GetRowIndex());
+ }
+ if (legacyReadLimit.HasChunkIndex()) {
+ result.SetChunkIndex(legacyReadLimit.GetChunkIndex());
+ }
+ if (legacyReadLimit.HasOffset()) {
+ result.SetOffset(legacyReadLimit.GetOffset());
+ }
+ if (legacyReadLimit.HasTabletIndex()) {
+ result.SetTabletIndex(legacyReadLimit.GetTabletIndex());
+ }
+ if (legacyReadLimit.HasLegacyKey()) {
+ result.KeyBound() = KeyBoundFromLegacyRow(legacyReadLimit.GetLegacyKey(), isUpper, keyLength);
+ }
+
+ return result;
+}
+
+TReadLimit ReadLimitFromLegacyReadLimitKeyless(const TLegacyReadLimit& legacyReadLimit)
+{
+ YT_VERIFY(!legacyReadLimit.HasLegacyKey());
+
+ TReadLimit result;
+ if (legacyReadLimit.HasRowIndex()) {
+ result.SetRowIndex(legacyReadLimit.GetRowIndex());
+ }
+ if (legacyReadLimit.HasChunkIndex()) {
+ result.SetChunkIndex(legacyReadLimit.GetChunkIndex());
+ }
+ if (legacyReadLimit.HasOffset()) {
+ result.SetOffset(legacyReadLimit.GetOffset());
+ }
+ if (legacyReadLimit.HasTabletIndex()) {
+ result.SetTabletIndex(legacyReadLimit.GetTabletIndex());
+ }
+
+ return result;
+}
+
+TLegacyReadLimit ReadLimitToLegacyReadLimit(const TReadLimit& readLimit)
+{
+ TLegacyReadLimit result;
+ if (const auto& rowIndex = readLimit.GetRowIndex()) {
+ result.SetRowIndex(*rowIndex);
+ }
+ if (const auto& chunkIndex = readLimit.GetChunkIndex()) {
+ result.SetChunkIndex(*chunkIndex);
+ }
+ if (const auto& offset = readLimit.GetOffset()) {
+ result.SetOffset(*offset);
+ }
+ if (const auto& tabletIndex = readLimit.GetTabletIndex()) {
+ result.SetTabletIndex(*tabletIndex);
+ }
+ if (readLimit.KeyBound()) {
+ result.SetLegacyKey(KeyBoundToLegacyRow(readLimit.KeyBound()));
+ }
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadRange ReadRangeFromLegacyReadRange(const TLegacyReadRange& legacyReadRange, int keyLength)
+{
+ TReadRange result;
+
+ result.LowerLimit() = ReadLimitFromLegacyReadLimit(legacyReadRange.LowerLimit(), /* isUpper */ false, keyLength);
+ result.UpperLimit() = ReadLimitFromLegacyReadLimit(legacyReadRange.UpperLimit(), /* isUpper */ true, keyLength);
+
+ return result;
+}
+
+TReadRange ReadRangeFromLegacyReadRangeKeyless(const TLegacyReadRange& legacyReadRange)
+{
+ TReadRange result;
+
+ result.LowerLimit() = ReadLimitFromLegacyReadLimitKeyless(legacyReadRange.LowerLimit());
+ result.UpperLimit() = ReadLimitFromLegacyReadLimitKeyless(legacyReadRange.UpperLimit());
+
+ return result;
+}
+
+TLegacyReadRange ReadRangeToLegacyReadRange(const TReadRange& readRange)
+{
+ TLegacyReadRange result;
+
+ result.LowerLimit() = ReadLimitToLegacyReadLimit(readRange.LowerLimit());
+ result.UpperLimit() = ReadLimitToLegacyReadLimit(readRange.UpperLimit());
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/read_limit.h b/yt/yt/client/chunk_client/read_limit.h
new file mode 100644
index 0000000000..0ce33e3b0e
--- /dev/null
+++ b/yt/yt/client/chunk_client/read_limit.h
@@ -0,0 +1,251 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/read_limit.pb.h>
+
+#include <yt/yt/client/table_client/key_bound.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/public.h>
+#include <yt/yt/core/ytree/serialize.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLegacyReadLimit
+{
+public:
+ TLegacyReadLimit() = default;
+
+ explicit TLegacyReadLimit(const NProto::TReadLimit& readLimit);
+ explicit TLegacyReadLimit(NProto::TReadLimit&& readLimit);
+ explicit TLegacyReadLimit(const std::unique_ptr<NProto::TReadLimit>& protoLimit);
+
+ explicit TLegacyReadLimit(const NTableClient::TLegacyOwningKey& key);
+ explicit TLegacyReadLimit(NTableClient::TLegacyOwningKey&& key);
+
+ TLegacyReadLimit& operator= (const NProto::TReadLimit& protoLimit);
+ TLegacyReadLimit& operator= (NProto::TReadLimit&& protoLimit);
+
+ TLegacyReadLimit GetSuccessor() const;
+
+ const NProto::TReadLimit& AsProto() const;
+
+ const NTableClient::TLegacyOwningKey& GetLegacyKey() const;
+ bool HasLegacyKey() const;
+ TLegacyReadLimit& SetLegacyKey(const NTableClient::TLegacyOwningKey& key);
+ TLegacyReadLimit& SetLegacyKey(NTableClient::TLegacyOwningKey&& key);
+
+ i64 GetRowIndex() const;
+ bool HasRowIndex() const;
+ TLegacyReadLimit& SetRowIndex(i64 rowIndex);
+
+ i64 GetOffset() const;
+ bool HasOffset() const;
+ TLegacyReadLimit& SetOffset(i64 offset);
+
+ i64 GetChunkIndex() const;
+ bool HasChunkIndex() const;
+ TLegacyReadLimit& SetChunkIndex(i64 chunkIndex);
+
+ i32 GetTabletIndex() const;
+ bool HasTabletIndex() const;
+ TLegacyReadLimit& SetTabletIndex(i32 tabletIndex);
+
+ bool IsTrivial() const;
+
+ void MergeLowerLegacyKey(const NTableClient::TLegacyOwningKey& key);
+ void MergeUpperLegacyKey(const NTableClient::TLegacyOwningKey& key);
+
+ void MergeLowerRowIndex(i64 rowIndex);
+ void MergeUpperRowIndex(i64 rowIndex);
+
+ void Persist(const TStreamPersistenceContext& context);
+
+ size_t SpaceUsed() const;
+
+private:
+ NProto::TReadLimit ReadLimit_;
+ NTableClient::TLegacyOwningKey Key_;
+
+ void InitKey();
+ void InitCopy(const NProto::TReadLimit& readLimit);
+ void InitMove(NProto::TReadLimit&& readLimit);
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TLegacyReadLimit& limit);
+
+bool IsTrivial(const TLegacyReadLimit& limit);
+bool IsTrivial(const NProto::TReadLimit& limit);
+
+void ToProto(NProto::TReadLimit* protoReadLimit, const TLegacyReadLimit& readLimit);
+void FromProto(TLegacyReadLimit* readLimit, const NProto::TReadLimit& protoReadLimit);
+
+void Serialize(const TLegacyReadLimit& readLimit, NYson::IYsonConsumer* consumer);
+void Deserialize(TLegacyReadLimit& readLimit, NYTree::INodePtr node);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLegacyReadRange
+{
+public:
+ TLegacyReadRange() = default;
+ TLegacyReadRange(const TLegacyReadLimit& lowerLimit, const TLegacyReadLimit& upperLimit);
+ explicit TLegacyReadRange(const TLegacyReadLimit& exact);
+
+ explicit TLegacyReadRange(const NProto::TReadRange& range);
+ explicit TLegacyReadRange(NProto::TReadRange&& range);
+ TLegacyReadRange& operator= (const NProto::TReadRange& range);
+ TLegacyReadRange& operator= (NProto::TReadRange&& range);
+
+ DEFINE_BYREF_RW_PROPERTY(TLegacyReadLimit, LowerLimit);
+ DEFINE_BYREF_RW_PROPERTY(TLegacyReadLimit, UpperLimit);
+
+ void Persist(const TStreamPersistenceContext& context);
+
+private:
+ void InitCopy(const NProto::TReadRange& range);
+ void InitMove(NProto::TReadRange&& range);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TLegacyReadRange& range);
+
+void ToProto(NProto::TReadRange* protoReadRange, const TLegacyReadRange& readRange);
+void FromProto(TLegacyReadRange* readRange, const NProto::TReadRange& protoReadRange);
+
+void Serialize(const TLegacyReadRange& readRange, NYson::IYsonConsumer* consumer);
+void Deserialize(TLegacyReadRange& readRange, NYTree::INodePtr node);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadLimit
+{
+public:
+ DEFINE_BYREF_RW_PROPERTY(NTableClient::TOwningKeyBound, KeyBound);
+ DEFINE_BYVAL_RW_PROPERTY(std::optional<i64>, RowIndex);
+ DEFINE_BYVAL_RW_PROPERTY(std::optional<i64>, Offset);
+ DEFINE_BYVAL_RW_PROPERTY(std::optional<i64>, ChunkIndex);
+ DEFINE_BYVAL_RW_PROPERTY(std::optional<i32>, TabletIndex);
+
+public:
+ TReadLimit() = default;
+ explicit TReadLimit(const NTableClient::TKeyBound& keyBound);
+ explicit TReadLimit(NTableClient::TOwningKeyBound keyBound);
+ TReadLimit(
+ const NProto::TReadLimit& readLimit,
+ bool isUpper,
+ int keyLength = 0);
+
+ //! Returns true if no selectors are specified.
+ bool IsTrivial() const;
+
+ //! Returns true if this read limit contains more than one "independent" selector.
+ //! By "independent" we mean following: "row_index" and "key" are independent, but
+ //! "tablet_index" and "row_index" are not since they are used for ordered dyntables
+ //! and tuple (tablet_index, row_index) defines a read limit for them.
+ //!
+ //! Rationale: this method allows checking if read limit is suitable for "exact" clause
+ //! or for specifying read ranges for Erase operation.
+ //!
+ //! Equivalent way of thinking: if readLimit.HasIndependentSelectors() == true,
+ //! then {upper_limit = readLimit} and {lower_limit = readLimit} always define
+ //! complementary row sets.
+ bool HasIndependentSelectors() const;
+
+ //! Return number of specified selectors.
+ int GetSelectorCount() const;
+
+ //! Given read limit with exactly one selector, either increment single integer selector, or
+ //! invert key bound selector. This method is used to transform "exact" YPath read limit
+ //! into a pair of lower and upper read limit.
+ TReadLimit ToExactUpperCounterpart() const;
+
+ //! Return inverted read limit, i.e. integer selectors remain as is and key bound is inverted.
+ //! NB: this method makes YT_VERIFY that read limit contains exactly one selector. Otherwise
+ //! semantics of such method is weird.
+ TReadLimit Invert() const;
+
+ bool operator == (const TReadLimit& other) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TReadLimit& readLimit);
+
+void ToProto(NProto::TReadLimit* protoReadLimit, const TReadLimit& readLimit);
+//! If protoReadLimit contains key, it is transformed into new key bound by
+//! calling KeyBoundFromLegacyKey using *keyLength. In this case, if keyLength
+//! is std::nullopt, exception is thrown.
+void FromProto(TReadLimit* readLimit, const NProto::TReadLimit& protoReadLimit, bool isUpper, int keyLength);
+
+void Serialize(const TReadLimit& readLimit, NYson::IYsonConsumer* consumer);
+//! This method deserializes modern read limit representation, recognizing only key bound, but not legacy key.
+void Deserialize(TReadLimit& readLimit, const NYTree::INodePtr& node);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadRange
+{
+public:
+ DEFINE_BYREF_RW_PROPERTY(TReadLimit, LowerLimit);
+ DEFINE_BYREF_RW_PROPERTY(TReadLimit, UpperLimit);
+
+public:
+ TReadRange() = default;
+ TReadRange(TReadLimit lowerLimit, TReadLimit upperLimit);
+ TReadRange(
+ const NProto::TReadRange& range,
+ int keyLength = 0);
+
+ bool operator == (const TReadRange& other) const;
+
+ //! Creates a range such that no row fits in it.
+ static TReadRange MakeEmpty();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TReadRange& readRange);
+
+void ToProto(NProto::TReadRange* protoReadRange, const TReadRange& readRange);
+//! See comment for FromProto(TReadLimit*, const NProto::TReadLimit&, bool, std::optional<int>).
+void FromProto(TReadRange* readRange, const NProto::TReadRange& protoReadRange, int keyLength);
+
+void Serialize(const TReadRange& readLimit, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Interop functions.
+
+//! Transform legacy read limit to new read limit possibly transforming legacy key into
+//! key bound by calling KeyBoundFromLegacyKey.
+TReadLimit ReadLimitFromLegacyReadLimit(const TLegacyReadLimit& legacyReadLimit, bool isUpper, int keyLength);
+//! Transform legacy read limit without legacy key into new read limit (merely copying all integer fields).
+TReadLimit ReadLimitFromLegacyReadLimitKeyless(const TLegacyReadLimit& legacyReadLimit);
+
+//! Transform new read limit into legacy read limit.
+TLegacyReadLimit ReadLimitToLegacyReadLimit(const TReadLimit& readLimit);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Transform legacy read range to new read range possibly transforming legacy keys into
+//! key bounds by calling KeyBoundFromLegacyKey.
+TReadRange ReadRangeFromLegacyReadRange(const TLegacyReadRange& legacyReadRange, int keyLength);
+//! Transform legacy read range without legacy keys into new read range (merely copying all integer fields).
+TReadRange ReadRangeFromLegacyReadRangeKeyless(const TLegacyReadRange& legacyReadRange);
+
+//! Transform new read range into legacy read range.
+TLegacyReadRange ReadRangeToLegacyReadRange(const TReadRange& readRange);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/reader_base.h b/yt/yt/client/chunk_client/reader_base.h
new file mode 100644
index 0000000000..baa163fb8f
--- /dev/null
+++ b/yt/yt/client/chunk_client/reader_base.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "data_statistics.h"
+#include "ready_event_reader_base.h"
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IReaderBase
+ : public virtual IReadyEventReaderBase
+{
+ virtual NProto::TDataStatistics GetDataStatistics() const = 0;
+ virtual TCodecStatistics GetDecompressionStatistics() const = 0;
+
+ virtual bool IsFetchingCompleted() const = 0;
+ virtual std::vector<TChunkId> GetFailedChunkIds() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IReaderBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/ready_event_reader_base.cpp b/yt/yt/client/chunk_client/ready_event_reader_base.cpp
new file mode 100644
index 0000000000..6f7e217916
--- /dev/null
+++ b/yt/yt/client/chunk_client/ready_event_reader_base.cpp
@@ -0,0 +1,39 @@
+#include "ready_event_reader_base.h"
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFuture<void> TReadyEventReaderBase::GetReadyEvent() const
+{
+ WaitTimer_.StartIfNotActive();
+ if (ReadyEvent_.IsSet()) {
+ WaitTimer_.Stop();
+ }
+ return ReadyEvent_;
+}
+
+const TFuture<void>& TReadyEventReaderBase::ReadyEvent() const
+{
+ return ReadyEvent_;
+}
+
+void TReadyEventReaderBase::SetReadyEvent(TFuture<void> readyEvent)
+{
+ // We could use TTimingGuard here but we try to not prolong
+ // reader lifetime for such insignificant business as timing.
+ ReadyEvent_ = readyEvent.Apply(BIND([weakThis = MakeWeak(this)] {
+ if (auto strongThis = weakThis.Lock()) {
+ strongThis->WaitTimer_.Stop();
+ }
+ }));
+}
+
+TDuration TReadyEventReaderBase::GetWaitTime() const
+{
+ return WaitTimer_.GetElapsedTime();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/ready_event_reader_base.h b/yt/yt/client/chunk_client/ready_event_reader_base.h
new file mode 100644
index 0000000000..ffaf6b1cf7
--- /dev/null
+++ b/yt/yt/client/chunk_client/ready_event_reader_base.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/profiling/timing.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IReadyEventReaderBase
+ : public virtual TRefCounted
+{
+ virtual TFuture<void> GetReadyEvent() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IReadyEventReaderBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadyEventReaderBase
+ : public virtual NChunkClient::IReadyEventReaderBase
+{
+protected:
+ //! Return ready event without starting wait timer. Intended for internal use in subclasses.
+ const TFuture<void>& ReadyEvent() const;
+
+ //! Set ready event. Ready event is wrapped with a callback which
+ //! stops wait timer when ready event is ready.
+ void SetReadyEvent(TFuture<void> readyEvent);
+
+ //! Return how much time caller spent waiting on ready event.
+ TDuration GetWaitTime() const;
+
+private:
+ TFuture<void> ReadyEvent_ = VoidFuture;
+
+ //! This timer is started when GetReadyEvent() is invoked and stopped when ready event is set.
+ //! In other words, it shows how much time caller spends waiting on ready event.
+ mutable NProfiling::TWallTimer WaitTimer_ = NProfiling::TWallTimer(false /* start */);
+
+ //! Return ready event and start wait timer if it is not already active.
+ //! This method is intended for external use, but not for accessing ready event from subclasses, thus private.
+ TFuture<void> GetReadyEvent() const override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/chunk_client/writer_base.h b/yt/yt/client/chunk_client/writer_base.h
new file mode 100644
index 0000000000..e4cd0b00c3
--- /dev/null
+++ b/yt/yt/client/chunk_client/writer_base.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NChunkClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! The purpose of this interface is to be a virtual base for TMultiChunkSequentialWriter
+//! and some specific writers, e.g. IVersionedWriter, to mix them up.
+struct IWriterBase
+ : public virtual TRefCounted
+{
+ //! Returns an asynchronous flag enabling to wait until data is written.
+ virtual TFuture<void> GetReadyEvent() = 0;
+
+ //! Closes the writer. Must be the last call to the writer.
+ virtual TFuture<void> Close() = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/complex_types/check_type_compatibility.cpp b/yt/yt/client/complex_types/check_type_compatibility.cpp
new file mode 100644
index 0000000000..49a744198f
--- /dev/null
+++ b/yt/yt/client/complex_types/check_type_compatibility.cpp
@@ -0,0 +1,444 @@
+#include "check_type_compatibility.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+
+using namespace NYT::NTableClient;
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TCompatibilityPair = std::pair<ESchemaCompatibility, TError>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TCompatibilityPair CheckTypeCompatibilityImpl(
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor);
+
+static const TCompatibilityPair& MinCompatibility(const TCompatibilityPair& lhs, const TCompatibilityPair& rhs)
+{
+ if (lhs.first <= rhs.first) {
+ return lhs;
+ } else {
+ return rhs;
+ }
+}
+
+static TCompatibilityPair CreateResultPair(
+ ESchemaCompatibility compatibility,
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor)
+{
+ if (compatibility == ESchemaCompatibility::FullyCompatible) {
+ return {compatibility, TError()};
+ }
+ return {
+ compatibility,
+ TError(
+ "Type of %Qv field is modified in non backward compatible manner",
+ oldDescriptor.GetDescription())
+ << TErrorAttribute("old_type", ToString(*oldDescriptor.GetType()))
+ << TErrorAttribute("new_type", ToString(*newDescriptor.GetType()))
+ };
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ESimpleTypeClass,
+ (Int)
+ (Uint)
+ (Floating)
+ (String)
+);
+
+template <ESimpleTypeClass typeClass>
+static int GetSimpleTypeRank(ESimpleLogicalValueType type)
+{
+ if constexpr (typeClass == ESimpleTypeClass::Int) {
+ switch (type) {
+ case ESimpleLogicalValueType::Int8:
+ return 8;
+ case ESimpleLogicalValueType::Int16:
+ return 16;
+ case ESimpleLogicalValueType::Int32:
+ return 32;
+ case ESimpleLogicalValueType::Int64:
+ return 64;
+ default:
+ return -1;
+ }
+ } else if constexpr (typeClass == ESimpleTypeClass::Uint) {
+ switch (type) {
+ case ESimpleLogicalValueType::Uint8:
+ return 8;
+ case ESimpleLogicalValueType::Uint16:
+ return 16;
+ case ESimpleLogicalValueType::Uint32:
+ return 32;
+ case ESimpleLogicalValueType::Uint64:
+ return 64;
+ default:
+ return -1;
+ }
+ } else if constexpr (typeClass == ESimpleTypeClass::Floating) {
+ switch (type) {
+ case ESimpleLogicalValueType::Float:
+ return 32;
+ case ESimpleLogicalValueType::Double:
+ return 64;
+ default:
+ return -1;
+ }
+ } else if constexpr (typeClass == ESimpleTypeClass::String) {
+ switch (type) {
+ case ESimpleLogicalValueType::Utf8:
+ return 1;
+ case ESimpleLogicalValueType::String:
+ return 2;
+ default:
+ return -1;
+ }
+ } else {
+ // Poor man static_assert(false).
+ static_assert(typeClass == ESimpleTypeClass::Int);
+ }
+}
+
+template <ESimpleTypeClass typeClass>
+static constexpr ESchemaCompatibility CheckCompatibilityUsingClass(ESimpleLogicalValueType oldType, ESimpleLogicalValueType newType)
+{
+ int oldRank = GetSimpleTypeRank<typeClass>(oldType);
+ if (oldRank <= 0) {
+ THROW_ERROR_EXCEPTION("Internal error; unexpected rank %v of type %Qlv",
+ oldRank,
+ oldType);
+ }
+ int newRank = GetSimpleTypeRank<typeClass>(newType);
+ if (newRank < 0) {
+ return ESchemaCompatibility::Incompatible;
+ }
+ if (newRank >= oldRank) {
+ return ESchemaCompatibility::FullyCompatible;
+ } else {
+ return ESchemaCompatibility::RequireValidation;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TCompatibilityPair CheckTypeCompatibilitySimple(
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor)
+{
+ auto oldElement = oldDescriptor.GetType()->AsSimpleTypeRef().GetElement();
+ auto newElement = newDescriptor.GetType()->AsSimpleTypeRef().GetElement();
+
+ if (newElement == ESimpleLogicalValueType::Any) {
+ // N.B. for historical reasons a bunch of types could be cast to Any type.
+ ESchemaCompatibility result;
+ switch (oldElement) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+
+ case ESimpleLogicalValueType::Float:
+ case ESimpleLogicalValueType::Double:
+
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Utf8:
+
+ case ESimpleLogicalValueType::Boolean:
+
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ case ESimpleLogicalValueType::Interval:
+
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+
+ case ESimpleLogicalValueType::Any:
+ result = ESchemaCompatibility::FullyCompatible;
+ break;
+
+ default:
+ result = ESchemaCompatibility::Incompatible;
+ break;
+ }
+ return CreateResultPair(
+ result,
+ oldDescriptor,
+ newDescriptor);
+ }
+
+ switch (oldElement) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+ return CreateResultPair(
+ CheckCompatibilityUsingClass<ESimpleTypeClass::Int>(oldElement, newElement),
+ oldDescriptor,
+ newDescriptor);
+
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+ return CreateResultPair(
+ CheckCompatibilityUsingClass<ESimpleTypeClass::Uint>(oldElement, newElement),
+ oldDescriptor,
+ newDescriptor);
+
+ case ESimpleLogicalValueType::Float:
+ case ESimpleLogicalValueType::Double:
+ return CreateResultPair(
+ CheckCompatibilityUsingClass<ESimpleTypeClass::Floating>(oldElement, newElement),
+ oldDescriptor,
+ newDescriptor);
+
+ case ESimpleLogicalValueType::Utf8:
+ case ESimpleLogicalValueType::String:
+ return CreateResultPair(
+ CheckCompatibilityUsingClass<ESimpleTypeClass::String>(oldElement, newElement),
+ oldDescriptor,
+ newDescriptor);
+
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ case ESimpleLogicalValueType::Boolean:
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ case ESimpleLogicalValueType::Interval:
+ case ESimpleLogicalValueType::Json:
+ case ESimpleLogicalValueType::Uuid:
+ case ESimpleLogicalValueType::Any: {
+ const auto compatibility =
+ oldElement == newElement ? ESchemaCompatibility::FullyCompatible : ESchemaCompatibility::Incompatible;
+ return CreateResultPair(compatibility, oldDescriptor, newDescriptor);
+ }
+ }
+ THROW_ERROR_EXCEPTION("Internal error: unknown logical type: %Qlv",
+ oldElement);
+}
+
+TCompatibilityPair CheckElementsCompatibility(
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor,
+ bool allowNewElements)
+{
+ const auto oldSize = std::ssize(oldDescriptor.GetType()->GetElements());
+ const auto newSize = std::ssize(newDescriptor.GetType()->GetElements());
+ if (oldSize > newSize) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Some elements of %Qv are removed",
+ oldDescriptor.GetDescription()),
+ };
+ }
+ if (!allowNewElements && oldSize != newSize) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Added new elements to tuple %Qv",
+ oldDescriptor.GetDescription()),
+ };
+ }
+ auto result = std::pair(ESchemaCompatibility::FullyCompatible, TError());
+ for (int i = 0; result.first != ESchemaCompatibility::Incompatible && i < oldSize; ++i) {
+ result = MinCompatibility(
+ result,
+ CheckTypeCompatibilityImpl(oldDescriptor.Element(i), newDescriptor.Element(i)));
+ }
+ return result;
+}
+
+TCompatibilityPair CheckFieldsCompatibility(
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor,
+ bool checkNewFieldsNullability)
+{
+ const auto& oldFields = oldDescriptor.GetType()->GetFields();
+ const auto& newFields = newDescriptor.GetType()->GetFields();
+ if (oldFields.size() > newFields.size()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Some members of %Qv are removed",
+ oldDescriptor.GetDescription()),
+ };
+ }
+
+ int fieldIndex = 0;
+ //
+ auto result = std::pair(ESchemaCompatibility::FullyCompatible, TError());
+ for (; fieldIndex < std::ssize(oldFields) && result.first != ESchemaCompatibility::Incompatible; ++fieldIndex) {
+ const auto& oldName = oldFields[fieldIndex].Name;
+ const auto& newName = newFields[fieldIndex].Name;
+ if (oldName != newName) {
+ result = {
+ ESchemaCompatibility::Incompatible,
+ TError(
+ "Member name mismatch in %Qv: old name %Qv, new name %Qv",
+ oldDescriptor.GetDescription(),
+ oldName,
+ newName)
+ };
+ } else {
+ const auto& oldFieldDescriptor = oldDescriptor.Field(fieldIndex);
+ const auto& newFieldDescriptor = newDescriptor.Field(fieldIndex);
+ auto currentCompatibility = CheckTypeCompatibilityImpl(oldFieldDescriptor, newFieldDescriptor);
+ result = MinCompatibility(result, currentCompatibility);
+ }
+ }
+
+ // All added fields must be nullable
+ if (checkNewFieldsNullability) {
+ for (; fieldIndex < std::ssize(newFields) && result.first != ESchemaCompatibility::Incompatible; ++fieldIndex) {
+ const auto& newFieldDescriptor = newDescriptor.Field(fieldIndex);
+ if (!newFieldDescriptor.GetType()->IsNullable()) {
+ result = {
+ ESchemaCompatibility::Incompatible,
+ TError(
+ "Newly added member %Qv is not optional",
+ newFieldDescriptor.GetDescription())
+ << TErrorAttribute("new_type", ToString(*newFieldDescriptor.GetType()))
+ };
+ }
+ }
+ }
+ return result;
+}
+
+TCompatibilityPair CheckDictTypeCompatibility(
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor)
+{
+ auto keyCompatibility = CheckTypeCompatibilityImpl(
+ oldDescriptor.DictKey(),
+ newDescriptor.DictKey());
+ if (keyCompatibility.first == ESchemaCompatibility::Incompatible) {
+ return keyCompatibility;
+ }
+ const auto valueCompatibility = CheckTypeCompatibilityImpl(
+ oldDescriptor.DictValue(),
+ newDescriptor.DictValue());
+ return MinCompatibility(keyCompatibility, valueCompatibility);
+}
+
+TCompatibilityPair CheckDecimalTypeCompatibility(
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor)
+{
+ if (*oldDescriptor.GetType() == *newDescriptor.GetType()) {
+ return {ESchemaCompatibility::FullyCompatible, {}};
+ } else {
+ return CreateResultPair(ESchemaCompatibility::Incompatible, oldDescriptor, newDescriptor);
+ }
+}
+
+// Returns pair:
+// 1. Inner element that is neither optional nor tagged.
+// 2. How many times this element is wrapped into Optional type.
+static std::pair<TComplexTypeFieldDescriptor, int> UnwrapOptionalAndTagged(const TComplexTypeFieldDescriptor& descriptor)
+{
+ int nesting = 0;
+ auto current = descriptor;
+ while (true) {
+ const auto metatype = current.GetType()->GetMetatype();
+ if (metatype == ELogicalMetatype::Optional) {
+ ++nesting;
+ current = current.OptionalElement();
+ } else if (metatype == ELogicalMetatype::Tagged) {
+ current = current.TaggedElement();
+ } else {
+ break;
+ }
+ }
+ return {current, nesting};
+}
+
+static TCompatibilityPair CheckTypeCompatibilityImpl(
+ const TComplexTypeFieldDescriptor& oldDescriptor,
+ const TComplexTypeFieldDescriptor& newDescriptor)
+{
+ const auto oldMetatype = oldDescriptor.GetType()->GetMetatype();
+ const auto newMetatype = newDescriptor.GetType()->GetMetatype();
+
+ if (oldMetatype == ELogicalMetatype::Optional ||
+ oldMetatype == ELogicalMetatype::Tagged ||
+ newMetatype == ELogicalMetatype::Optional ||
+ newMetatype == ELogicalMetatype::Tagged)
+ {
+ const auto [oldElement, oldNesting] = UnwrapOptionalAndTagged(oldDescriptor);
+ const auto [newElement, newNesting] = UnwrapOptionalAndTagged(newDescriptor);
+
+ if (oldNesting == newNesting || oldNesting == 0 && newNesting == 1) {
+ return CheckTypeCompatibilityImpl(oldElement, newElement);
+ } else if (oldNesting == 1 && newNesting == 0) {
+ auto elementCompatibility = CheckTypeCompatibilityImpl(oldElement, newElement);
+ return MinCompatibility(
+ elementCompatibility,
+ CreateResultPair(ESchemaCompatibility::RequireValidation, oldElement, newElement));
+ } else {
+ return CreateResultPair(ESchemaCompatibility::Incompatible, oldElement, newElement);
+ }
+ }
+
+ if (oldMetatype != newMetatype) {
+ return CreateResultPair(ESchemaCompatibility::Incompatible, oldDescriptor, newDescriptor);
+ }
+
+ switch (oldMetatype) {
+ case ELogicalMetatype::Simple:
+ return CheckTypeCompatibilitySimple(oldDescriptor, newDescriptor);
+ case ELogicalMetatype::Optional:
+ case ELogicalMetatype::Tagged:
+ // Optional and Tagged cases were checked earlier in this function.
+ THROW_ERROR_EXCEPTION("Internal error; unexpected optional or tagged");
+ case ELogicalMetatype::List:
+ return CheckTypeCompatibilityImpl(oldDescriptor.ListElement(), newDescriptor.ListElement());
+ case ELogicalMetatype::VariantStruct:
+ return CheckFieldsCompatibility(
+ oldDescriptor,
+ newDescriptor,
+ /*checkNewFieldsNullability*/ false);
+ case ELogicalMetatype::Struct:
+ return CheckFieldsCompatibility(
+ oldDescriptor,
+ newDescriptor,
+ /*checkNewFieldsNullability*/ true);
+ case ELogicalMetatype::Tuple:
+ return CheckElementsCompatibility(
+ oldDescriptor,
+ newDescriptor,
+ /*allowNewElements*/ false);
+ case ELogicalMetatype::VariantTuple:
+ return CheckElementsCompatibility(
+ oldDescriptor,
+ newDescriptor,
+ /*allowNewElement*/ true);
+ case ELogicalMetatype::Dict:
+ return CheckDictTypeCompatibility(oldDescriptor, newDescriptor);
+ case ELogicalMetatype::Decimal:
+ return CheckDecimalTypeCompatibility(oldDescriptor, newDescriptor);
+ }
+ THROW_ERROR_EXCEPTION("Internal error; unexpected metatype: %Qlv", oldMetatype);
+}
+
+TCompatibilityPair CheckTypeCompatibility(
+ const NYT::NTableClient::TLogicalTypePtr& oldType,
+ const NYT::NTableClient::TLogicalTypePtr& newType)
+{
+ return CheckTypeCompatibilityImpl(TComplexTypeFieldDescriptor(oldType), TComplexTypeFieldDescriptor(newType));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/check_type_compatibility.h b/yt/yt/client/complex_types/check_type_compatibility.h
new file mode 100644
index 0000000000..086f1d9134
--- /dev/null
+++ b/yt/yt/client/complex_types/check_type_compatibility.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Returned value is pair with elements
+// 1. Compatibility of types.
+// 2. If types are NOT FullyCompatible, error contains description of incompatibility.
+std::pair<NTableClient::ESchemaCompatibility, TError> CheckTypeCompatibility(
+ const NYT::NTableClient::TLogicalTypePtr& oldType,
+ const NYT::NTableClient::TLogicalTypePtr& newType);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/check_yson_token-inl.h b/yt/yt/client/complex_types/check_yson_token-inl.h
new file mode 100644
index 0000000000..4f6f83da4d
--- /dev/null
+++ b/yt/yt/client/complex_types/check_yson_token-inl.h
@@ -0,0 +1,25 @@
+#ifndef CHECK_YSON_TOKEN_INL_H_
+#error "Direct inclusion of this file is not allowed, include check_yson.h"
+// For the sake of sane code completion.
+#include "check_yson_token-inl.h"
+#endif
+
+#include <yt/yt/core/yson/pull_parser.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_FORCE_INLINE void EnsureYsonToken(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const NYson::TYsonPullParserCursor& cursor,
+ NYson::EYsonItemType expected)
+{
+ if (expected != cursor->GetType()) {
+ ThrowUnexpectedYsonTokenException(descriptor, cursor, {expected});
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/check_yson_token.cpp b/yt/yt/client/complex_types/check_yson_token.cpp
new file mode 100644
index 0000000000..6109f31d95
--- /dev/null
+++ b/yt/yt/client/complex_types/check_yson_token.cpp
@@ -0,0 +1,22 @@
+#include "check_yson_token.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+
+namespace NYT::NComplexTypes {
+
+using namespace NYson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ThrowUnexpectedYsonTokenException(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const TYsonPullParserCursor& cursor,
+ const std::vector<EYsonItemType>& expected)
+{
+ ThrowUnexpectedYsonTokenException(descriptor.GetDescription(), cursor, expected);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/check_yson_token.h b/yt/yt/client/complex_types/check_yson_token.h
new file mode 100644
index 0000000000..153acc298a
--- /dev/null
+++ b/yt/yt/client/complex_types/check_yson_token.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <yt/yt/client/table_client/public.h>
+#include <yt/yt/core/yson/public.h>
+
+#include <vector>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_FORCE_INLINE void EnsureYsonToken(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const NYson::TYsonPullParserCursor& cursor,
+ NYson::EYsonItemType expected);
+
+void ThrowUnexpectedYsonTokenException(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const NYson::TYsonPullParserCursor& cursor,
+ const std::vector<NYson::EYsonItemType>& expected);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
+
+#define CHECK_YSON_TOKEN_INL_H_
+#include "check_yson_token-inl.h"
+#undef CHECK_YSON_TOKEN_INL_H_
diff --git a/yt/yt/client/complex_types/infinite_entity.cpp b/yt/yt/client/complex_types/infinite_entity.cpp
new file mode 100644
index 0000000000..8534dd9892
--- /dev/null
+++ b/yt/yt/client/complex_types/infinite_entity.cpp
@@ -0,0 +1,39 @@
+#include "infinite_entity.h"
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TInfiniteEntity::TInfiniteEntity()
+ : Stream_(TStringBuf("#;#;#;#;#;#;#;#;"))
+ , Parser_(&Stream_, NYson::EYsonType::ListFragment)
+ , Cursor_(&Parser_)
+{
+ YT_VERIFY(Cursor_.TryConsumeFragmentStart());
+}
+
+NYson::TYsonPullParserCursor* TInfiniteEntity::GetCursor()
+{
+ return &Cursor_;
+}
+
+TInfiniteEntity::TRingBufferStream::TRingBufferStream(TStringBuf buffer)
+ : Buffer_(buffer)
+ , Pointer_(Buffer_.data())
+{ }
+
+size_t TInfiniteEntity::TRingBufferStream::DoNext(const void** ptr, size_t len)
+{
+ const auto end = Buffer_.data() + Buffer_.size();
+ auto result = Min<size_t>(len, end - Pointer_);
+ *ptr = Pointer_;
+ Pointer_ += result;
+ if (Pointer_ == end) {
+ Pointer_ = Buffer_.data();
+ }
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/infinite_entity.h b/yt/yt/client/complex_types/infinite_entity.h
new file mode 100644
index 0000000000..fcf1dd0782
--- /dev/null
+++ b/yt/yt/client/complex_types/infinite_entity.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/pull_parser.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TInfiniteEntity
+{
+public:
+ TInfiniteEntity();
+
+ NYson::TYsonPullParserCursor* GetCursor();
+
+private:
+ class TRingBufferStream
+ : public IZeroCopyInput
+ {
+ public:
+ explicit TRingBufferStream(TStringBuf buffer);
+
+ private:
+ size_t DoNext(const void** ptr, size_t len) override;
+
+ private:
+ const TStringBuf Buffer_;
+ const char* Pointer_;
+ };
+
+private:
+ TRingBufferStream Stream_;
+ NYson::TYsonPullParser Parser_;
+ NYson::TYsonPullParserCursor Cursor_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes \ No newline at end of file
diff --git a/yt/yt/client/complex_types/public.h b/yt/yt/client/complex_types/public.h
new file mode 100644
index 0000000000..fa9173f00e
--- /dev/null
+++ b/yt/yt/client/complex_types/public.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/core/yson/public.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename... TArgs>
+using TComplexTypeYsonScanner = std::function<void(NYson::TYsonPullParserCursor*, TArgs...)>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes \ No newline at end of file
diff --git a/yt/yt/client/complex_types/scanner_factory.h b/yt/yt/client/complex_types/scanner_factory.h
new file mode 100644
index 0000000000..647a67855d
--- /dev/null
+++ b/yt/yt/client/complex_types/scanner_factory.h
@@ -0,0 +1,231 @@
+#pragma once
+
+#include "check_yson_token.h"
+#include "infinite_entity.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/core/yson/pull_parser.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Helper class to generate efficient scanners of complex values.
+template <typename... TArgs>
+class TScannerFactory
+{
+public:
+ using TScanner = TComplexTypeYsonScanner<TArgs...>;
+
+ template <typename TApplier, typename TElement>
+ static TScanner CreateOptionalScanner(
+ NTableClient::TComplexTypeFieldDescriptor descriptor,
+ TApplier applier,
+ TElement element)
+ {
+ using namespace NYson;
+ bool isElementNullable = descriptor.GetType()->AsOptionalTypeRef().IsElementNullable();
+
+ if (isElementNullable) {
+ return [
+ element=std::move(element),
+ descriptor=std::move(descriptor),
+ applier=std::move(applier)
+ ] (TYsonPullParserCursor* cursor, TArgs... args) {
+ const auto ysonType = (*cursor)->GetType();
+ if (ysonType == EYsonItemType::EntityValue) {
+ applier.OnEmptyOptional(args...);
+ cursor->Next();
+ } else if (ysonType != EYsonItemType::BeginList) {
+ ThrowUnexpectedYsonTokenException(descriptor, *cursor, {EYsonItemType::EntityValue, EYsonItemType::BeginList});
+ } else {
+ cursor->Next();
+ applier.OnFilledOptional(element, cursor, args...);
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ }
+ };
+ } else {
+ return [
+ element=std::move(element),
+ descriptor=std::move(descriptor),
+ applier=std::move(applier)
+ ] (TYsonPullParserCursor* cursor, TArgs... args) {
+ auto ysonType = (*cursor)->GetType();
+ if (ysonType == EYsonItemType::EntityValue) {
+ applier.OnEmptyOptional(args...);
+ cursor->Next();
+ } else {
+ applier.OnFilledOptional(element, cursor, args...);
+ }
+ };
+ }
+ }
+
+ template <typename TApplier, typename TElement>
+ static TScanner CreateListScanner(
+ NTableClient::TComplexTypeFieldDescriptor descriptor,
+ TApplier applier,
+ TElement element)
+ {
+ using namespace NYson;
+ return [
+ element=std::move(element),
+ descriptor=std::move(descriptor),
+ applier=std::move(applier)
+ ] (TYsonPullParserCursor* cursor, TArgs... args) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+
+ applier.OnListBegin(args...);
+ while ((*cursor)->GetType() != EYsonItemType::EndList) {
+ applier.OnListItem(element, cursor, args...);
+ }
+ applier.OnListEnd(args...);
+
+ // We are on list end token since we exited while loop
+ cursor->Next();
+ };
+ }
+
+ template <typename TApplier, typename TElement>
+ static TScanner CreateTupleScanner(
+ NTableClient::TComplexTypeFieldDescriptor descriptor,
+ TApplier applier,
+ std::vector<TElement> elements)
+ {
+ using namespace NYson;
+ return [
+ elements=std::move(elements),
+ descriptor=std::move(descriptor),
+ applier=std::move(applier)
+ ] (TYsonPullParserCursor* cursor, TArgs... args) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+
+ applier.OnTupleBegin(args...);
+ for (const auto& scanner : elements) {
+ applier.OnTupleItem(scanner, cursor, args...);
+ }
+ applier.OnTupleEnd(args...);
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ };
+ }
+
+ template <typename TApplier, typename TField>
+ static TScanner CreateStructScanner(
+ NTableClient::TComplexTypeFieldDescriptor descriptor,
+ TApplier applier,
+ std::vector<TField> fields)
+ {
+ using namespace NYson;
+ return [
+ fields=std::move(fields),
+ descriptor=std::move(descriptor),
+ applier=std::move(applier)
+ ] (TYsonPullParserCursor* cursor, TArgs... args) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+
+ applier.OnStructBegin(args...);
+ for (auto it = fields.begin(), end = fields.end(); it != end; ++it) {
+ if (cursor->GetCurrent().GetType() == EYsonItemType::EndList) {
+ TInfiniteEntity infiniteEntity;
+ auto entityCursor = infiniteEntity.GetCursor();
+ do {
+ applier.OnStructField(*it, entityCursor, args...);
+ ++it;
+ } while (it != end);
+ break;
+ }
+ applier.OnStructField(*it, cursor, args...);
+ }
+ applier.OnStructEnd(args...);
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ };
+ }
+
+ template <typename TApplier, typename TAlternatives>
+ static TScanner CreateVariantScanner(
+ NTableClient::TComplexTypeFieldDescriptor descriptor,
+ TApplier applier,
+ std::vector<TAlternatives> alternatives)
+ {
+ using namespace NYson;
+ return [
+ descriptor=std::move(descriptor),
+ applier=std::move(applier),
+ alternatives=std::move(alternatives)
+ ] (TYsonPullParserCursor* cursor, TArgs... args) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::Int64Value);
+ auto tag = cursor->GetCurrent().UncheckedAsInt64();
+ if (tag < 0) {
+ THROW_ERROR_EXCEPTION(
+ "Error while parsing %Qv: variant tag (%v) is negative",
+ descriptor.GetDescription(),
+ tag);
+ }
+ if (tag >= std::ssize(alternatives)) {
+ THROW_ERROR_EXCEPTION(
+ "Error while parsing %Qv: variant tag (%v) exceeds variant alternative count (%v)",
+ descriptor.GetDescription(),
+ tag,
+ alternatives.size());
+ }
+ cursor->Next();
+
+ applier.OnVariantAlternative(alternatives[tag], cursor, args...);
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ };
+ }
+
+ template <typename TApplier, typename TElement>
+ static TScanner CreateDictScanner(
+ NTableClient::TComplexTypeFieldDescriptor descriptor,
+ TApplier applier,
+ TElement key,
+ TElement value)
+ {
+ using namespace NYson;
+ return [
+ descriptor=std::move(descriptor),
+ applier=std::move(applier),
+ key=std::move(key),
+ value=std::move(value)
+ ] (TYsonPullParserCursor* cursor, TArgs... args) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+
+ applier.OnDictBegin(args...);
+ while ((*cursor)->GetType() != EYsonItemType::EndList) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+
+ applier.OnKey(key, cursor, args...);
+
+ applier.OnValue(value, cursor, args...);
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ }
+ applier.OnDictEnd(args...);
+
+ // Skip end list token
+ cursor->Next();
+ };
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT:NComplexTypes
diff --git a/yt/yt/client/complex_types/time_text.cpp b/yt/yt/client/complex_types/time_text.cpp
new file mode 100644
index 0000000000..6bff1d9571
--- /dev/null
+++ b/yt/yt/client/complex_types/time_text.cpp
@@ -0,0 +1,62 @@
+#include "time_text.h"
+
+namespace NYT::NComplexTypes {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void ValidateLength(TStringBuf data, ESimpleLogicalValueType valueType)
+{
+ size_t minLength;
+ size_t maxLength;
+ switch (valueType) {
+ case ESimpleLogicalValueType::Date:
+ minLength = maxLength = DateLength;
+ break;
+ case ESimpleLogicalValueType::Datetime:
+ minLength = maxLength = DateTimeLength;
+ break;
+ case ESimpleLogicalValueType::Timestamp:
+ minLength = DateTimeLength;
+ maxLength = TimestampLength;
+ break;
+ default:
+ YT_ABORT();
+ }
+ if (minLength > data.size() || data.size() > maxLength) {
+ THROW_ERROR_EXCEPTION(
+ "Invalid date string length. Expected: [%v..%v], got: %v",
+ minLength,
+ maxLength,
+ data.size());
+ }
+}
+
+ui64 BinaryTimeFromText(TStringBuf data, ESimpleLogicalValueType valueType)
+{
+ // ISO 8601 allows omitting dates' "suffix". E.g 2021-09, 2021-09-30, 2021-09-30T23:59 are valid ISO 8601 dates.
+ // So we have to validate date's length to make sure we respect ValueType_.
+ ValidateLength(data, valueType);
+ TInstant instant;
+ try {
+ instant = TInstant::ParseIso8601(data);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Could not parse date %Qv", data)
+ << ex;
+ }
+ switch (valueType) {
+ case ESimpleLogicalValueType::Date:
+ return instant.Days();
+ case ESimpleLogicalValueType::Datetime:
+ return instant.Seconds();
+ case ESimpleLogicalValueType::Timestamp:
+ return instant.MicroSeconds();
+ default:
+ YT_ABORT();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/time_text.h b/yt/yt/client/complex_types/time_text.h
new file mode 100644
index 0000000000..64073963a8
--- /dev/null
+++ b/yt/yt/client/complex_types/time_text.h
@@ -0,0 +1,15 @@
+#include <yt/yt/client/table_client/row_base.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr static auto DateLength = std::char_traits<char>::length("YYYY-MM-DD");
+constexpr static auto DateTimeLength = std::char_traits<char>::length("YYYY-MM-DDThh:mm:ssZ");
+constexpr static auto TimestampLength = std::char_traits<char>::length("YYYY-MM-DDThh:mm:ss.123456Z");
+
+ui64 BinaryTimeFromText(TStringBuf data, NTableClient::ESimpleLogicalValueType valueType);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/uuid_text.cpp b/yt/yt/client/complex_types/uuid_text.cpp
new file mode 100644
index 0000000000..02f0419932
--- /dev/null
+++ b/yt/yt/client/complex_types/uuid_text.cpp
@@ -0,0 +1,127 @@
+#include "uuid_text.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <util/system/byteorder.h>
+#include <util/system/unaligned_mem.h>
+
+#include <util/string/hex.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void CheckUuidSize(TStringBuf bytes)
+{
+ if (bytes.size() != UuidBinarySize) {
+ THROW_ERROR_EXCEPTION(
+ "Invalid binary UUID length: got %v, expected: %v",
+ bytes.size(),
+ UuidBinarySize);
+ }
+}
+
+void TextYqlUuidToBytes(TStringBuf uuid, char* ptr)
+{
+ // Opposite to TextYqlUuidFromBytes(). Check that function first.
+ if (uuid.size() != UuidYqlTextSize) {
+ THROW_ERROR_EXCEPTION(
+ "Invalid text YQL UUID length: got %v, expected: %v",
+ uuid.size(),
+ UuidYqlTextSize);
+ }
+ auto parseByte = [] (const char* ptr) {
+ try {
+ return Char2Digit(*ptr) * 16 + Char2Digit(*(ptr + 1));
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Could not parse hex byte") << ex;
+ }
+ };
+ auto verifyDash = [] (const char* ptr) {
+ if (*ptr != '-') {
+ THROW_ERROR_EXCEPTION("Unexpected character: actual %Qv, expected %Qv",
+ *ptr,
+ '-');
+ }
+ };
+
+ const char* input = uuid.Data();
+ for (int i = 3; i >= 0; --i) {
+ *ptr++ = parseByte(input + 2 * i);
+ }
+ verifyDash(input + 8);
+ for (int i = 5; i >= 4; --i) {
+ *ptr++ = parseByte(input + 1 + 2 * i);
+ }
+ verifyDash(input + 13);
+ for (int i = 7; i >= 6; --i) {
+ *ptr++ = parseByte(input + 2 + 2 * i);
+ }
+ verifyDash(input + 18);
+ for (int i = 8; i <= 9; ++i) {
+ *ptr++ = parseByte(input + 3 + 2 * i);
+ }
+ verifyDash(input + 23);
+ for (int i = 10; i < 16; ++i) {
+ *ptr++ = parseByte(input + 4 + 2 * i);
+ }
+}
+
+char* TextYqlUuidFromBytes(TStringBuf bytes, char* ptr)
+{
+ // Inspired by https://a.yandex-team.ru/arc/trunk/arcadia/yql/minikql/mkql_type_ops.cpp?rev=6853830#L327
+ CheckUuidSize(bytes);
+ static const char* HexDigits = "0123456789abcdef";
+
+ auto writeByte = [&ptr] (ui8 x) {
+ *ptr++ = HexDigits[x >> 4];
+ *ptr++ = HexDigits[x & 0b1111];
+ };
+
+ for (int i = 3; i >= 0; --i) {
+ writeByte(bytes[i]);
+ }
+ *ptr++ = '-';
+ for (int i = 5; i >= 4; --i) {
+ writeByte(bytes[i]);
+ }
+ *ptr++ = '-';
+ for (int i = 7; i >= 6; --i) {
+ writeByte(bytes[i]);
+ }
+ *ptr++ = '-';
+ for (int i = 8; i <= 9; ++i) {
+ writeByte(bytes[i]);
+ }
+ *ptr++ = '-';
+ for (int i = 10; i < 16; ++i) {
+ writeByte(bytes[i]);
+ }
+ return ptr;
+}
+
+void GuidToBytes(TGuid guid, char* ptr)
+{
+ auto low = LittleToHost(guid.Parts64[0]);
+ auto high = LittleToHost(guid.Parts64[1]);
+
+ WriteUnaligned<ui64>(ptr, HostToInet(high));
+ WriteUnaligned<ui64>(ptr + sizeof(ui64), HostToInet(low));
+}
+
+TGuid GuidFromBytes(TStringBuf bytes)
+{
+ // Doing the same way as
+ // https://a.yandex-team.ru/arc/trunk/arcadia/yt/yt/client/formats/skiff_yson_converter-inl.h?rev=r8579019#L124
+
+ CheckUuidSize(bytes);
+ const char* ptr = bytes.Data();
+ auto low = InetToHost(ReadUnaligned<ui64>(ptr + sizeof(ui64)));
+ auto high = InetToHost(ReadUnaligned<ui64>(ptr));
+
+ return TGuid(HostToLittle(low), HostToLittle(high));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/uuid_text.h b/yt/yt/client/complex_types/uuid_text.h
new file mode 100644
index 0000000000..55006737f2
--- /dev/null
+++ b/yt/yt/client/complex_types/uuid_text.h
@@ -0,0 +1,19 @@
+#include <yt/yt/core/misc/guid.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr size_t UuidYqlTextSize = 36;
+constexpr size_t UuidYtTextSize = 35;
+constexpr size_t UuidBinarySize = 16;
+
+void TextYqlUuidToBytes(TStringBuf uuid, char* ptr);
+char* TextYqlUuidFromBytes(TStringBuf bytes, char* ptr);
+
+void GuidToBytes(TGuid guid, char* ptr);
+TGuid GuidFromBytes(TStringBuf bytes);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/yson_format_conversion.cpp b/yt/yt/client/complex_types/yson_format_conversion.cpp
new file mode 100644
index 0000000000..3b50011e61
--- /dev/null
+++ b/yt/yt/client/complex_types/yson_format_conversion.cpp
@@ -0,0 +1,1267 @@
+#include "yson_format_conversion.h"
+
+#include "uuid_text.h"
+#include "time_text.h"
+#include "scanner_factory.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/unversioned_value.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/misc/collection_helpers.h>
+#include <yt/yt/core/misc/blob_output.h>
+#include <yt/yt/core/misc/guid.h>
+
+#include <yt/yt/core/yson/consumer.h>
+#include <yt/yt/core/yson/writer.h>
+
+#include <yt/yt/library/decimal/decimal.h>
+
+#include <util/generic/buffer.h>
+
+#include <util/stream/buffer.h>
+#include <util/stream/mem.h>
+
+#include <variant>
+
+namespace NYT::NComplexTypes {
+
+using namespace NTableClient;
+using namespace NYson;
+using namespace NFormats;
+using namespace NDecimal;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EConverterType,
+ (ToServer)
+ (ToClient)
+);
+
+struct TYsonConverterCreatorConfig
+{
+ TYsonConverterConfig Config;
+ EConverterType ConverterType;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsTimeType(const TLogicalTypePtr& type)
+{
+ if (type->GetMetatype() != ELogicalMetatype::Simple) {
+ return false;
+ }
+ switch (type->AsSimpleTypeRef().GetElement()) {
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool IsUuidType(const TLogicalTypePtr& type)
+{
+ return type->GetMetatype() == ELogicalMetatype::Simple &&
+ type->AsSimpleTypeRef().GetElement() == ESimpleLogicalValueType::Uuid;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TIsTransformForTypeNeededCache
+{
+public:
+ TIsTransformForTypeNeededCache(
+ const TLogicalTypePtr& logicalType,
+ const TYsonConverterCreatorConfig& config)
+ {
+ CheckAndCacheTriviality(logicalType, config);
+ }
+
+ bool IsTrivial(const TLogicalTypePtr& logicalType) const
+ {
+ return GetOrCrash(Cache_, logicalType.Get());
+ }
+
+private:
+ bool CheckAndCacheTriviality(const TLogicalTypePtr& logicalType, const TYsonConverterCreatorConfig& config)
+ {
+ auto& result = Cache_[logicalType.Get()];
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ if (IsTimeType(logicalType)) {
+ return result = (config.Config.TimeMode == ETimeMode::Binary);
+ } else if (IsUuidType(logicalType)) {
+ return result = (config.Config.UuidMode == EUuidMode::Binary);
+ } else {
+ return result = true;
+ }
+
+ case ELogicalMetatype::Decimal:
+ return result = (config.Config.DecimalMode == EDecimalMode::Binary);
+
+ case ELogicalMetatype::Optional:
+ case ELogicalMetatype::List:
+ case ELogicalMetatype::Tagged:
+ return result = CheckAndCacheTriviality(logicalType->GetElement(), config);
+
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::VariantTuple: {
+ result = true;
+ for (const auto& element : logicalType->GetElements()) {
+ if (!CheckAndCacheTriviality(element, config)) {
+ result = false;
+ // no break here, we want to cache all elements
+ }
+ }
+ return result;
+ }
+
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantStruct: {
+ result = (config.Config.ComplexTypeMode == EComplexTypeMode::Positional);
+ for (const auto& field : logicalType->GetFields()) {
+ if (!CheckAndCacheTriviality(field.Type, config)) {
+ result = false;
+ // no break here, we want to cache all elements
+ }
+ }
+ return result;
+ }
+
+ case ELogicalMetatype::Dict:
+ result = (config.Config.StringKeyedDictMode == EDictMode::Positional);
+ if (!CheckAndCacheTriviality(logicalType->AsDictTypeRef().GetKey(), config)) {
+ result = false;
+ }
+ if (!CheckAndCacheTriviality(logicalType->AsDictTypeRef().GetValue(), config)) {
+ result = false;
+ }
+ return result;
+ }
+ YT_ABORT();
+ }
+
+private:
+ THashMap<void*, bool> Cache_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void IdRecoder(TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+{
+ cursor->TransferComplexValue(consumer);
+}
+
+using TYsonCursorConverter = std::function<void(TYsonPullParserCursor*, IYsonConsumer*)>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CheckValueType(EValueType actualType, EValueType expectedType)
+{
+ if (actualType != expectedType) {
+ THROW_ERROR_EXCEPTION(
+ "Unexpected value type: actual %Qlv, expected %Qlv",
+ actualType,
+ expectedType);
+ }
+}
+
+void CheckYsonItemType(EYsonItemType actualType, EYsonItemType expectedType)
+{
+ if (actualType != expectedType) {
+ THROW_ERROR_EXCEPTION(
+ "Unexpected yson token: actual %Qlv, expected %Qlv",
+ actualType,
+ expectedType);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecimalRawServerToClientConverter
+{
+public:
+ TDecimalRawServerToClientConverter(int precision, int scale)
+ : Precision_(precision)
+ , Scale_(scale)
+ { }
+
+ void operator () (TUnversionedValue value, IYsonConsumer* consumer)
+ {
+ CheckValueType(value.Type, EValueType::String);
+ auto data = value.AsStringBuf();
+ auto converted = TDecimal::BinaryToText(data, Precision_, Scale_, Buffer_.data(), Buffer_.size());
+ consumer->OnStringScalar(converted);
+ }
+
+private:
+ const int Precision_;
+ const int Scale_;
+ std::array<char, TDecimal::MaxTextSize> Buffer_;
+};
+
+class TDecimalRawClientToServerConverter
+{
+public:
+ TDecimalRawClientToServerConverter(int precision, int scale)
+ : Precision_(precision)
+ , Scale_(scale)
+ { }
+
+ // This operator should be called only after previous result is consumed.
+ // So use-after-free won't occur.
+ TUnversionedValue operator () (TUnversionedValue value)
+ {
+ CheckValueType(value.Type, EValueType::String);
+ auto data = value.AsStringBuf();
+ auto converted = TDecimal::TextToBinary(data, Precision_, Scale_, Buffer_.data(), Buffer_.size());
+ return MakeUnversionedStringValue(converted);
+ }
+
+private:
+ const int Precision_;
+ const int Scale_;
+ std::array<char, TDecimal::MaxBinarySize> Buffer_;
+};
+
+template <EConverterType ConverterType>
+class TDecimalCursorConverter
+{
+public:
+ TDecimalCursorConverter(int precision, int scale)
+ : Precision_(precision)
+ , Scale_(scale)
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+ {
+ CheckYsonItemType((*cursor)->GetType(), EYsonItemType::StringValue);
+ auto data = (*cursor)->UncheckedAsString();
+ TStringBuf converted;
+ if constexpr (ConverterType == EConverterType::ToClient) {
+ converted = TDecimal::BinaryToText(data, Precision_, Scale_, Buffer_.data(), Buffer_.size());
+ } else {
+ static_assert(ConverterType == EConverterType::ToServer);
+ converted = TDecimal::TextToBinary(data, Precision_, Scale_, Buffer_.data(), Buffer_.size());
+ }
+ consumer->OnStringScalar(converted);
+ cursor->Next();
+ }
+
+private:
+ const int Precision_;
+ const int Scale_;
+
+ static_assert(ConverterType == EConverterType::ToClient || ConverterType == EConverterType::ToServer);
+ constexpr static auto Size_ = (ConverterType == EConverterType::ToClient)
+ ? TDecimal::MaxTextSize
+ : TDecimal::MaxBinarySize;
+ std::array<char, Size_> Buffer_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTimeServerToClientConverter
+{
+public:
+ TTimeServerToClientConverter(ESimpleLogicalValueType valueType)
+ : ConvertedWriter_(Converted_)
+ , ValueType_(valueType)
+ {
+ Converted_.reserve(TimestampLength);
+ }
+
+ TTimeServerToClientConverter(const TTimeServerToClientConverter& other)
+ : ConvertedWriter_(Converted_)
+ , ValueType_(other.ValueType_)
+ {
+ Converted_.reserve(TimestampLength);
+ }
+
+ void operator () (TUnversionedValue value, IYsonConsumer* consumer)
+ {
+ CheckValueType(value.Type, EValueType::Uint64);
+ Convert(value.Data.Uint64, consumer);
+ }
+
+ void operator () (TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+ {
+ CheckYsonItemType((*cursor)->GetType(), EYsonItemType::Uint64Value);
+ Convert((*cursor)->UncheckedAsUint64(), consumer);
+ cursor->Next();
+ }
+
+private:
+ void Convert(ui64 data, IYsonConsumer* consumer)
+ {
+ Converted_.clear();
+ switch (ValueType_) {
+ case ESimpleLogicalValueType::Date:
+ ConvertedWriter_ << TInstant::Days(data);
+ Converted_.resize(DateLength);
+ break;
+ case ESimpleLogicalValueType::Datetime:
+ ConvertedWriter_ << TInstant::Seconds(data);
+ Converted_.resize(DateTimeLength);
+ Converted_.back() = 'Z';
+ break;
+ case ESimpleLogicalValueType::Timestamp:
+ ConvertedWriter_ << TInstant::MicroSeconds(data);
+ break;
+ default:
+ YT_ABORT();
+ }
+ consumer->OnStringScalar(Converted_);
+ }
+
+private:
+ TString Converted_;
+ TStringOutput ConvertedWriter_;
+ const ESimpleLogicalValueType ValueType_;
+};
+
+class TTimeClientToServerConverter
+{
+public:
+ TTimeClientToServerConverter(ESimpleLogicalValueType valueType)
+ : ValueType_(valueType)
+ { }
+
+ TUnversionedValue operator () (TUnversionedValue value)
+ {
+ CheckValueType(value.Type, EValueType::String);
+ auto data = value.AsStringBuf();
+ return MakeUnversionedUint64Value(BinaryTimeFromText(data, ValueType_));
+ }
+
+ void operator () (TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+ {
+ CheckYsonItemType((*cursor)->GetType(), EYsonItemType::StringValue);
+ auto data = (*cursor)->UncheckedAsString();
+ consumer->OnUint64Scalar(BinaryTimeFromText(data, ValueType_));
+ cursor->Next();
+ }
+
+private:
+ const ESimpleLogicalValueType ValueType_;
+};
+
+std::variant<TYsonServerToClientConverter, TYsonClientToServerConverter> CreateDecimalRawConverter(
+ const TLogicalTypePtr& type,
+ const TYsonConverterCreatorConfig& config)
+{
+ YT_VERIFY(config.Config.DecimalMode != EDecimalMode::Binary);
+
+ const auto& decimalTypeRef = type->AsDecimalTypeRef();
+ auto precision = decimalTypeRef.GetPrecision();
+ auto scale = decimalTypeRef.GetScale();
+ switch (config.ConverterType) {
+ case EConverterType::ToClient:
+ return TDecimalRawServerToClientConverter(precision, scale);
+ case EConverterType::ToServer:
+ return TDecimalRawClientToServerConverter(precision, scale);
+ }
+ YT_ABORT();
+}
+
+TYsonCursorConverter CreateDecimalConverter(
+ const TYsonConverterCreatorConfig& config,
+ int precision,
+ int scale)
+{
+ YT_VERIFY(config.Config.DecimalMode != EDecimalMode::Binary);
+ switch (config.ConverterType) {
+ case EConverterType::ToClient:
+ return TDecimalCursorConverter<EConverterType::ToClient>(precision, scale);
+ case EConverterType::ToServer:
+ return TDecimalCursorConverter<EConverterType::ToServer>(precision, scale);
+ }
+ YT_ABORT();
+}
+
+std::variant<TYsonServerToClientConverter, TYsonClientToServerConverter> CreateTimeRawConverter(
+ const TLogicalTypePtr& type,
+ const TYsonConverterCreatorConfig& config)
+{
+ YT_VERIFY(config.Config.TimeMode != ETimeMode::Binary);
+ const auto& simpleType = type->AsSimpleTypeRef().GetElement();
+
+ switch (config.ConverterType) {
+ case EConverterType::ToClient:
+ return TTimeServerToClientConverter(simpleType);
+ case EConverterType::ToServer:
+ return TTimeClientToServerConverter(simpleType);
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUuidServerToClientConverter
+{
+public:
+ TUuidServerToClientConverter(EUuidMode uuidMode)
+ : UuidMode_(uuidMode)
+ { }
+
+ void operator () (TUnversionedValue value, IYsonConsumer* consumer)
+ {
+ CheckValueType(value.Type, EValueType::String);
+ Convert(value.AsStringBuf(), consumer);
+ }
+
+ void operator () (TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+ {
+ CheckYsonItemType((*cursor)->GetType(), EYsonItemType::StringValue);
+ Convert((*cursor)->UncheckedAsString(), consumer);
+ cursor->Next();
+ }
+
+private:
+ void Convert(TStringBuf data, IYsonConsumer* consumer)
+ {
+ char* end;
+ switch (UuidMode_) {
+ case EUuidMode::TextYql:
+ end = TextYqlUuidFromBytes(data, Buffer_.data());
+ break;
+ case EUuidMode::TextYt:
+ end = WriteGuidToBuffer(Buffer_.data(), GuidFromBytes(data));
+ break;
+ default:
+ // binary uuid should not be converted
+ YT_ABORT();
+ }
+ consumer->OnStringScalar(TStringBuf(Buffer_.data(), end));
+ }
+
+private:
+ const EUuidMode UuidMode_;
+ std::array<char, std::max(UuidYtTextSize, UuidYqlTextSize)> Buffer_;
+};
+
+class TUuidClientToServerConverter
+{
+public:
+ TUuidClientToServerConverter(EUuidMode uuidMode)
+ : UuidMode_(uuidMode)
+ { }
+
+ TUnversionedValue operator () (TUnversionedValue value)
+ {
+ CheckValueType(value.Type, EValueType::String);
+ auto data = value.AsStringBuf();
+ return MakeUnversionedStringValue(Convert(data));
+ }
+
+ void operator () (TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+ {
+ CheckYsonItemType((*cursor)->GetType(), EYsonItemType::StringValue);
+ auto data = (*cursor)->UncheckedAsString();
+ consumer->OnStringScalar(Convert(data));
+ cursor->Next();
+ }
+
+private:
+ TStringBuf Convert(TStringBuf data)
+ {
+ switch (UuidMode_) {
+ case EUuidMode::TextYql:
+ TextYqlUuidToBytes(data, Buffer_.data());
+ break;
+ case EUuidMode::TextYt:
+ GuidToBytes(TGuid::FromString(data), Buffer_.data());
+ break;
+ default:
+ // binary uuid should not be converted
+ YT_ABORT();
+ }
+ return TStringBuf(Buffer_.data(), Buffer_.size());
+ }
+
+private:
+ const EUuidMode UuidMode_;
+ std::array<char, UuidBinarySize> Buffer_;
+};
+
+std::variant<TYsonServerToClientConverter, TYsonClientToServerConverter> CreateUuidRawConverter(
+ const TYsonConverterCreatorConfig& config)
+{
+ YT_VERIFY(config.Config.UuidMode != EUuidMode::Binary);
+
+ switch (config.ConverterType) {
+ case EConverterType::ToClient:
+ return TUuidServerToClientConverter(config.Config.UuidMode);
+ case EConverterType::ToServer:
+ return TUuidClientToServerConverter(config.Config.UuidMode);
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TStructFieldInfo
+{
+ TYsonCursorConverter Converter;
+ TString FieldName;
+ bool IsNullable = false;
+};
+
+
+template <bool IsElementNullable>
+class TOptionalHandler
+{
+public:
+ void OnEmptyOptional(IYsonConsumer* consumer) const
+ {
+ consumer->OnEntity();
+ }
+
+ void OnFilledOptional(const TYsonCursorConverter& recoder, TYsonPullParserCursor* cursor, IYsonConsumer* consumer) const
+ {
+ if constexpr (IsElementNullable) {
+ consumer->OnBeginList();
+ consumer->OnListItem();
+ recoder(cursor, consumer);
+ consumer->OnEndList();
+ } else {
+ recoder(cursor, consumer);
+ }
+ }
+};
+
+class TListHandler
+{
+public:
+ Y_FORCE_INLINE void OnListBegin(IYsonConsumer* consumer) const
+ {
+ consumer->OnBeginList();
+ }
+
+ Y_FORCE_INLINE void OnListItem(
+ const TYsonCursorConverter& recoder,
+ TYsonPullParserCursor* cursor,
+ IYsonConsumer* consumer) const
+ {
+ consumer->OnListItem();
+ recoder(cursor, consumer);
+ }
+
+ Y_FORCE_INLINE void OnListEnd(IYsonConsumer* consumer) const
+ {
+ consumer->OnEndList();
+ }
+};
+
+class TTupleApplier
+{
+public:
+ Y_FORCE_INLINE void OnTupleBegin(IYsonConsumer* consumer) const
+ {
+ consumer->OnBeginList();
+ }
+
+ Y_FORCE_INLINE void
+ OnTupleItem(const TYsonCursorConverter& recoder, TYsonPullParserCursor* cursor, IYsonConsumer* consumer) const
+ {
+ consumer->OnListItem();
+ recoder(cursor, consumer);
+ }
+
+ Y_FORCE_INLINE void OnTupleEnd(IYsonConsumer* consumer) const
+ {
+ consumer->OnEndList();
+ }
+};
+
+template <bool SkipNullValues>
+class TStructApplier
+{
+public:
+ Y_FORCE_INLINE void OnStructBegin(IYsonConsumer* consumer) const
+ {
+ consumer->OnBeginMap();
+ }
+
+ Y_FORCE_INLINE void OnStructEnd(IYsonConsumer* consumer) const
+ {
+ consumer->OnEndMap();
+ }
+
+ Y_FORCE_INLINE void OnStructField(
+ const TStructFieldInfo& field,
+ TYsonPullParserCursor* cursor,
+ IYsonConsumer* consumer) const
+ {
+ if constexpr (SkipNullValues) {
+ if (field.IsNullable && (*cursor)->GetType() == EYsonItemType::EntityValue) {
+ cursor->Next();
+ return;
+ }
+ }
+ consumer->OnKeyedItem(field.FieldName);
+ field.Converter(cursor, consumer);
+ }
+};
+
+class TVariantTupleApplier
+{
+public:
+ Y_FORCE_INLINE void OnVariantAlternative(
+ const std::pair<int, TYsonCursorConverter>& alternative,
+ TYsonPullParserCursor* cursor,
+ IYsonConsumer* consumer) const
+ {
+ consumer->OnBeginList();
+
+ consumer->OnListItem();
+ consumer->OnInt64Scalar(alternative.first);
+
+ consumer->OnListItem();
+ alternative.second(cursor, consumer);
+
+ consumer->OnEndList();
+ }
+};
+
+class TVariantStructApplier
+{
+public:
+ Y_FORCE_INLINE void OnVariantAlternative(
+ const std::pair<TString, TYsonCursorConverter>& alternative,
+ TYsonPullParserCursor* cursor,
+ IYsonConsumer* consumer) const
+ {
+ consumer->OnBeginList();
+ consumer->OnListItem();
+
+ consumer->OnStringScalar(alternative.first);
+
+ consumer->OnListItem();
+ alternative.second(cursor, consumer);
+
+ consumer->OnEndList();
+ }
+};
+
+template <EDictMode mode>
+class TDictApplier
+{
+public:
+ Y_FORCE_INLINE void OnDictBegin(IYsonConsumer* consumer) const
+ {
+ if constexpr (mode == EDictMode::Positional) {
+ consumer->OnBeginList();
+ } else if constexpr (mode == EDictMode::Named) {
+ consumer->OnBeginMap();
+ } else {
+ // Not compilable.
+ static_assert(mode == EDictMode::Positional);
+ }
+ }
+
+ Y_FORCE_INLINE void
+ OnKey(const TYsonCursorConverter& keyRecoder, TYsonPullParserCursor* cursor, IYsonConsumer* consumer) const
+ {
+ if constexpr (mode == EDictMode::Positional) {
+ consumer->OnListItem();
+ consumer->OnBeginList();
+ consumer->OnListItem();
+ keyRecoder(cursor, consumer);
+ } else if constexpr (mode == EDictMode::Named) {
+ const auto& item = *cursor;
+
+ // Named representation of dict supported only for string keys.
+ YT_ASSERT(item->GetType() == EYsonItemType::StringValue);
+
+ consumer->OnKeyedItem(item->UncheckedAsString());
+ cursor->Next();
+ } else {
+ // Not compilable.
+ static_assert(mode == EDictMode::Positional);
+ }
+ }
+
+ Y_FORCE_INLINE void
+ OnValue(const TYsonCursorConverter& valueRecoder, TYsonPullParserCursor* cursor, IYsonConsumer* consumer) const
+ {
+ if constexpr (mode == EDictMode::Positional) {
+ consumer->OnListItem();
+ valueRecoder(cursor, consumer);
+ consumer->OnEndList();
+ } if constexpr (mode == EDictMode::Named) {
+ valueRecoder(cursor, consumer);
+ } else {
+ // Not compilable.
+ static_assert(mode == EDictMode::Positional);
+ }
+ }
+
+ Y_FORCE_INLINE void OnDictEnd(IYsonConsumer* consumer) const
+ {
+ if constexpr (mode == EDictMode::Positional) {
+ consumer->OnEndList();
+ } if constexpr (mode == EDictMode::Named) {
+ consumer->OnEndMap();
+ } else {
+ // Not compilable.
+ static_assert(mode == EDictMode::Positional);
+ }
+ }
+};
+
+class TNamedToPositionalDictConverter
+{
+public:
+ TNamedToPositionalDictConverter(TComplexTypeFieldDescriptor descriptor, TYsonCursorConverter valueConverter)
+ : Descriptor_(std::move(descriptor))
+ , ValueConverter_(std::move(valueConverter))
+ {
+ }
+
+ void operator () (TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+ {
+ EnsureYsonToken(Descriptor_, *cursor, EYsonItemType::BeginMap);
+ cursor->Next();
+
+ consumer->OnBeginList();
+ while ((*cursor)->GetType() != EYsonItemType::EndMap) {
+ EnsureYsonToken(Descriptor_, *cursor, EYsonItemType::StringValue);
+ auto key = (*cursor)->UncheckedAsString();
+
+ consumer->OnListItem();
+ consumer->OnBeginList();
+ consumer->OnListItem();
+ consumer->OnStringScalar(key);
+
+ cursor->Next();
+
+ consumer->OnListItem();
+ ValueConverter_(cursor, consumer);
+ consumer->OnEndList();
+
+ }
+
+ // Skip map end token.
+ cursor->Next();
+ consumer->OnEndList();
+ }
+
+private:
+ TComplexTypeFieldDescriptor Descriptor_;
+ TYsonCursorConverter ValueConverter_;
+};
+
+class TNamedToPositionalStructConverter
+{
+public:
+ TNamedToPositionalStructConverter(TComplexTypeFieldDescriptor descriptor, std::vector<TStructFieldInfo> fields)
+ : BufferOutput_(Buffer_)
+ , YsonWriter_(&BufferOutput_, EYsonType::ListFragment)
+ , Descriptor_(std::move(descriptor))
+ {
+ PositionTable_.reserve(fields.size());
+ for (int i = 0; i < std::ssize(fields); ++i) {
+ auto& field = fields[i];
+ FieldMap_.emplace(
+ field.FieldName,
+ TFieldMapEntry{std::move(field.Converter), i});
+
+ PositionTable_.emplace_back();
+ PositionTable_.back().IsNullable = field.IsNullable;
+ PositionTable_.back().FieldName = std::move(field.FieldName);
+ }
+ }
+
+ // NB. to wrap this object into std::function we must be able to copy it.
+ TNamedToPositionalStructConverter(const TNamedToPositionalStructConverter& other)
+ : FieldMap_(other.FieldMap_)
+ , PositionTable_(other.PositionTable_)
+ , Buffer_(other.Buffer_)
+ , BufferOutput_(Buffer_)
+ , YsonWriter_(&BufferOutput_, EYsonType::ListFragment)
+ , Descriptor_(other.Descriptor_)
+ , CurrentGeneration_(other.CurrentGeneration_)
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, IYsonConsumer* consumer)
+ {
+ IncrementGeneration();
+
+ EnsureYsonToken(Descriptor_, *cursor, EYsonItemType::BeginMap);
+ cursor->Next();
+ Buffer_.Clear();
+
+ YT_ASSERT(YsonWriter_.GetDepth() == 0);
+
+ while ((*cursor)->GetType() != EYsonItemType::EndMap) {
+ EnsureYsonToken(Descriptor_, *cursor, EYsonItemType::StringValue);
+ auto fieldName = (*cursor)->UncheckedAsString();
+ auto it = FieldMap_.find(fieldName);
+ if (it == FieldMap_.end()) {
+ THROW_ERROR_EXCEPTION(
+ "Unknown field %Qv while parsing %Qv",
+ fieldName,
+ Descriptor_.GetDescription());
+ }
+ cursor->Next();
+
+ auto& positionEntry = PositionTable_[it->second.Position];
+ if (positionEntry.Generation == CurrentGeneration_) {
+ THROW_ERROR_EXCEPTION(
+ "Multiple occurrences of field %Qv while parsing %Qv",
+ it->first, // NB. it's not safe to use fieldName since we moved cursor
+ Descriptor_.GetDescription());
+ }
+
+ auto offset = Buffer_.Size();
+ it->second.Converter(cursor, &YsonWriter_);
+ YsonWriter_.Flush();
+
+ positionEntry.Offset = offset;
+ positionEntry.Size = Buffer_.size() - offset;
+ positionEntry.Generation = CurrentGeneration_;
+ }
+
+ // Skip map end token.
+ cursor->Next();
+
+ consumer->OnBeginList();
+ for (const auto& positionEntry : PositionTable_) {
+ if (positionEntry.Generation == CurrentGeneration_) {
+ auto yson = TStringBuf(Buffer_.Data() + positionEntry.Offset, positionEntry.Size);
+ consumer->OnRaw(yson, EYsonType::ListFragment);
+ } else if (positionEntry.IsNullable) {
+ consumer->OnRaw("#;", EYsonType::ListFragment);
+ } else {
+ THROW_ERROR_EXCEPTION("Field %Qv is missing while parsing %Qv",
+ positionEntry.FieldName,
+ Descriptor_.GetDescription());
+ }
+ }
+ consumer->OnEndList();
+ }
+private:
+ void IncrementGeneration()
+ {
+ if (++CurrentGeneration_ == 0) {
+ for (auto& entry : PositionTable_) {
+ entry.Generation = 0;
+ }
+ CurrentGeneration_ = 1;
+ }
+ }
+
+private:
+ struct TFieldMapEntry
+ {
+ TYsonCursorConverter Converter;
+ int Position = 0;
+ };
+
+ struct TPositionTableEntry {
+ size_t Offset = 0;
+ size_t Size = 0;
+ ui16 Generation = 0;
+ bool IsNullable = false;
+ TString FieldName;
+ };
+
+ THashMap<TString, TFieldMapEntry> FieldMap_;
+ std::vector<TPositionTableEntry> PositionTable_;
+ TBuffer Buffer_;
+ TBufferOutput BufferOutput_;
+ TBufferedBinaryYsonWriter YsonWriter_;
+ TComplexTypeFieldDescriptor Descriptor_;
+ ui16 CurrentGeneration_ = 0;
+};
+
+class TClientToServerComplexValueConverterWrapper
+{
+public:
+ TClientToServerComplexValueConverterWrapper(TYsonCursorConverter converter)
+ : Converter_(std::move(converter))
+ , ConvertedWriter_(&ConvertedBuffer_)
+ { }
+
+ TClientToServerComplexValueConverterWrapper(const TClientToServerComplexValueConverterWrapper& other)
+ : Converter_(other.Converter_)
+ , ConvertedWriter_(&ConvertedBuffer_)
+ { }
+
+ // This operator should be called only after previous result is consumed.
+ // So use-after-free won't occur.
+ TUnversionedValue operator () (TUnversionedValue value)
+ {
+ TMemoryInput in(value.Data.String, value.Length);
+ TYsonPullParser parser(&in, EYsonType::Node);
+ TYsonPullParserCursor cursor(&parser);
+
+ ConvertedBuffer_.Clear();
+ Converter_(&cursor, &ConvertedWriter_);
+ ConvertedWriter_.Flush();
+ return MakeUnversionedCompositeValue(ConvertedBuffer_.Blob().ToStringBuf());
+ }
+
+private:
+ TYsonCursorConverter Converter_;
+ TBlobOutput ConvertedBuffer_;
+ TBufferedBinaryYsonWriter ConvertedWriter_;
+};
+
+TYsonCursorConverter CreateNamedToPositionalVariantStructConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ std::vector<std::pair<TString, TYsonCursorConverter>> fieldConverters)
+{
+ THashMap<TString, std::pair<int, TYsonCursorConverter>> typeMap;
+ int fieldIndex = 0;
+ for (auto& [fieldName, converter] : fieldConverters) {
+ typeMap.emplace(std::move(fieldName), std::pair(fieldIndex, std::move(converter)));
+ ++fieldIndex;
+ }
+
+ return [
+ descriptor=descriptor,
+ typeMap=std::move(typeMap)
+ ] (TYsonPullParserCursor* cursor, IYsonConsumer* ysonConsumer) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::StringValue);
+ auto fieldName = (*cursor)->UncheckedAsString();
+ auto it = typeMap.find(fieldName);
+ if (it == typeMap.end()) {
+ THROW_ERROR_EXCEPTION(
+ "Unknown variant field %Qv while parsing %Qv",
+ fieldName,
+ descriptor.GetDescription());
+ }
+ cursor->Next();
+
+ const auto& [variantIndex, converter] = it->second;
+ ysonConsumer->OnBeginList();
+
+ ysonConsumer->OnListItem();
+ ysonConsumer->OnInt64Scalar(variantIndex);
+
+ ysonConsumer->OnListItem();
+ converter(cursor, ysonConsumer);
+
+ ysonConsumer->OnEndList();
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ };
+}
+
+TYsonCursorConverter CreateStructFieldsConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ std::vector<TStructFieldInfo> fieldInfos,
+ const TYsonConverterCreatorConfig& config)
+{
+ YT_VERIFY(config.Config.ComplexTypeMode == EComplexTypeMode::Positional);
+ return [
+ descriptor=descriptor,
+ fieldInfos=std::move(fieldInfos)
+ ] (TYsonPullParserCursor* cursor, IYsonConsumer* ysonConsumer) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+ ysonConsumer->OnBeginList();
+
+ for (const auto& fieldInfo : fieldInfos) {
+ if ((*cursor)->GetType() == EYsonItemType::EndList) {
+ break;
+ }
+ ysonConsumer->OnListItem();
+ fieldInfo.Converter(cursor, ysonConsumer);
+ }
+ ysonConsumer->OnEndList();
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ };
+}
+
+TYsonCursorConverter CreateVariantStructFieldsConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ std::vector<std::pair<TString, TYsonCursorConverter>> elementConverters,
+ const TYsonConverterCreatorConfig& config)
+{
+ YT_VERIFY(config.Config.ComplexTypeMode == EComplexTypeMode::Positional);
+ return [
+ descriptor=descriptor,
+ alternatives=std::move(elementConverters)
+ ] (TYsonPullParserCursor* cursor, IYsonConsumer* ysonConsumer) {
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::BeginList);
+ cursor->Next();
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::Int64Value);
+ auto tag = cursor->GetCurrent().UncheckedAsInt64();
+ if (tag < 0 || tag >= std::ssize(alternatives)) {
+ THROW_ERROR_EXCEPTION(
+ "Error while parsing %Qv: variant tag (%v) is out of range [0, %v)",
+ descriptor.GetDescription(),
+ tag,
+ alternatives.size());
+ }
+ ysonConsumer->OnBeginList();
+
+ ysonConsumer->OnListItem();
+ ysonConsumer->OnInt64Scalar(tag);
+
+ cursor->Next();
+ ysonConsumer->OnListItem();
+ alternatives[tag].second(cursor, ysonConsumer);
+
+ ysonConsumer->OnEndList();
+
+ EnsureYsonToken(descriptor, *cursor, EYsonItemType::EndList);
+ cursor->Next();
+ };
+}
+
+using TYsonConsumerScannerFactory = TScannerFactory<IYsonConsumer*>;
+
+TYsonCursorConverter CreateYsonConverterImpl(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const TIsTransformForTypeNeededCache& cache,
+ const TYsonConverterCreatorConfig& config)
+{
+ const auto& type = descriptor.GetType();
+ if (cache.IsTrivial(type)) {
+ return IdRecoder;
+ }
+ switch (type->GetMetatype()) {
+ case ELogicalMetatype::Simple: {
+ const auto& simpleValueType = type->AsSimpleTypeRef().GetElement();
+ if (IsTimeType(type)) {
+ switch (config.ConverterType) {
+ case EConverterType::ToClient:
+ return TTimeServerToClientConverter(simpleValueType);
+ case EConverterType::ToServer:
+ return TTimeClientToServerConverter(simpleValueType);
+ }
+ YT_ABORT();
+ }
+ if (IsUuidType(type)) {
+ switch (config.ConverterType) {
+ case EConverterType::ToClient:
+ return TUuidServerToClientConverter(config.Config.UuidMode);
+ case EConverterType::ToServer:
+ return TUuidClientToServerConverter(config.Config.UuidMode);
+ }
+ YT_ABORT();
+ }
+ [[fallthrough]]; // AUTOGENERATED_FALLTHROUGH_FIXME
+ }
+ case ELogicalMetatype::Decimal: {
+ const auto& decimalTypeRef = type->AsDecimalTypeRef();
+ auto precision = decimalTypeRef.GetPrecision();
+ auto scale = decimalTypeRef.GetScale();
+ return CreateDecimalConverter(config, precision, scale);
+ }
+ case ELogicalMetatype::Optional: {
+ auto elementConverter = CreateYsonConverterImpl(descriptor.OptionalElement(), cache, config);
+ if (type->AsOptionalTypeRef().IsElementNullable()) {
+ return TYsonConsumerScannerFactory::CreateOptionalScanner(
+ descriptor,
+ TOptionalHandler<true>(),
+ elementConverter);
+ } else {
+ return TYsonConsumerScannerFactory::CreateOptionalScanner(
+ descriptor,
+ TOptionalHandler<false>(),
+ elementConverter);
+ };
+ }
+ case ELogicalMetatype::List: {
+ auto elementConverter = CreateYsonConverterImpl(descriptor.ListElement(), cache, config);
+ return TYsonConsumerScannerFactory::CreateListScanner(descriptor, TListHandler(), elementConverter);
+ }
+ case ELogicalMetatype::Tuple: {
+ std::vector<TYsonCursorConverter> elementConverters;
+ const auto size = type->GetElements().size();
+ for (size_t i = 0; i != size; ++i) {
+ elementConverters.push_back(CreateYsonConverterImpl(descriptor.TupleElement(i), cache, config));
+ }
+ return TYsonConsumerScannerFactory::CreateTupleScanner(
+ descriptor, TTupleApplier(), std::move(elementConverters));
+ }
+ case ELogicalMetatype::Struct: {
+ const auto& fields = type->GetFields();
+ std::vector<TStructFieldInfo> fieldInfos;
+ for (size_t i = 0; i != fields.size(); ++i) {
+ fieldInfos.emplace_back();
+ fieldInfos.back().FieldName = fields[i].Name;
+ fieldInfos.back().Converter = CreateYsonConverterImpl(descriptor.StructField(i), cache, config);
+ fieldInfos.back().IsNullable = fields[i].Type->IsNullable();
+ }
+ if (config.Config.ComplexTypeMode == EComplexTypeMode::Positional) {
+ return CreateStructFieldsConverter(descriptor, fieldInfos, config);
+ } else {
+ YT_VERIFY(config.Config.ComplexTypeMode == EComplexTypeMode::Named);
+ if (config.ConverterType == EConverterType::ToServer) {
+ return TNamedToPositionalStructConverter(descriptor, std::move(fieldInfos));
+ } else {
+ YT_VERIFY(config.ConverterType == EConverterType::ToClient);
+ if (config.Config.SkipNullValues) {
+ return TYsonConsumerScannerFactory::CreateStructScanner(
+ descriptor, TStructApplier<true>(), std::move(fieldInfos));
+ } else {
+ return TYsonConsumerScannerFactory::CreateStructScanner(
+ descriptor, TStructApplier<false>(), std::move(fieldInfos));
+ }
+ }
+ }
+ }
+ case ELogicalMetatype::VariantTuple: {
+ std::vector<std::pair<int,TYsonCursorConverter>> elementConverters;
+ const auto size = type->GetElements().size();
+ for (size_t i = 0; i != size; ++i) {
+ elementConverters.emplace_back(i, CreateYsonConverterImpl(descriptor.VariantTupleElement(i), cache, config));
+ }
+ return TYsonConsumerScannerFactory::CreateVariantScanner(
+ descriptor, TVariantTupleApplier(), std::move(elementConverters));
+ }
+ case ELogicalMetatype::VariantStruct: {
+ std::vector<std::pair<TString, TYsonCursorConverter>> elementConverters;
+ const auto& fields = type->GetFields();
+ for (size_t i = 0; i != fields.size(); ++i) {
+ elementConverters.emplace_back(
+ fields[i].Name,
+ CreateYsonConverterImpl(descriptor.VariantStructField(i), cache, config));
+ }
+ if (config.Config.ComplexTypeMode == EComplexTypeMode::Positional) {
+ return CreateVariantStructFieldsConverter(descriptor, std::move(elementConverters), config);
+ } else {
+ YT_VERIFY(config.Config.ComplexTypeMode == EComplexTypeMode::Named);
+ if (config.ConverterType == EConverterType::ToServer) {
+ return CreateNamedToPositionalVariantStructConverter(descriptor, std::move(elementConverters));
+ } else {
+ YT_VERIFY(config.ConverterType == EConverterType::ToClient);
+ return TYsonConsumerScannerFactory::CreateVariantScanner(
+ descriptor, TVariantStructApplier(), std::move(elementConverters));
+ }
+ }
+ }
+ case ELogicalMetatype::Dict: {
+ auto keyConverter = CreateYsonConverterImpl(descriptor.DictKey(), cache, config);
+ auto valueConverter = CreateYsonConverterImpl(descriptor.DictValue(), cache, config);
+
+ if (config.Config.StringKeyedDictMode == EDictMode::Named) {
+ auto keyType = descriptor.DictKey().GetType();
+ if (keyType->GetMetatype() == ELogicalMetatype::Simple
+ && keyType->AsSimpleTypeRef().GetElement() == ESimpleLogicalValueType::String) {
+ if (config.ConverterType == EConverterType::ToClient) {
+ return TYsonConsumerScannerFactory::CreateDictScanner(
+ descriptor,
+ TDictApplier<EDictMode::Named>(),
+ keyConverter,
+ valueConverter);
+ } else {
+ YT_VERIFY(config.ConverterType == EConverterType::ToServer);
+ return TNamedToPositionalDictConverter(descriptor, std::move(valueConverter));
+ }
+ }
+ }
+
+ return TYsonConsumerScannerFactory::CreateDictScanner(
+ descriptor,
+ TDictApplier<EDictMode::Positional>(),
+ keyConverter,
+ valueConverter);
+ }
+ case ELogicalMetatype::Tagged:
+ return CreateYsonConverterImpl(descriptor.TaggedElement(), cache, config);
+ }
+ YT_ABORT();
+}
+
+std::variant<TYsonServerToClientConverter, TYsonClientToServerConverter> CreateYsonConverterForConfig(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const TIsTransformForTypeNeededCache& cache,
+ const TYsonConverterCreatorConfig& config)
+{
+ const auto& type = descriptor.GetType();
+ const auto metatype = type->GetMetatype();
+
+ if (metatype == ELogicalMetatype::Decimal) {
+ return CreateDecimalRawConverter(type, config);
+ }
+ if (metatype == ELogicalMetatype::Optional && type->GetElement()->GetMetatype() == ELogicalMetatype::Decimal) {
+ return CreateDecimalRawConverter(type->GetElement(), config);
+ }
+
+ if (IsTimeType(type)) {
+ return CreateTimeRawConverter(type, config);
+ }
+ if (metatype == ELogicalMetatype::Optional && IsTimeType(type->GetElement())) {
+ return CreateTimeRawConverter(type->GetElement(), config);
+ }
+
+ if (IsUuidType(type)) {
+ return CreateUuidRawConverter(config);
+ }
+ if (metatype == ELogicalMetatype::Optional && IsUuidType(type->GetElement())) {
+ return CreateUuidRawConverter(config);
+ }
+
+ auto converter = CreateYsonConverterImpl(descriptor, cache, config);
+ if (config.ConverterType == EConverterType::ToClient) {
+ return [
+ converter=std::move(converter)
+ ] (TUnversionedValue value, IYsonConsumer* consumer) {
+ TMemoryInput in(value.Data.String, value.Length);
+ TYsonPullParser parser(&in, EYsonType::Node);
+ TYsonPullParserCursor cursor(&parser);
+ converter(&cursor, consumer);
+ };
+ } else {
+ YT_VERIFY(config.ConverterType == EConverterType::ToServer);
+ return TClientToServerComplexValueConverterWrapper(std::move(converter));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYsonServerToClientConverter CreateYsonServerToClientConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const TYsonConverterConfig& config)
+{
+ TYsonConverterCreatorConfig creatorConfig{config, EConverterType::ToClient};
+ TIsTransformForTypeNeededCache cache(descriptor.GetType(), creatorConfig);
+ if (cache.IsTrivial(descriptor.GetType())) {
+ return {};
+ }
+
+ auto converterVariant = CreateYsonConverterForConfig(descriptor, cache, creatorConfig);
+ YT_VERIFY(std::holds_alternative<TYsonServerToClientConverter>(converterVariant));
+ return std::get<TYsonServerToClientConverter>(converterVariant);
+}
+
+TYsonClientToServerConverter CreateYsonClientToServerConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const TYsonConverterConfig& config)
+{
+ TYsonConverterCreatorConfig creatorConfig{config, EConverterType::ToServer};
+ TIsTransformForTypeNeededCache cache(descriptor.GetType(), creatorConfig);
+ if (cache.IsTrivial(descriptor.GetType())) {
+ return {};
+ }
+
+ auto converterVariant = CreateYsonConverterForConfig(descriptor, cache, creatorConfig);
+ YT_VERIFY(std::holds_alternative<TYsonClientToServerConverter>(converterVariant));
+ return std::get<TYsonClientToServerConverter>(converterVariant);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/complex_types/yson_format_conversion.h b/yt/yt/client/complex_types/yson_format_conversion.h
new file mode 100644
index 0000000000..7bac5cc6a0
--- /dev/null
+++ b/yt/yt/client/complex_types/yson_format_conversion.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/client/formats/public.h>
+
+#include <yt/yt/core/yson/pull_parser.h>
+
+namespace NYT::NComplexTypes {
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// There are two modes of representing complex types in yson: named and positional.
+// They are similar for all types but Struct and VariantStruct.
+//
+// Consider Struct({{"field0", type0}, {"field1", type1}, ...})
+// Its positional representation might be:
+// [v0; v1; ...] (v0 is of type0, v1 is of type1)
+//
+// Named representation of the same structure:
+// {field0=v0; field1=v1}
+//
+//
+// Consider Struct({{"field0", type0}, {"field1", type1}, ...})
+// Its positional representation might be:
+// [1, v1]
+//
+// Named representation of the same structure:
+// [field1; v1]
+//
+// Functions in this file create convertors between these two representations.
+
+using TYsonClientToServerConverter = std::function<NTableClient::TUnversionedValue(NTableClient::TUnversionedValue value)>;
+using TYsonServerToClientConverter = std::function<void(NTableClient::TUnversionedValue value, NYson::IYsonConsumer* consumer)>;
+
+struct TYsonConverterConfig
+{
+ NFormats::EComplexTypeMode ComplexTypeMode = NFormats::EComplexTypeMode::Named;
+ NFormats::EDictMode StringKeyedDictMode = NFormats::EDictMode::Positional;
+ NFormats::EDecimalMode DecimalMode = NFormats::EDecimalMode::Binary;
+ NFormats::ETimeMode TimeMode = NFormats::ETimeMode::Binary;
+ NFormats::EUuidMode UuidMode = NFormats::EUuidMode::Binary;
+
+ // When SkipNullValues is true converters doesn't write
+ // structure fields that have `#` value (i.e. they are Null).
+ bool SkipNullValues = false;
+};
+
+TYsonServerToClientConverter CreateYsonServerToClientConverter(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const TYsonConverterConfig& config);
+
+TYsonClientToServerConverter CreateYsonClientToServerConverter(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const TYsonConverterConfig& config);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/converters/boolean_converter.cpp b/yt/yt/client/converters/boolean_converter.cpp
new file mode 100644
index 0000000000..05dd7ff3d7
--- /dev/null
+++ b/yt/yt/client/converters/boolean_converter.cpp
@@ -0,0 +1,101 @@
+#include "boolean_converter.h"
+#include "helper.h"
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+void FillColumnarBooleanValues(
+ TBatchColumn* column,
+ i64 startIndex,
+ i64 valueCount,
+ TRef bitmap)
+{
+ column->StartIndex = startIndex;
+ column->ValueCount = valueCount;
+
+ auto& values = column->Values.emplace();
+ values.BitWidth = 1;
+ values.Data = bitmap;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBooleanColumnConverter
+ : public IColumnConverter
+{
+public:
+ TBooleanColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema)
+ : ColumnIndex_(columnIndex)
+ , ColumnSchema_(columnSchema)
+ { }
+
+ TConvertedColumn Convert(TRange<NTableClient::TUnversionedRow> rows) override
+ {
+ Reset();
+ AddValues(rows);
+
+ auto column = std::make_shared<TBatchColumn>();
+ auto nullBitmapRef = NullBitmap_.Flush<TConverterTag>();
+ auto valuesRef = Values_.Flush<TConverterTag>();
+
+ FillColumnarBooleanValues(column.get(), 0, rows.size(), valuesRef);
+ FillColumnarNullBitmap(column.get(), 0, rows.size(), nullBitmapRef);
+
+ column->Type = ColumnSchema_.LogicalType();
+ column->Id = ColumnIndex_;
+
+ TOwningColumn owner = {
+ std::move(column),
+ std::move(nullBitmapRef),
+ std::move(valuesRef),
+ /*stringBuffer*/ std::nullopt
+ };
+
+ return {{owner}, owner.Column.get()};
+ }
+
+
+private:
+ const int ColumnIndex_;
+ NTableClient::TColumnSchema ColumnSchema_;
+
+ TBitmapOutput Values_;
+ TBitmapOutput NullBitmap_;
+
+ void Reset()
+ {
+ Values_ = TBitmapOutput();
+ NullBitmap_ = TBitmapOutput();
+ }
+
+ void AddValues(TRange<NTableClient::TUnversionedRow> rows)
+ {
+ for (auto row : rows) {
+ const auto& value = row[ColumnIndex_];
+ bool isNull = value.Type == NTableClient::EValueType::Null;
+ bool data = isNull ? false : value.Data.Boolean;
+ NullBitmap_.Append(isNull);
+ Values_.Append(data);
+ }
+ }
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateBooleanColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema)
+{
+ return std::make_unique<TBooleanColumnConverter>(columnIndex, columnSchema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/boolean_converter.h b/yt/yt/client/converters/boolean_converter.h
new file mode 100644
index 0000000000..7eee8b5349
--- /dev/null
+++ b/yt/yt/client/converters/boolean_converter.h
@@ -0,0 +1,15 @@
+#pragma once
+#include "converter.h"
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateBooleanColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/converter.cpp b/yt/yt/client/converters/converter.cpp
new file mode 100644
index 0000000000..d513d382a7
--- /dev/null
+++ b/yt/yt/client/converters/converter.cpp
@@ -0,0 +1,77 @@
+#include "converter.h"
+
+#include "boolean_converter.h"
+#include "floating_point_converter.h"
+#include "integer_converter.h"
+#include "null_converter.h"
+#include "string_converter.h"
+
+#include <yt/yt/client/table_client/row_base.h>
+#include <yt/yt/client/table_client/schema.h>
+
+namespace NYT::NConverters {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateColumnConvert(
+ const NTableClient::TColumnSchema& columnSchema,
+ int columnIndex) {
+ switch (columnSchema.GetWireType()) {
+ case EValueType::Int64:
+ return CreateInt64ColumnConverter(columnIndex, columnSchema);
+
+ case EValueType::Uint64:
+ return CreateUint64ColumnConverter(columnIndex, columnSchema);
+
+ case EValueType::Double:
+ switch (columnSchema.CastToV1Type()) {
+ case NTableClient::ESimpleLogicalValueType::Float:
+ return CreateFloatingPoint32ColumnConverter(columnIndex, columnSchema);
+ default:
+ return CreateFloatingPoint64ColumnConverter(columnIndex, columnSchema);
+ }
+
+ case EValueType::String:
+ return CreateStringConverter(columnIndex, columnSchema);
+
+ case EValueType::Boolean:
+ return CreateBooleanColumnConverter(columnIndex, columnSchema);
+
+ case EValueType::Any:
+ return CreateAnyConverter(columnIndex, columnSchema);
+
+ case EValueType::Composite:
+ return CreateCompositeConverter(columnIndex, columnSchema);
+
+ case EValueType::Null:
+ return CreateNullConverter();
+
+ case EValueType::Min:
+ case EValueType::TheBottom:
+ case EValueType::Max:
+ break;
+ }
+ ThrowUnexpectedValueType(columnSchema.GetWireType());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+TConvertedColumnRange ConvertRowsToColumns(
+ TRange<NTableClient::TUnversionedRow> rows,
+ const std::vector<NTableClient::TColumnSchema> &columnSchema)
+{
+ TConvertedColumnRange convertedColumnsRange;
+ for (int columnId = 0; columnId < std::ssize(columnSchema); columnId++) {
+ auto converter = CreateColumnConvert(columnSchema[columnId], columnId);
+ auto columns = converter->Convert(rows);
+ convertedColumnsRange.push_back(columns);
+ }
+ return convertedColumnsRange;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/yt/yt/client/converters/converter.h b/yt/yt/client/converters/converter.h
new file mode 100644
index 0000000000..01cdea393e
--- /dev/null
+++ b/yt/yt/client/converters/converter.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/misc/bitmap.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NConverters {
+
+using TBatchColumn = NTableClient::IUnversionedColumnarRowBatch::TColumn;
+using TBatchColumnPtr = std::shared_ptr<TBatchColumn>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TOwningColumn
+{
+ TBatchColumnPtr Column;
+ std::optional<TSharedRef> NullBitmap;
+ std::optional<TSharedRef> ValueBuffer;
+ std::optional<TSharedRef> StringBuffer;
+};
+
+struct TConvertedColumn
+{
+ std::vector<TOwningColumn> Columns;
+ TBatchColumn* RootColumn;
+};
+
+using TConvertedColumnRange = std::vector<TConvertedColumn>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IColumnConverter
+ : public TNonCopyable
+{
+ virtual ~IColumnConverter() = default;
+ virtual TConvertedColumn Convert(TRange<NTableClient::TUnversionedRow> rows) = 0;
+};
+
+using IColumnConverterPtr = std::unique_ptr<IColumnConverter>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+TConvertedColumnRange ConvertRowsToColumns(
+ TRange<NTableClient::TUnversionedRow> rows,
+ const std::vector<NTableClient::TColumnSchema> &columnSchema);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/floating_point_converter.cpp b/yt/yt/client/converters/floating_point_converter.cpp
new file mode 100644
index 0000000000..125f02292e
--- /dev/null
+++ b/yt/yt/client/converters/floating_point_converter.cpp
@@ -0,0 +1,134 @@
+#include "floating_point_converter.h"
+#include "helper.h"
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+namespace NYT::NConverters {
+
+using namespace NProto;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+template <typename T>
+void FillColumnarFloatingPointValues(
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* column,
+ i64 startIndex,
+ i64 valueCount,
+ TRef data)
+{
+ column->StartIndex = startIndex;
+ column->ValueCount = valueCount;
+
+ auto& values = column->Values.emplace();
+ values.BitWidth = sizeof(T) * 8;
+ values.Data = data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+template <typename T>
+TSharedRef SerializeFloatingPointVector(const std::vector<T>& values)
+{
+ auto data = TSharedMutableRef::Allocate<TConverterTag>(values.size() * sizeof(T) + sizeof(ui64), {.InitializeStorage = false});
+ *reinterpret_cast<ui64*>(data.Begin()) = static_cast<ui64>(values.size());
+ std::memcpy(
+ data.Begin() + sizeof(ui64),
+ values.data(),
+ values.size() * sizeof(T));
+ return data;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue, NTableClient::EValueType ValueType>
+class TFloatingPointColumnConverter
+ : public IColumnConverter
+{
+public:
+ TFloatingPointColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema)
+ : ColumnIndex_(columnIndex)
+ , ColumnSchema_(columnSchema)
+ {
+ static_assert(std::is_floating_point_v<TValue>);
+ }
+
+ TConvertedColumn Convert(TRange<NTableClient::TUnversionedRow> rows) {
+ Reset();
+ AddValues(rows);
+ auto nullBitmapRef = NullBitmap_.Flush<TConverterTag>();
+ auto valuesRef = TSharedRef::MakeCopy<TConverterTag>(TRef(Values_.data(), sizeof(TValue) * Values_.size()));
+
+ auto column = std::make_shared<TBatchColumn>();
+
+ FillColumnarFloatingPointValues<TValue>(
+ column.get(),
+ 0,
+ rows.size(),
+ valuesRef);
+
+ FillColumnarNullBitmap(
+ column.get(),
+ 0,
+ rows.size(),
+ nullBitmapRef);
+
+ column->Type = ColumnSchema_.LogicalType();
+ column->Id = ColumnIndex_;
+
+ TOwningColumn owner = {
+ std::move(column),
+ std::move(nullBitmapRef),
+ std::move(valuesRef),
+ /*stringBuffer*/ std::nullopt
+ };
+
+ TConvertedColumn res = {{owner}, owner.Column.get()};
+ return res;
+
+ }
+
+private:
+ const int ColumnIndex_;
+ TColumnSchema ColumnSchema_;
+ std::vector<TValue> Values_;
+ TBitmapOutput NullBitmap_;
+
+ void Reset()
+ {
+ Values_.clear();
+ NullBitmap_ = TBitmapOutput();
+ }
+
+ void AddValues(TRange<NTableClient::TUnversionedRow> rows)
+ {
+ for (auto row : rows) {
+ const auto& value = row[ColumnIndex_];
+ NullBitmap_.Append(value.Type == EValueType::Null);
+ Values_.push_back(value.Data.Double);
+ }
+ }
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateFloatingPoint32ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema) {
+ return std::make_unique<TFloatingPointColumnConverter<float, NTableClient::EValueType::Double>>(columnIndex, columnSchema);
+}
+
+IColumnConverterPtr CreateFloatingPoint64ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema) {
+ return std::make_unique<TFloatingPointColumnConverter<double, NTableClient::EValueType::Double>>(columnIndex, columnSchema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/floating_point_converter.h b/yt/yt/client/converters/floating_point_converter.h
new file mode 100644
index 0000000000..b5f049e29e
--- /dev/null
+++ b/yt/yt/client/converters/floating_point_converter.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "converter.h"
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateFloatingPoint32ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema);
+
+IColumnConverterPtr CreateFloatingPoint64ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/helper.cpp b/yt/yt/client/converters/helper.cpp
new file mode 100644
index 0000000000..f531325421
--- /dev/null
+++ b/yt/yt/client/converters/helper.cpp
@@ -0,0 +1,60 @@
+#include "helper.h"
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+#include <yt/yt/client/table_client/columnar.h>
+#include <yt/yt/client/table_client/logical_type.h>
+
+#include <yt/yt/core/misc/bitmap.h>
+#include <yt/yt/core/misc/common.h>
+
+namespace NYT::NConverters {
+
+using namespace NProto;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FillColumnarNullBitmap(
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* column,
+ i64 startIndex,
+ i64 valueCount,
+ TRef bitmap)
+{
+ column->StartIndex = startIndex;
+ column->ValueCount = valueCount;
+
+ auto& nullBitmap = column->NullBitmap.emplace();
+ nullBitmap.Data = bitmap;
+}
+
+
+void FillColumnarDictionary(
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* primaryColumn,
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* dictionaryColumn,
+ NTableClient::IUnversionedColumnarRowBatch::TDictionaryId dictionaryId,
+ NTableClient::TLogicalTypePtr type,
+ i64 startIndex,
+ i64 valueCount,
+ TRef ids)
+{
+ primaryColumn->StartIndex = startIndex;
+ primaryColumn->ValueCount = valueCount;
+
+ dictionaryColumn->Type = type && type->GetMetatype() == ELogicalMetatype::Optional
+ ? type->AsOptionalTypeRef().GetElement()
+ : type;
+
+ auto& primaryValues = primaryColumn->Values.emplace();
+ primaryValues.BitWidth = 32;
+ primaryValues.Data = ids;
+
+ auto& dictionary = primaryColumn->Dictionary.emplace();
+ dictionary.DictionaryId = dictionaryId;
+ dictionary.ZeroMeansNull = true;
+ dictionary.ValueColumn = dictionaryColumn;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/helper.h b/yt/yt/client/converters/helper.h
new file mode 100644
index 0000000000..fc2e22bce7
--- /dev/null
+++ b/yt/yt/client/converters/helper.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/misc/common.h>
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FillColumnarNullBitmap(
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* column,
+ i64 startIndex,
+ i64 valueCount,
+ TRef bitmap);
+
+void FillColumnarDictionary(
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* primaryColumn,
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* dictionaryColumn,
+ NTableClient::IUnversionedColumnarRowBatch::TDictionaryId dictionaryId,
+ NTableClient::TLogicalTypePtr type,
+ i64 startIndex,
+ i64 valueCount,
+ TRef ids);
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EUnversionedStringSegmentType,
+ ((DictionaryDense) (0))
+ ((DirectDense) (1))
+);
+
+/*
+TODO: Dictionary for vector:
+
+DEFINE_ENUM(EUnversionedIntegerSegmentType,
+ ((DictionaryDense) (0))
+ ((DirectDense) (1))
+);
+*/
+
+struct TConverterTag
+{};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
+
diff --git a/yt/yt/client/converters/integer_converter.cpp b/yt/yt/client/converters/integer_converter.cpp
new file mode 100644
index 0000000000..eb36c5181a
--- /dev/null
+++ b/yt/yt/client/converters/integer_converter.cpp
@@ -0,0 +1,176 @@
+#include "integer_converter.h"
+#include "helper.h"
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+#include <library/cpp/yt/coding/zig_zag.h>
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+ui64 EncodeValue(i64 value)
+{
+ return ZigZagEncode64(value);
+}
+
+ui64 EncodeValue(ui64 value)
+{
+ return value;
+}
+
+template <class TValue>
+typename std::enable_if<std::is_signed<TValue>::value, TValue>::type
+GetValue(const NTableClient::TUnversionedValue& value)
+{
+ return value.Data.Int64;
+}
+
+template <class TValue>
+typename std::enable_if<std::is_unsigned<TValue>::value, TValue>::type
+GetValue(const NTableClient::TUnversionedValue& value)
+{
+ return value.Data.Uint64;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FillColumnarIntegerValues(
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* column,
+ i64 startIndex,
+ i64 valueCount,
+ NTableClient::EValueType valueType,
+ ui64 baseValue,
+ TRef data)
+{
+ column->StartIndex = startIndex;
+ column->ValueCount = valueCount;
+
+ auto& values = column->Values.emplace();
+ values.BaseValue = baseValue;
+ values.BitWidth = 64;
+ values.ZigZagEncoded = (valueType == NTableClient::EValueType::Int64);
+ values.Data = data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TValue - i64 or ui64.
+template <class TValue>
+class TIntegerColumnConverter
+ : public IColumnConverter
+{
+public:
+ TIntegerColumnConverter(int columnIndex,
+ NTableClient::EValueType ValueType,
+ NTableClient::TColumnSchema columnSchema)
+
+ : ColumnIndex_(columnIndex),
+ ValueType_(ValueType),
+ ColumnSchema_(columnSchema)
+ {}
+
+ TConvertedColumn Convert(TRange<NTableClient::TUnversionedRow> rows) override
+ {
+ Reset();
+ AddValues(rows);
+ for (i64 index = 0; index < std::ssize(Values_); ++index) {
+ if (!NullBitmap_[index]) {
+ Values_[index] -= MinValue_;
+ }
+ }
+
+ auto nullBitmapRef = NullBitmap_.Flush<TConverterTag>();
+ auto valuesRef = TSharedRef::MakeCopy<TConverterTag>(TRef(Values_.data(), sizeof(ui64) * Values_.size()));
+ auto column = std::make_shared<TBatchColumn>();
+
+ FillColumnarIntegerValues(
+ column.get(),
+ 0,
+ RowCount_,
+ ValueType_,
+ MinValue_,
+ valuesRef);
+
+ FillColumnarNullBitmap(
+ column.get(),
+ 0,
+ RowCount_,
+ nullBitmapRef);
+
+ column->Type = ColumnSchema_.LogicalType();
+ column->Id = ColumnIndex_;
+
+ TOwningColumn owner = {
+ std::move(column),
+ std::move(nullBitmapRef),
+ std::move(valuesRef),
+ /*stringBuffer*/ std::nullopt
+ };
+
+ TConvertedColumn res = {{owner}, owner.Column.get()};
+ return res;
+ }
+
+
+private:
+ const int ColumnIndex_;
+ NTableClient::EValueType ValueType_;
+ i64 RowCount_ = 0;
+ NTableClient::TColumnSchema ColumnSchema_;
+
+ TBitmapOutput NullBitmap_;
+ std::vector<ui64> Values_;
+
+ ui64 MaxValue_;
+ ui64 MinValue_;
+
+ // TODO: Dictionary column
+ // THashMap<ui64, int> DistinctValues_;
+
+ void Reset()
+ {
+ Values_.clear();
+ RowCount_ = 0;
+ MaxValue_ = 0;
+ MinValue_ = std::numeric_limits<ui64>::max();
+ NullBitmap_ = TBitmapOutput();
+ }
+
+ void AddValues(TRange<NTableClient::TUnversionedRow> rows)
+ {
+ for (auto row : rows) {
+ const auto& value = row[ColumnIndex_];
+ bool isNull = value.Type == NTableClient::EValueType::Null;
+ ui64 data = 0;
+ if (!isNull) {
+ data = EncodeValue(GetValue<TValue>(value));
+ }
+ Values_.push_back(data);
+ NullBitmap_.Append(isNull);
+ ++RowCount_;
+ }
+ }
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateInt64ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema)
+{
+ return std::make_unique<TIntegerColumnConverter<i64>>(columnIndex, NTableClient::EValueType::Int64, columnSchema);
+}
+
+
+IColumnConverterPtr CreateUint64ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema)
+{
+ return std::make_unique<TIntegerColumnConverter<ui64>>(columnIndex, NTableClient::EValueType::Uint64, columnSchema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/integer_converter.h b/yt/yt/client/converters/integer_converter.h
new file mode 100644
index 0000000000..18c59954fc
--- /dev/null
+++ b/yt/yt/client/converters/integer_converter.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "converter.h"
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateInt64ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema);
+
+std::unique_ptr<IColumnConverter> CreateUint64ColumnConverter(int columnIndex, const NTableClient::TColumnSchema& columnSchema);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/null_converter.cpp b/yt/yt/client/converters/null_converter.cpp
new file mode 100644
index 0000000000..779ed66dbd
--- /dev/null
+++ b/yt/yt/client/converters/null_converter.cpp
@@ -0,0 +1,48 @@
+#include "null_converter.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+
+namespace NYT::NConverters {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TNullColumnWriterConverter
+ : public IColumnConverter
+{
+public:
+ explicit TNullColumnWriterConverter()
+ {}
+
+ TConvertedColumn Convert(TRange<NTableClient::TUnversionedRow> rows) override
+ {
+ auto rowCount = rows.size();
+
+ auto column = std::make_shared<TBatchColumn>();
+
+ column->Type = SimpleLogicalType(ESimpleLogicalValueType::Null);
+ column->ValueCount = rowCount;
+
+ TOwningColumn owner = {
+ std::move(column),
+ /*NullBitmap*/ std::nullopt,
+ /*ValueBuffer*/ std::nullopt,
+ /*stringBuffer*/ std::nullopt
+ };
+
+ return {{owner}, owner.Column.get()};
+ }
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateNullConverter()
+{
+ return std::make_unique<TNullColumnWriterConverter>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/null_converter.h b/yt/yt/client/converters/null_converter.h
new file mode 100644
index 0000000000..0a90048cdb
--- /dev/null
+++ b/yt/yt/client/converters/null_converter.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "converter.h"
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateNullConverter();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/string_converter.cpp b/yt/yt/client/converters/string_converter.cpp
new file mode 100644
index 0000000000..44faf756e1
--- /dev/null
+++ b/yt/yt/client/converters/string_converter.cpp
@@ -0,0 +1,379 @@
+#include "string_converter.h"
+#include "helper.h"
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+#include <yt/yt/core/misc/bit_packed_unsigned_vector.h>
+
+#include <library/cpp/yt/string/string_builder.h>
+
+namespace NYT::NConverters {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+void FillColumnarStringValues(
+ NTableClient::IUnversionedColumnarRowBatch::TColumn* column,
+ i64 startIndex,
+ i64 valueCount,
+ ui32 avgLength,
+ TRef offsets,
+ TRef stringData)
+{
+ column->StartIndex = startIndex;
+ column->ValueCount = valueCount;
+
+ auto& values = column->Values.emplace();
+ values.BitWidth = 32;
+ values.ZigZagEncoded = true;
+ values.Data = offsets;
+
+ auto& strings = column->Strings.emplace();
+ strings.AvgLength = avgLength;
+ strings.Data = stringData;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+template <EValueType ValueType>
+class TStringConverter
+ : public IColumnConverter
+{
+public:
+ TStringConverter(
+ int columnIndex,
+ const TColumnSchema& columnSchema)
+ : ColumnIndex_(columnIndex)
+ , ColumnSchema_(columnSchema)
+ {}
+
+ TConvertedColumn Convert(TRange<NTableClient::TUnversionedRow> rows) override
+ {
+ Reset();
+ AddValues(rows);
+ return GetColumns();
+ }
+
+private:
+ const int ColumnIndex_;
+ TColumnSchema ColumnSchema_;
+ ui32 RowCount_ = 0;
+ ui32 AllSize_ = 0;
+
+ std::vector<TStringBuf> Values_;
+
+ i64 DictionaryByteSize_;
+ THashMap<TStringBuf, ui32> Dictionary_;
+ TStringBuilder DirectBuffer_;
+
+ void Reset()
+ {
+ AllSize_ = 0;
+ RowCount_ = 0;
+ DictionaryByteSize_ = 0;
+
+ DirectBuffer_.Reset();
+ Values_.clear();
+ Dictionary_.clear();
+ }
+
+ TSharedRef GetDirectDenseNullBitmap() const
+ {
+ TBitmapOutput nullBitmap(Values_.size());
+
+ for (auto value : Values_) {
+ nullBitmap.Append(IsValueNull(value));
+ }
+
+ return nullBitmap.Flush<TConverterTag>();
+ }
+
+ std::vector<ui32> GetDirectDenseOffsets() const
+ {
+ std::vector<ui32> offsets;
+ offsets.reserve(Values_.size());
+
+ ui32 offset = 0;
+ for (auto value : Values_) {
+ offset += value.length();
+ offsets.push_back(offset);
+ }
+
+ return offsets;
+ }
+
+ TConvertedColumn GetDirectColumn(TSharedRef nullBitmap)
+ {
+ auto offsets = GetDirectDenseOffsets();
+
+ // Save offsets as diff from expected.
+ ui32 expectedLength;
+ ui32 maxDiff;
+ PrepareDiffFromExpected(&offsets, &expectedLength, &maxDiff);
+
+ auto directData = DirectBuffer_.GetBuffer();
+
+ auto offsetsRef = TSharedRef::MakeCopy<TConverterTag>(TRef(offsets.data(), sizeof(ui32) * offsets.size()));
+ auto directDataPtr = TSharedRef::MakeCopy<TConverterTag>(TRef(directData.data(), directData.size()));
+ auto column = std::make_shared<TBatchColumn>();
+
+ FillColumnarStringValues(
+ column.get(),
+ 0,
+ RowCount_,
+ expectedLength,
+ TRef(offsetsRef),
+ TRef(directDataPtr));
+
+ FillColumnarNullBitmap(
+ column.get(),
+ 0,
+ RowCount_,
+ TRef(nullBitmap));
+
+ column->Type = ColumnSchema_.LogicalType();
+ column->Id = ColumnIndex_;
+
+ TOwningColumn owner = {
+ std::move(column),
+ std::move(nullBitmap),
+ std::move(offsetsRef),
+ std::move(directDataPtr)
+ };
+
+ TConvertedColumn res = {{owner}, owner.Column.get()};
+ return res;
+ }
+
+ TConvertedColumn GetDictionaryColumn()
+ {
+ auto dictionaryData = TSharedMutableRef::Allocate<TConverterTag>(DictionaryByteSize_, {.InitializeStorage = false});
+
+ std::vector<ui32> dictionaryOffsets;
+ dictionaryOffsets.reserve(Dictionary_.size());
+
+ std::vector<ui32> ids;
+ ids.reserve(Values_.size());
+
+ ui32 dictionarySize = 0;
+ ui32 dictionaryOffset = 0;
+ for (auto value : Values_) {
+ if (IsValueNull(value)) {
+ ids.push_back(0);
+ continue;
+ }
+
+ ui32 id = GetOrCrash(Dictionary_, value);
+ ids.push_back(id);
+
+ if (id > dictionarySize) {
+ std::memcpy(
+ dictionaryData.Begin() + dictionaryOffset,
+ value.data(),
+ value.length());
+ dictionaryOffset += value.length();
+ dictionaryOffsets.push_back(dictionaryOffset);
+ ++dictionarySize;
+ }
+ }
+
+ YT_VERIFY(dictionaryOffset == DictionaryByteSize_);
+
+ // 1. Value ids.
+ auto idsRef = TSharedRef::MakeCopy<TConverterTag>(TRef(ids.data(), sizeof(ui32) * ids.size()));
+
+ // 2. Dictionary offsets.
+ ui32 expectedLength;
+ ui32 maxDiff;
+ PrepareDiffFromExpected(&dictionaryOffsets, &expectedLength, &maxDiff);
+ auto dictionaryOffsetsRef = TSharedRef::MakeCopy<TConverterTag>(TRef(dictionaryOffsets.data(), sizeof(ui32) * dictionaryOffsets.size()));
+
+ auto primaryColumn = std::make_shared<TBatchColumn>();
+ auto dictionaryColumn = std::make_shared<TBatchColumn>();
+
+ FillColumnarStringValues(
+ dictionaryColumn.get(),
+ 0,
+ dictionaryOffsets.size(),
+ expectedLength,
+ TRef(dictionaryOffsetsRef),
+ dictionaryData);
+
+ FillColumnarDictionary(
+ primaryColumn.get(),
+ dictionaryColumn.get(),
+ NTableClient::IUnversionedColumnarRowBatch::GenerateDictionaryId(),
+ primaryColumn->Type,
+ 0,
+ RowCount_,
+ idsRef);
+
+ dictionaryColumn->Type = ColumnSchema_.LogicalType();
+ primaryColumn->Type = ColumnSchema_.LogicalType();
+ primaryColumn->Id = ColumnIndex_;
+
+ TOwningColumn dictOwner = {
+ std::move(dictionaryColumn),
+ /*NullBitmap*/ std::nullopt,
+ std::move(dictionaryOffsetsRef),
+ std::move(dictionaryData)
+ };
+
+ TOwningColumn primeOwner = {
+ std::move(primaryColumn),
+ /*NullBitmap*/ std::nullopt,
+ std::move(idsRef),
+ /*stringBuffer*/ std::nullopt
+ };
+
+ return {{primeOwner, dictOwner}, primeOwner.Column.get()};
+ }
+
+ TConvertedColumn GetColumns()
+ {
+ auto sizes = GetMethodsCosts();
+
+ auto minElement = std::min_element(sizes.begin(), sizes.end());
+ auto type = EUnversionedStringSegmentType(std::distance(sizes.begin(), minElement));
+
+ switch (type) {
+
+ case EUnversionedStringSegmentType::DirectDense:
+ return GetDirectColumn(GetDirectDenseNullBitmap());
+
+ case EUnversionedStringSegmentType::DictionaryDense:
+ return GetDictionaryColumn();
+
+ default:
+ YT_ABORT();
+ }
+ }
+
+ TEnumIndexedVector<EUnversionedStringSegmentType, i32> GetMethodsCosts() const
+ {
+ TEnumIndexedVector<EUnversionedStringSegmentType, i32> sizes;
+ for (auto type : TEnumTraits<EUnversionedStringSegmentType>::GetDomainValues()) {
+ sizes[type] = GetSpecificMethodCosts(type);
+ }
+ return sizes;
+ }
+
+ i32 GetSpecificMethodCosts(EUnversionedStringSegmentType type) const
+ {
+ switch (type) {
+
+ case EUnversionedStringSegmentType::DictionaryDense:
+ return GetDictionaryByteSize();
+
+ case EUnversionedStringSegmentType::DirectDense:
+ return GetDirectByteSize();
+
+ default:
+ YT_ABORT();
+ }
+ }
+
+ void AddValues(TRange<NTableClient::TUnversionedRow> rows)
+ {
+ for (auto row : rows) {
+ const auto& unversionedValue = row[ColumnIndex_];
+ auto value = CaptureValue(unversionedValue);
+ Values_.push_back(value);
+ ++RowCount_;
+ }
+ }
+
+ static bool IsValueNull(TStringBuf lhs)
+ {
+ return !lhs.data();
+ }
+
+ i64 GetDirectByteSize() const
+ {
+ return AllSize_;
+ }
+
+ i64 GetDictionaryByteSize() const
+ {
+ return DictionaryByteSize_ + Values_.size() * sizeof(ui32);
+ }
+
+
+ TStringBuf CaptureValue(const TUnversionedValue& unversionedValue)
+ {
+ if (unversionedValue.Type == EValueType::Null) {
+ return {};
+ }
+
+ auto valueCapacity = IsAnyOrComposite(ValueType) && !IsAnyOrComposite(unversionedValue.Type)
+ ? GetYsonSize(unversionedValue)
+ : static_cast<i64>(unversionedValue.Length);
+
+ char* buffer = DirectBuffer_.Preallocate(valueCapacity);
+ if (!buffer) {
+ // This means, that we reserved nothing, because all strings are either null or empty.
+ // To distinguish between null and empty, we set preallocated pointer to special value.
+ static char* const EmptyStringBase = reinterpret_cast<char*>(1);
+ buffer = EmptyStringBase;
+ }
+
+ auto start = buffer;
+
+ if (IsAnyOrComposite(ValueType) && !IsAnyOrComposite(unversionedValue.Type)) {
+ // Any non-any and non-null value convert to YSON.
+ buffer += WriteYson(buffer, unversionedValue);
+ } else {
+ std::memcpy(
+ buffer,
+ unversionedValue.Data.String,
+ unversionedValue.Length);
+ buffer += unversionedValue.Length;
+ }
+
+ auto value = TStringBuf(start, buffer);
+
+ YT_VERIFY(value.size() <= valueCapacity);
+
+ DirectBuffer_.Advance(value.size());
+
+ if (Dictionary_.emplace(value, Dictionary_.size() + 1).second) {
+ DictionaryByteSize_ += value.size();
+ }
+ AllSize_ += value.size();
+ return value;
+ }
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateStringConverter(
+ int columnIndex,
+ const NTableClient::TColumnSchema& columnSchema)
+{
+ return std::make_unique<TStringConverter<EValueType::String>>(columnIndex, columnSchema);
+}
+
+IColumnConverterPtr CreateAnyConverter(
+ int columnIndex,
+ const NTableClient::TColumnSchema& columnSchema)
+{
+ return std::make_unique<TStringConverter<EValueType::Any>>(columnIndex, columnSchema);
+}
+
+IColumnConverterPtr CreateCompositeConverter(
+ int columnIndex,
+ const NTableClient::TColumnSchema& columnSchema)
+{
+ return std::make_unique<TStringConverter<EValueType::Composite>>(columnIndex, columnSchema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/converters/string_converter.h b/yt/yt/client/converters/string_converter.h
new file mode 100644
index 0000000000..7cda42526e
--- /dev/null
+++ b/yt/yt/client/converters/string_converter.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "converter.h"
+
+#include <yt/yt/client/table_client/schema.h>
+
+namespace NYT::NConverters {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IColumnConverterPtr CreateStringConverter(
+ int columnIndex,
+ const NTableClient::TColumnSchema& columnSchema);
+
+IColumnConverterPtr CreateAnyConverter(
+ int columnIndex,
+ const NTableClient::TColumnSchema& columnSchema);
+
+IColumnConverterPtr CreateCompositeConverter(
+ int columnIndex,
+ const NTableClient::TColumnSchema& columnSchema);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NConverters
diff --git a/yt/yt/client/cypress_client/public.cpp b/yt/yt/client/cypress_client/public.cpp
new file mode 100644
index 0000000000..147aed65b7
--- /dev/null
+++ b/yt/yt/client/cypress_client/public.cpp
@@ -0,0 +1,12 @@
+#include "public.h"
+
+namespace NYT::NCypressClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TLockId NullLockId;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NCypressClient
+
diff --git a/yt/yt/client/cypress_client/public.h b/yt/yt/client/cypress_client/public.h
new file mode 100644
index 0000000000..05e70d4984
--- /dev/null
+++ b/yt/yt/client/cypress_client/public.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NCypressClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using NObjectClient::TObjectId;
+using NObjectClient::TTransactionId;
+using NObjectClient::NullObjectId;
+using NObjectClient::NullTransactionId;
+using NObjectClient::EObjectType;
+using NObjectClient::TVersionedObjectId;
+
+using TNodeId = TObjectId;
+using TLockId = TObjectId;
+using TCypressShardId = TObjectId;
+using TVersionedNodeId = TVersionedObjectId;
+
+extern const TLockId NullLockId;
+
+// NB: The order is from weakest to strongest.
+DEFINE_ENUM(ELockMode,
+ ((None) (0))
+ ((Snapshot) (1))
+ ((Shared) (2))
+ ((Exclusive) (3))
+);
+
+DEFINE_ENUM(ELockState,
+ ((Pending) (0))
+ ((Acquired) (1))
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((SameTransactionLockConflict) (400))
+ ((DescendantTransactionLockConflict) (401))
+ ((ConcurrentTransactionLockConflict) (402))
+ ((PendingLockConflict) (403))
+ ((LockDestroyed) (404))
+ ((TooManyLocksOnTransaction) (405))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NCypressClient
diff --git a/yt/yt/client/driver/admin_commands.cpp b/yt/yt/client/driver/admin_commands.cpp
new file mode 100644
index 0000000000..8e8dfa5abc
--- /dev/null
+++ b/yt/yt/client/driver/admin_commands.cpp
@@ -0,0 +1,453 @@
+#include "admin_commands.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+using namespace NYTree;
+using namespace NConcurrency;
+using namespace NChaosClient;
+using namespace NObjectClient;
+
+using NApi::TMaintenanceId;
+using NApi::TMaintenanceFilter;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TBuildSnapshotCommand::TBuildSnapshotCommand()
+{
+ RegisterParameter("cell_id", Options.CellId);
+ RegisterParameter("set_read_only", Options.SetReadOnly)
+ .Optional();
+ RegisterParameter("wait_for_snapshot_completion", Options.WaitForSnapshotCompletion)
+ .Optional();
+}
+
+void TBuildSnapshotCommand::DoExecute(ICommandContextPtr context)
+{
+ auto snapshotId = WaitFor(context->GetClient()->BuildSnapshot(Options))
+ .ValueOrThrow();
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("snapshot_id").Value(snapshotId)
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TBuildMasterSnapshotsCommand::TBuildMasterSnapshotsCommand()
+{
+ RegisterParameter("set_read_only", Options.SetReadOnly)
+ .Optional();
+ RegisterParameter("wait_for_snapshot_completion", Options.WaitForSnapshotCompletion)
+ .Optional();
+ RegisterParameter("retry", Options.Retry)
+ .Optional();
+}
+
+void TBuildMasterSnapshotsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto cellIdToSnapshotId = WaitFor(context->GetClient()->BuildMasterSnapshots(Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .DoListFor(cellIdToSnapshotId, [=] (TFluentList fluent, const auto& pair) {
+ fluent
+ .Item().BeginMap()
+ .Item("cell_id").Value(pair.first)
+ .Item("snapshot_id").Value(pair.second)
+ .EndMap();
+ })
+ );
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSwitchLeaderCommand::TSwitchLeaderCommand()
+{
+ RegisterParameter("cell_id", CellId_);
+ RegisterParameter("new_leader_address", NewLeaderAddress_);
+}
+
+void TSwitchLeaderCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->SwitchLeader(CellId_, NewLeaderAddress_))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TResetStateHashCommand::TResetStateHashCommand()
+{
+ RegisterParameter("cell_id", CellId_);
+ RegisterParameter("new_state_hash", Options.NewStateHash)
+ .Optional();
+}
+
+void TResetStateHashCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->ResetStateHash(CellId_, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+THealExecNodeCommand::THealExecNodeCommand()
+{
+ RegisterParameter("address", Address_);
+ RegisterParameter("locations", Options.Locations)
+ .Optional();
+ RegisterParameter("alert_types_to_reset", Options.AlertTypesToReset)
+ .Optional();
+ RegisterParameter("force_reset", Options.ForceReset)
+ .Optional();
+}
+
+void THealExecNodeCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->HealExecNode(Address_, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSuspendCoordinatorCommand::TSuspendCoordinatorCommand()
+{
+ RegisterParameter("coordinator_cell_id", CoordinatorCellId_);
+}
+
+void TSuspendCoordinatorCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->SuspendCoordinator(CoordinatorCellId_))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TResumeCoordinatorCommand::TResumeCoordinatorCommand()
+{
+ RegisterParameter("coordinator_cell_id", CoordinatorCellId_);
+}
+
+void TResumeCoordinatorCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->ResumeCoordinator(CoordinatorCellId_))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMigrateReplicationCardsCommand::TMigrateReplicationCardsCommand()
+{
+ RegisterParameter("chaos_cell_id", ChaosCellId_);
+ RegisterParameter("destination_cell_id", Options.DestinationCellId)
+ .Optional();
+ RegisterParameter("replication_card_ids", Options.ReplicationCardIds)
+ .Optional();
+}
+
+void TMigrateReplicationCardsCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->MigrateReplicationCards(ChaosCellId_, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSuspendChaosCellsCommand::TSuspendChaosCellsCommand()
+{
+ RegisterParameter("cell_ids", CellIds_);
+}
+
+void TSuspendChaosCellsCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->SuspendChaosCells(CellIds_))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TResumeChaosCellsCommand::TResumeChaosCellsCommand()
+{
+ RegisterParameter("cell_ids", CellIds_);
+}
+
+void TResumeChaosCellsCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->ResumeChaosCells(CellIds_))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSuspendTabletCellsCommand::TSuspendTabletCellsCommand()
+{
+ RegisterParameter("cell_ids", CellIds_);
+}
+
+void TSuspendTabletCellsCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->SuspendTabletCells(CellIds_, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TResumeTabletCellsCommand::TResumeTabletCellsCommand()
+{
+ RegisterParameter("cell_ids", CellIds_);
+}
+
+void TResumeTabletCellsCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->ResumeTabletCells(CellIds_, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAddMaintenanceCommand::TAddMaintenanceCommand()
+{
+ RegisterParameter("component", Component_);
+ RegisterParameter("address", Address_);
+ RegisterParameter("type", Type_);
+ RegisterParameter("comment", Comment_);
+}
+
+void TAddMaintenanceCommand::DoExecute(ICommandContextPtr context)
+{
+ auto id = WaitFor(context->GetClient()->AddMaintenance(
+ Component_,
+ Address_,
+ Type_,
+ Comment_,
+ Options))
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "id", id);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRemoveMaintenanceCommand::TRemoveMaintenanceCommand()
+{
+ RegisterParameter("component", Component_);
+ RegisterParameter("address", Address_);
+
+ RegisterParameter("id", Id_)
+ .Default();
+
+ RegisterParameter("ids", Ids_)
+ .Default();
+
+ RegisterParameter("user", User_)
+ .Default();
+ RegisterParameter("mine", Mine_)
+ .Optional();
+
+ RegisterParameter("type", Type_)
+ .Optional();
+
+ RegisterParameter("all", All_)
+ .Optional();
+
+ RegisterPostprocessor([&] {
+ THROW_ERROR_EXCEPTION_IF(Id_ && Ids_,
+ "At most one of {\"id\", \"ids\"} can be specified at the same time");
+
+ THROW_ERROR_EXCEPTION_IF(Ids_ && Ids_->empty(),
+ "\"ids\" must not be empty if specified");
+
+ THROW_ERROR_EXCEPTION_IF(!Id_ && !Ids_ && !User_ && !Mine_ && !Type_ && !All_,
+ "\"all\" must be specified explicitly");
+
+ THROW_ERROR_EXCEPTION_IF(Mine_ && User_,
+ "Cannot specify both \"user\" and \"mine\"");
+
+ THROW_ERROR_EXCEPTION_IF(All_ && (User_ || Mine_ || Type_ || Id_ || Ids_),
+ "\"all\" cannot be used with other options");
+ });
+}
+
+namespace {
+
+TStringBuf MaintenanceTypeToString(NApi::EMaintenanceType type)
+{
+ switch (type) {
+ case NApi::EMaintenanceType::Ban:
+ return "ban";
+ case NApi::EMaintenanceType::Decommission:
+ return "decommission";
+ case NApi::EMaintenanceType::DisableSchedulerJobs:
+ return "disable_scheduler_jobs";
+ case NApi::EMaintenanceType::DisableWriteSessions:
+ return "disable_write_sessions";
+ case NApi::EMaintenanceType::DisableTabletCells:
+ return "disable_tablet_cells";
+ case NApi::EMaintenanceType::PendingRestart:
+ return "pending_restart";
+ default:
+ YT_ABORT();
+ }
+}
+
+} // namespace
+
+void TRemoveMaintenanceCommand::DoExecute(ICommandContextPtr context)
+{
+ TMaintenanceFilter filter;
+
+ if (Id_) {
+ filter.Ids = {*Id_};
+ } else if (Ids_) {
+ filter.Ids = *Ids_;
+ }
+
+ if (Mine_) {
+ filter.User = TMaintenanceFilter::TByUser::TMine{};
+ } else if (User_) {
+ filter.User = *User_;
+ } else {
+ filter.User = TMaintenanceFilter::TByUser::TAll{};
+ }
+
+ if (Type_) {
+ filter.Type = *Type_;
+ }
+
+ auto removedMaintenanceCounts = WaitFor(context->GetClient()->RemoveMaintenance(
+ Component_,
+ Address_,
+ filter,
+ Options))
+ .ValueOrThrow();
+
+ ProduceOutput(context, [&] (NYson::IYsonConsumer* consumer) {
+ auto fluent = BuildYsonFluently(consumer)
+ .BeginMap();
+ for (auto type : TEnumTraits<NApi::EMaintenanceType>::GetDomainValues()) {
+ if (removedMaintenanceCounts[type] > 0) {
+ fluent = fluent.Item(MaintenanceTypeToString(type)).Value(removedMaintenanceCounts[type]);
+ }
+ }
+ fluent.EndMap();
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDisableChunkLocationsCommand::TDisableChunkLocationsCommand()
+{
+ RegisterParameter("node_address", NodeAddress_);
+ RegisterParameter("location_uuids", LocationUuids_)
+ .Default();
+}
+
+void TDisableChunkLocationsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->DisableChunkLocations(NodeAddress_, LocationUuids_, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("location_uuids")
+ .BeginList()
+ .DoFor(result.LocationUuids, [&] (TFluentList fluent, const auto& uuid) {
+ fluent.Item().Value(uuid);
+ })
+ .EndList()
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDestroyChunkLocationsCommand::TDestroyChunkLocationsCommand()
+{
+ RegisterParameter("node_address", NodeAddress_);
+ RegisterParameter("location_uuids", LocationUuids_)
+ .Default();
+}
+
+void TDestroyChunkLocationsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->DestroyChunkLocations(NodeAddress_, LocationUuids_, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("location_uuids")
+ .BeginList()
+ .DoFor(result.LocationUuids, [&] (TFluentList fluent, const auto& uuid) {
+ fluent.Item().Value(uuid);
+ })
+ .EndList()
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TResurrectChunkLocationsCommand::TResurrectChunkLocationsCommand()
+{
+ RegisterParameter("node_address", NodeAddress_);
+ RegisterParameter("location_uuids", LocationUuids_)
+ .Default();
+}
+
+void TResurrectChunkLocationsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->ResurrectChunkLocations(NodeAddress_, LocationUuids_, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("location_uuids")
+ .BeginList()
+ .DoFor(result.LocationUuids, [&] (TFluentList fluent, const auto& uuid) {
+ fluent.Item().Value(uuid);
+ })
+ .EndList()
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRequestRebootCommand::TRequestRebootCommand()
+{
+ RegisterParameter("node_address", NodeAddress_);
+}
+
+void TRequestRebootCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->RequestReboot(NodeAddress_, Options))
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/admin_commands.h b/yt/yt/client/driver/admin_commands.h
new file mode 100644
index 0000000000..9691829b53
--- /dev/null
+++ b/yt/yt/client/driver/admin_commands.h
@@ -0,0 +1,273 @@
+#pragma once
+
+#include "command.h"
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBuildSnapshotCommand
+ : public TTypedCommand<NApi::TBuildSnapshotOptions>
+{
+public:
+ TBuildSnapshotCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBuildMasterSnapshotsCommand
+ : public TTypedCommand<NApi::TBuildMasterSnapshotsOptions>
+{
+public:
+ TBuildMasterSnapshotsCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSwitchLeaderCommand
+ : public TTypedCommand<NApi::TSwitchLeaderOptions>
+{
+public:
+ TSwitchLeaderCommand();
+
+private:
+ NHydra::TCellId CellId_;
+ TString NewLeaderAddress_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TResetStateHashCommand
+ : public TTypedCommand<NApi::TResetStateHashOptions>
+{
+public:
+ TResetStateHashCommand();
+
+private:
+ NHydra::TCellId CellId_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class THealExecNodeCommand
+ : public TTypedCommand<NApi::THealExecNodeOptions>
+{
+public:
+ THealExecNodeCommand();
+
+private:
+ TString Address_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSuspendCoordinatorCommand
+ : public TTypedCommand<NApi::TSuspendCoordinatorOptions>
+{
+public:
+ TSuspendCoordinatorCommand();
+
+private:
+ NObjectClient::TCellId CoordinatorCellId_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TResumeCoordinatorCommand
+ : public TTypedCommand<NApi::TResumeCoordinatorOptions>
+{
+public:
+ TResumeCoordinatorCommand();
+
+private:
+ NObjectClient::TCellId CoordinatorCellId_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMigrateReplicationCardsCommand
+ : public TTypedCommand<NApi::TMigrateReplicationCardsOptions>
+{
+public:
+ TMigrateReplicationCardsCommand();
+
+private:
+ NObjectClient::TCellId ChaosCellId_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSuspendChaosCellsCommand
+ : public TTypedCommand<NApi::TSuspendChaosCellsOptions>
+{
+public:
+ TSuspendChaosCellsCommand();
+
+private:
+ std::vector<NObjectClient::TCellId> CellIds_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TResumeChaosCellsCommand
+ : public TTypedCommand<NApi::TResumeChaosCellsOptions>
+{
+public:
+ TResumeChaosCellsCommand();
+
+private:
+ std::vector<NObjectClient::TCellId> CellIds_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSuspendTabletCellsCommand
+ : public TTypedCommand<NApi::TSuspendTabletCellsOptions>
+{
+public:
+ TSuspendTabletCellsCommand();
+
+private:
+ std::vector<NObjectClient::TCellId> CellIds_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TResumeTabletCellsCommand
+ : public TTypedCommand<NApi::TResumeTabletCellsOptions>
+{
+public:
+ TResumeTabletCellsCommand();
+
+private:
+ std::vector<NObjectClient::TCellId> CellIds_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAddMaintenanceCommand
+ : public TTypedCommand<NApi::TAddMaintenanceOptions>
+{
+public:
+ TAddMaintenanceCommand();
+
+private:
+ NApi::EMaintenanceComponent Component_;
+ TString Address_;
+ NApi::EMaintenanceType Type_;
+ TString Comment_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoveMaintenanceCommand
+ : public TTypedCommand<NApi::TRemoveMaintenanceOptions>
+{
+public:
+ TRemoveMaintenanceCommand();
+
+private:
+ NApi::EMaintenanceComponent Component_;
+ TString Address_;
+ bool Mine_ = false;
+ bool All_ = false;
+ std::optional<TString> User_;
+ std::optional<NApi::TMaintenanceId> Id_;
+ std::optional<std::vector<NApi::TMaintenanceId>> Ids_;
+ std::optional<NApi::EMaintenanceType> Type_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDisableChunkLocationsCommand
+ : public TTypedCommand<NApi::TDisableChunkLocationsOptions>
+{
+public:
+ TDisableChunkLocationsCommand();
+
+private:
+ TString NodeAddress_;
+ std::vector<TGuid> LocationUuids_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDestroyChunkLocationsCommand
+ : public TTypedCommand<NApi::TDestroyChunkLocationsOptions>
+{
+public:
+ TDestroyChunkLocationsCommand();
+
+private:
+ TString NodeAddress_;
+ std::vector<TGuid> LocationUuids_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TResurrectChunkLocationsCommand
+ : public TTypedCommand<NApi::TResurrectChunkLocationsOptions>
+{
+public:
+ TResurrectChunkLocationsCommand();
+
+private:
+ TString NodeAddress_;
+ std::vector<TGuid> LocationUuids_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Endpoint is necessary for manual configuration regeneration, disk partitioning and node restart.
+// Important part of Hot Swap mechanic.
+class TRequestRebootCommand
+ : public TTypedCommand<NApi::TRequestRebootOptions>
+{
+public:
+ TRequestRebootCommand();
+
+private:
+ TString NodeAddress_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/authentication_commands.cpp b/yt/yt/client/driver/authentication_commands.cpp
new file mode 100644
index 0000000000..170dfdcbf9
--- /dev/null
+++ b/yt/yt/client/driver/authentication_commands.cpp
@@ -0,0 +1,97 @@
+#include "authentication_commands.h"
+
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT::NDriver {
+
+using namespace NConcurrency;
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSetUserPasswordCommand::TSetUserPasswordCommand()
+{
+ RegisterParameter("user", User_);
+ RegisterParameter("current_password_sha256", CurrentPasswordSha256_)
+ .Default();
+ RegisterParameter("new_password_sha256", NewPasswordSha256_);
+}
+
+void TSetUserPasswordCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->SetUserPassword(
+ User_,
+ CurrentPasswordSha256_,
+ NewPasswordSha256_,
+ Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TIssueTokenCommand::TIssueTokenCommand()
+{
+ RegisterParameter("user", User_);
+ RegisterParameter("password_sha256", PasswordSha256_)
+ .Default();
+}
+
+void TIssueTokenCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->IssueToken(
+ User_,
+ PasswordSha256_,
+ Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(ConvertToYsonString(result.Token));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRevokeTokenCommand::TRevokeTokenCommand()
+{
+ RegisterParameter("user", User_);
+ RegisterParameter("password_sha256", PasswordSha256_)
+ .Default();
+ RegisterParameter("token_sha256", TokenSha256_);
+}
+
+void TRevokeTokenCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->RevokeToken(
+ User_,
+ PasswordSha256_,
+ TokenSha256_,
+ Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TListUserTokensCommand::TListUserTokensCommand()
+{
+ RegisterParameter("user", User_);
+ RegisterParameter("password_sha256", PasswordSha256_)
+ .Default();
+}
+
+void TListUserTokensCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->ListUserTokens(
+ User_,
+ PasswordSha256_,
+ Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(ConvertToYsonString(result.Tokens));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/authentication_commands.h b/yt/yt/client/driver/authentication_commands.h
new file mode 100644
index 0000000000..7181d7ce8c
--- /dev/null
+++ b/yt/yt/client/driver/authentication_commands.h
@@ -0,0 +1,73 @@
+#include "command.h"
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSetUserPasswordCommand
+ : public TTypedCommand<NApi::TSetUserPasswordOptions>
+{
+public:
+ TSetUserPasswordCommand();
+
+private:
+ TString User_;
+
+ TString CurrentPasswordSha256_;
+ TString NewPasswordSha256_;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TIssueTokenCommand
+ : public TTypedCommand<NApi::TIssueTokenOptions>
+{
+public:
+ TIssueTokenCommand();
+
+private:
+ TString User_;
+
+ TString PasswordSha256_;
+
+ void DoExecute(ICommandContextPtr context);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRevokeTokenCommand
+ : public TTypedCommand<NApi::TRevokeTokenOptions>
+{
+public:
+ TRevokeTokenCommand();
+
+private:
+ TString User_;
+
+ TString PasswordSha256_;
+ TString TokenSha256_;
+
+ void DoExecute(ICommandContextPtr context);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TListUserTokensCommand
+ : public TTypedCommand<NApi::TListUserTokensOptions>
+{
+public:
+ TListUserTokensCommand();
+
+private:
+ TString User_;
+
+ TString PasswordSha256_;
+
+ void DoExecute(ICommandContextPtr context);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/chaos_commands.cpp b/yt/yt/client/driver/chaos_commands.cpp
new file mode 100644
index 0000000000..5a3c72358f
--- /dev/null
+++ b/yt/yt/client/driver/chaos_commands.cpp
@@ -0,0 +1,58 @@
+#include "chaos_commands.h"
+
+#include <yt/yt/client/chaos_client/replication_card_serialization.h>
+
+#include <yt/yt/client/tablet_client/config.h>
+
+namespace NYT::NDriver {
+
+using namespace NApi;
+using namespace NChaosClient;
+using namespace NConcurrency;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUpdateChaosTableReplicaProgressCommand::TUpdateChaosTableReplicaProgressCommand()
+{
+ RegisterParameter("replica_id", ReplicaId);
+ RegisterParameter("progress", Options.Progress);
+}
+
+void TUpdateChaosTableReplicaProgressCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+
+ auto future = client->UpdateChaosTableReplicaProgress(ReplicaId, Options);
+ WaitFor(future)
+ .ThrowOnError();
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAlterReplicationCardCommand::TAlterReplicationCardCommand()
+{
+ RegisterParameter("replication_card_id", ReplicationCardId);
+ RegisterParameter("replicated_table_options", Options.ReplicatedTableOptions)
+ .Optional();
+ RegisterParameter("enable_replicated_table_tracker", Options.EnableReplicatedTableTracker)
+ .Optional();
+ RegisterParameter("replication_card_collocation_id", Options.ReplicationCardCollocationId)
+ .Optional();
+}
+
+void TAlterReplicationCardCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->AlterReplicationCard(
+ ReplicationCardId,
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/chaos_commands.h b/yt/yt/client/driver/chaos_commands.h
new file mode 100644
index 0000000000..ed763e84d8
--- /dev/null
+++ b/yt/yt/client/driver/chaos_commands.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/chaos_client/replication_card.h>
+
+#include <yt/yt/core/ytree/yson_serializable.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUpdateChaosTableReplicaProgressCommand
+ : public TTypedCommand<NApi::TUpdateChaosTableReplicaProgressOptions>
+{
+public:
+ TUpdateChaosTableReplicaProgressCommand();
+
+private:
+ NChaosClient::TReplicaId ReplicaId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAlterReplicationCardCommand
+ : public TTypedCommand<NApi::TAlterReplicationCardOptions>
+{
+public:
+ TAlterReplicationCardCommand();
+
+private:
+ NChaosClient::TReplicationCardId ReplicationCardId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/command-inl.h b/yt/yt/client/driver/command-inl.h
new file mode 100644
index 0000000000..e646780a5d
--- /dev/null
+++ b/yt/yt/client/driver/command-inl.h
@@ -0,0 +1,274 @@
+#ifndef COMMAND_INL_H
+#error "Direct inclusion of this file is not allowed, include command.h"
+// For the sake of sane code completion.
+#include "command.h"
+#endif
+
+#include "private.h"
+#include "driver.h"
+
+#include <yt/yt/client/transaction_client/helpers.h>
+
+#include <yt/yt/core/logging/log.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/serialize.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+void ProduceSingleOutputValue(
+ ICommandContextPtr context,
+ TStringBuf name,
+ const T& value)
+{
+ ProduceSingleOutput(context, name, [&] (NYson::IYsonConsumer* consumer) {
+ NYTree::BuildYsonFluently(consumer)
+ .Value(value);
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TTransactionalCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TTransactionalOptions&>>
+>::TTransactionalCommandBase()
+{
+ this->RegisterParameter("transaction_id", this->Options.TransactionId)
+ .Optional();
+ this->RegisterParameter("ping", this->Options.Ping)
+ .Optional();
+ this->RegisterParameter("ping_ancestor_transactions", this->Options.PingAncestors)
+ .Optional();
+ this->RegisterParameter("suppress_transaction_coordinator_sync", this->Options.SuppressTransactionCoordinatorSync)
+ .Optional();
+ this->RegisterParameter("suppress_upstream_sync", this->Options.SuppressUpstreamSync)
+ .Optional();
+}
+
+template <class TOptions>
+NApi::ITransactionPtr TTransactionalCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TTransactionalOptions&>>
+>::AttachTransaction(
+ ICommandContextPtr context,
+ bool required)
+{
+ auto transactionId = this->Options.TransactionId;
+ if (!transactionId) {
+ if (required) {
+ THROW_ERROR_EXCEPTION("Transaction is required");
+ }
+ return nullptr;
+ }
+
+ const auto& transactionPool = context->GetDriver()->GetStickyTransactionPool();
+
+ if (!NTransactionClient::IsMasterTransactionId(transactionId)) {
+ return transactionPool->GetTransactionAndRenewLeaseOrThrow(transactionId);
+ }
+
+ auto transaction = transactionPool->FindTransactionAndRenewLease(transactionId);
+ if (!transaction) {
+ NApi::TTransactionAttachOptions options;
+ options.Ping = this->Options.Ping;
+ options.PingAncestors = this->Options.PingAncestors;
+ transaction = context->GetClient()->AttachTransaction(transactionId, options);
+ }
+
+ return transaction;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TMutatingCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TMutatingOptions&>>
+>::TMutatingCommandBase()
+{
+ this->RegisterParameter("mutation_id", this->Options.MutationId)
+ .Optional();
+ this->RegisterParameter("retry", this->Options.Retry)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TReadOnlyMasterCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TMasterReadOptions&>>
+>::TReadOnlyMasterCommandBase()
+{
+ this->RegisterParameter("read_from", this->Options.ReadFrom)
+ .Optional();
+ this->RegisterParameter("disable_per_user_cache", this->Options.DisablePerUserCache)
+ .Optional();
+ this->RegisterParameter("expire_after_successful_update_time", this->Options.ExpireAfterSuccessfulUpdateTime)
+ .Optional();
+ this->RegisterParameter("expire_after_failed_update_time", this->Options.ExpireAfterFailedUpdateTime)
+ .Optional();
+ this->RegisterParameter("success_staleness_bound", this->Options.SuccessStalenessBound)
+ .Optional();
+ this->RegisterParameter("cache_sticky_group_size", this->Options.CacheStickyGroupSize)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TReadOnlyTabletCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TTabletReadOptions&>>
+>::TReadOnlyTabletCommandBase()
+{
+ this->RegisterParameter("read_from", this->Options.ReadFrom)
+ .Optional();
+ this->RegisterParameter("rpc_hedging_delay", this->Options.RpcHedgingDelay)
+ .Optional();
+ this->RegisterParameter("timestamp", this->Options.Timestamp)
+ .Optional();
+ this->RegisterParameter("retention_timestamp", this->Options.RetentionTimestamp)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TSuppressableAccessTrackingCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TSuppressableAccessTrackingOptions&>>
+>::TSuppressableAccessTrackingCommandBase()
+{
+ this->RegisterParameter("suppress_access_tracking", this->Options.SuppressAccessTracking)
+ .Optional();
+ this->RegisterParameter("suppress_modification_tracking", this->Options.SuppressModificationTracking)
+ .Optional();
+ this->RegisterParameter("suppress_expiration_timeout_renewal", this->Options.SuppressExpirationTimeoutRenewal)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TPrerequisiteCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TPrerequisiteOptions&>>
+>::TPrerequisiteCommandBase()
+{
+ this->RegisterParameter("prerequisite_transaction_ids", this->Options.PrerequisiteTransactionIds)
+ .Optional();
+ this->RegisterParameter("prerequisite_revisions", this->Options.PrerequisiteRevisions)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TTimeoutCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TTimeoutOptions&>>
+>::TTimeoutCommandBase()
+{
+ this->RegisterParameter("timeout", this->Options.Timeout)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TTabletReadCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, TTabletTransactionOptions&>>
+>::TTabletReadCommandBase()
+{
+ this->RegisterParameter("transaction_id", this->Options.TransactionId)
+ .Optional();
+}
+
+template <class TOptions>
+NApi::IClientBasePtr TTabletReadCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, TTabletTransactionOptions&>>
+>::GetClientBase(ICommandContextPtr context)
+{
+ if (auto transactionId = this->Options.TransactionId) {
+ const auto& transactionPool = context->GetDriver()->GetStickyTransactionPool();
+ return transactionPool->GetTransactionAndRenewLeaseOrThrow(transactionId);
+ } else {
+ return context->GetClient();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TTabletWriteCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, TTabletWriteOptions&>>
+>::TTabletWriteCommandBase()
+{
+ this->RegisterParameter("atomicity", this->Options.Atomicity)
+ .Default();
+ this->RegisterParameter("durability", this->Options.Durability)
+ .Default();
+}
+
+template <class TOptions>
+NApi::ITransactionPtr TTabletWriteCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, TTabletWriteOptions&>>
+>::GetTransaction(ICommandContextPtr context)
+{
+ if (auto transactionId = this->Options.TransactionId) {
+ const auto& transactionPool = context->GetDriver()->GetStickyTransactionPool();
+ return transactionPool->GetTransactionAndRenewLeaseOrThrow(transactionId);
+ } else {
+ NApi::TTransactionStartOptions options;
+ options.Atomicity = this->Options.Atomicity;
+ options.Durability = this->Options.Durability;
+ const auto& client = context->GetClient();
+ return NConcurrency::WaitFor(client->StartTransaction(NTransactionClient::ETransactionType::Tablet, options))
+ .ValueOrThrow();
+ }
+}
+
+template <class TOptions>
+bool TTabletWriteCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, TTabletWriteOptions&>>
+>::ShouldCommitTransaction()
+{
+ return !this->Options.TransactionId;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+TSelectRowsCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TSelectRowsOptionsBase&>>
+>::TSelectRowsCommandBase()
+{
+ this->RegisterParameter("range_expansion_limit", this->Options.RangeExpansionLimit)
+ .Optional();
+ this->RegisterParameter("max_subqueries", this->Options.MaxSubqueries)
+ .GreaterThan(0)
+ .Optional();
+ this->RegisterParameter("udf_registry_path", this->Options.UdfRegistryPath)
+ .Default();
+ this->RegisterParameter("verbose_logging", this->Options.VerboseLogging)
+ .Optional();
+ this->RegisterParameter("new_range_inference", this->Options.NewRangeInference)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/command.cpp b/yt/yt/client/driver/command.cpp
new file mode 100644
index 0000000000..7271e545ae
--- /dev/null
+++ b/yt/yt/client/driver/command.cpp
@@ -0,0 +1,119 @@
+#include "command.h"
+#include "config.h"
+
+#include <yt/yt/client/object_client/helpers.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/ypath/tokenizer.h>
+
+namespace NYT::NDriver {
+
+using namespace NYPath;
+using namespace NYson;
+using namespace NYTree;
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+void ProduceOutput(
+ ICommandContextPtr context,
+ const std::function<void(IYsonConsumer*)>& producer)
+{
+ TStringStream stream;
+ TYsonWriter writer(&stream, EYsonFormat::Binary);
+ producer(&writer);
+ writer.Flush();
+ context->ProduceOutputValue(TYsonString(stream.Str()));
+}
+
+void ProduceEmptyOutput(ICommandContextPtr context)
+{
+ switch (context->GetConfig()->ApiVersion) {
+ case ApiVersion3:
+ break;
+ default:
+ context->ProduceOutputValue(NYTree::BuildYsonStringFluently().BeginMap().EndMap());
+ break;
+ }
+}
+
+void ProduceSingleOutput(
+ ICommandContextPtr context,
+ TStringBuf name,
+ const std::function<void(IYsonConsumer*)>& producer)
+{
+ switch (context->GetConfig()->ApiVersion) {
+ case ApiVersion3:
+ ProduceOutput(context, producer);
+ break;
+ default:
+ ProduceOutput(context, [&] (IYsonConsumer* consumer) {
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item(name).Do([&] (auto fluent) {
+ producer(fluent.GetConsumer());
+ })
+ .EndMap();
+ });
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCommandBase::TCommandBase()
+{
+ SetUnrecognizedStrategy(NYTree::EUnrecognizedStrategy::Keep);
+}
+
+void TCommandBase::Execute(ICommandContextPtr context)
+{
+ const auto& request = context->Request();
+ Logger.AddTag("RequestId: %" PRIx64 ", User: %v",
+ request.Id,
+ request.AuthenticatedUser);
+ Deserialize(*this, request.Parameters);
+
+ if (!HasResponseParameters()) {
+ ProduceResponseParameters(context, /* producer */ {});
+ }
+
+ DoExecute(context);
+}
+
+bool TCommandBase::HasResponseParameters() const
+{
+ return false;
+}
+
+void TCommandBase::ProduceResponseParameters(
+ ICommandContextPtr context,
+ const std::function<void(IYsonConsumer*)>& producer)
+{
+ if (producer) {
+ YT_VERIFY(HasResponseParameters());
+ producer(context->Request().ResponseParametersConsumer);
+ }
+ if (context->Request().ResponseParametersFinishedCallback) {
+ context->Request().ResponseParametersFinishedCallback();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Keep sync with yt/ytlib/scheduler/helpers.cpp.
+TYPath GetNewOperationPath(TGuid operationId)
+{
+ int hashByte = operationId.Parts32[0] & 0xff;
+ return
+ "//sys/operations/" +
+ Format("%02x", hashByte) +
+ "/" +
+ ToYPathLiteral(ToString(operationId));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/command.h b/yt/yt/client/driver/command.h
new file mode 100644
index 0000000000..669f688fe4
--- /dev/null
+++ b/yt/yt/client/driver/command.h
@@ -0,0 +1,321 @@
+#pragma once
+
+#include "private.h"
+#include "driver.h"
+
+#include <yt/yt/client/api/client.h>
+#include <yt/yt/client/api/transaction.h>
+#include <yt/yt/client/api/sticky_transaction_pool.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/ytree/yson_serializable.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ICommand
+{
+ virtual ~ICommand() = default;
+ virtual void Execute(ICommandContextPtr context) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ICommandContext
+ : public virtual TRefCounted
+{
+ virtual const TDriverConfigPtr& GetConfig() const = 0;
+ virtual const NApi::IClientPtr& GetClient() const = 0;
+ virtual const NApi::IClientPtr& GetRootClient() const = 0;
+ virtual NApi::IInternalClientPtr GetInternalClientOrThrow() const = 0;
+ virtual const IDriverPtr& GetDriver() const = 0;
+
+ virtual const TDriverRequest& Request() const = 0;
+
+ virtual const NFormats::TFormat& GetInputFormat() = 0;
+ virtual const NFormats::TFormat& GetOutputFormat() = 0;
+
+ virtual void ProduceOutputValue(const NYson::TYsonString& yson) = 0;
+ virtual NYson::TYsonString ConsumeInputValue() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ICommandContext)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Calls |context->ProduceOutputValue| with |TYsonString| created by the producer.
+void ProduceOutput(
+ ICommandContextPtr context,
+ const std::function<void(NYson::IYsonConsumer*)>& producer);
+
+//! Produces either nothing (v3) or empty map (>=v4).
+void ProduceEmptyOutput(ICommandContextPtr context);
+
+//! Run |producer| (v3) or open a map with key |name| and run |producer| then close map (>=v4).
+void ProduceSingleOutput(
+ ICommandContextPtr context,
+ TStringBuf name,
+ const std::function<void(NYson::IYsonConsumer*)>& producer);
+
+//! Produces either |value| (v3) or map {|name|=|value|} (>=v4).
+template <typename T>
+void ProduceSingleOutputValue(
+ ICommandContextPtr context,
+ TStringBuf name,
+ const T& value);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCommandBase
+ : public virtual NYTree::TYsonSerializableLite
+ , public ICommand
+{
+protected:
+ NLogging::TLogger Logger = DriverLogger;
+
+ virtual void DoExecute(ICommandContextPtr context) = 0;
+ virtual bool HasResponseParameters() const;
+
+ TCommandBase();
+
+ void ProduceResponseParameters(
+ ICommandContextPtr context,
+ const std::function<void(NYson::IYsonConsumer*)>& producer);
+
+public:
+ void Execute(ICommandContextPtr context) override;
+};
+
+template <class TOptions>
+class TTypedCommandBase
+ : public TCommandBase
+{
+protected:
+ TOptions Options;
+
+};
+
+template <class TOptions, class = void>
+class TTransactionalCommandBase
+{ };
+
+template <class TOptions>
+class TTransactionalCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TTransactionalOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TTransactionalCommandBase();
+ NApi::ITransactionPtr AttachTransaction(
+ ICommandContextPtr context,
+ bool required);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions, class = void>
+class TMutatingCommandBase
+{ };
+
+template <class TOptions>
+class TMutatingCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TMutatingOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TMutatingCommandBase();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions, class = void>
+class TReadOnlyMasterCommandBase
+{ };
+
+template <class TOptions>
+class TReadOnlyMasterCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TMasterReadOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TReadOnlyMasterCommandBase();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions, class = void>
+class TReadOnlyTabletCommandBase
+{ };
+
+template <class TOptions>
+class TReadOnlyTabletCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TTabletReadOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TReadOnlyTabletCommandBase();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions, class = void>
+class TSuppressableAccessTrackingCommandBase
+{ };
+
+template <class TOptions>
+class TSuppressableAccessTrackingCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TSuppressableAccessTrackingOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TSuppressableAccessTrackingCommandBase();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions, class = void>
+class TPrerequisiteCommandBase
+{ };
+
+template <class TOptions>
+class TPrerequisiteCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TPrerequisiteOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TPrerequisiteCommandBase();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions, class = void>
+class TTimeoutCommandBase
+{ };
+
+template <class TOptions>
+class TTimeoutCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TTimeoutOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TTimeoutCommandBase();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTabletTransactionOptions
+{
+ NTransactionClient::TTransactionId TransactionId;
+};
+
+template <class TOptions, class = void>
+class TTabletReadCommandBase
+{ };
+
+template <class TOptions>
+class TTabletReadCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, TTabletTransactionOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TTabletReadCommandBase();
+ NApi::IClientBasePtr GetClientBase(ICommandContextPtr context);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTabletWriteOptions
+ : public TTabletTransactionOptions
+{
+ NTransactionClient::EAtomicity Atomicity;
+ NTransactionClient::EDurability Durability;
+};
+
+struct TInsertRowsOptions
+ : public TTabletWriteOptions
+ , public NApi::TModifyRowsOptions
+{ };
+
+struct TDeleteRowsOptions
+ : public TTabletWriteOptions
+ , public NApi::TModifyRowsOptions
+{ };
+
+struct TLockRowsOptions
+ : public TTabletTransactionOptions
+{ };
+
+template <class TOptions, class = void>
+class TTabletWriteCommandBase
+{ };
+
+template <class TOptions>
+class TTabletWriteCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, TTabletWriteOptions&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TTabletWriteCommandBase();
+ NApi::ITransactionPtr GetTransaction(ICommandContextPtr context);
+ bool ShouldCommitTransaction();
+};
+
+template <class TOptions, class = void>
+class TSelectRowsCommandBase
+{ };
+
+template <class TOptions>
+class TSelectRowsCommandBase<
+ TOptions,
+ typename std::enable_if_t<std::is_convertible_v<TOptions&, NApi::TSelectRowsOptionsBase&>>
+>
+ : public virtual TTypedCommandBase<TOptions>
+{
+protected:
+ TSelectRowsCommandBase();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+class TTypedCommand
+ : public virtual TTypedCommandBase<TOptions>
+ , public TTransactionalCommandBase<TOptions>
+ , public TTabletReadCommandBase<TOptions>
+ , public TTabletWriteCommandBase<TOptions>
+ , public TMutatingCommandBase<TOptions>
+ , public TReadOnlyMasterCommandBase<TOptions>
+ , public TReadOnlyTabletCommandBase<TOptions>
+ , public TSuppressableAccessTrackingCommandBase<TOptions>
+ , public TPrerequisiteCommandBase<TOptions>
+ , public TTimeoutCommandBase<TOptions>
+ , public TSelectRowsCommandBase<TOptions>
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
+#define COMMAND_INL_H
+#include "command-inl.h"
+#undef COMMAND_INL_H
diff --git a/yt/yt/client/driver/config.cpp b/yt/yt/client/driver/config.cpp
new file mode 100644
index 0000000000..2fa3ed33c9
--- /dev/null
+++ b/yt/yt/client/driver/config.cpp
@@ -0,0 +1,79 @@
+#include "config.h"
+
+#include <yt/yt/client/api/config.h>
+
+#include <yt/yt/client/chunk_client/config.h>
+
+#include <yt/yt/client/table_client/config.h>
+
+#include <yt/yt/core/misc/cache_config.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TDriverConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("file_reader", &TThis::FileReader)
+ .DefaultNew();
+ registrar.Parameter("file_writer", &TThis::FileWriter)
+ .DefaultNew();
+ registrar.Parameter("table_reader", &TThis::TableReader)
+ .DefaultNew();
+ registrar.Parameter("table_writer", &TThis::TableWriter)
+ .DefaultNew();
+ registrar.Parameter("journal_reader", &TThis::JournalReader)
+ .DefaultNew();
+ registrar.Parameter("journal_writer", &TThis::JournalWriter)
+ .DefaultNew();
+ registrar.Parameter("fetcher", &TThis::Fetcher)
+ .DefaultNew();
+ registrar.Parameter("chunk_fragment_reader", &TThis::ChunkFragmentReader)
+ .DefaultNew();
+
+ registrar.Parameter("read_buffer_row_count", &TThis::ReadBufferRowCount)
+ .Default(10'000);
+ registrar.Parameter("read_buffer_size", &TThis::ReadBufferSize)
+ .Default(1_MB);
+ registrar.Parameter("write_buffer_size", &TThis::WriteBufferSize)
+ .Default(1_MB);
+
+ registrar.Parameter("client_cache", &TThis::ClientCache)
+ .DefaultNew();
+
+ registrar.Parameter("api_version", &TThis::ApiVersion)
+ .Default(ApiVersion3)
+ .GreaterThanOrEqual(ApiVersion3)
+ .LessThanOrEqual(ApiVersion4);
+
+ registrar.Parameter("token", &TThis::Token)
+ .Optional();
+
+ registrar.Parameter("proxy_discovery_cache", &TThis::ProxyDiscoveryCache)
+ .DefaultNew();
+
+ registrar.Parameter("enable_internal_commands", &TThis::EnableInternalCommands)
+ .Default(false);
+
+ registrar.Parameter("expect_structured_input_in_structured_batch_commands", &TThis::ExpectStructuredInputInStructuredBatchCommands)
+ .Default(true);
+
+ registrar.Preprocessor([] (TThis* config) {
+ config->ClientCache->Capacity = 1024_KB;
+ config->ProxyDiscoveryCache->RefreshTime = TDuration::Seconds(15);
+ config->ProxyDiscoveryCache->ExpireAfterSuccessfulUpdateTime = TDuration::Seconds(15);
+ config->ProxyDiscoveryCache->ExpireAfterFailedUpdateTime = TDuration::Seconds(15);
+ });
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->ApiVersion != ApiVersion3 && config->ApiVersion != ApiVersion4) {
+ THROW_ERROR_EXCEPTION("Unsupported API version %v",
+ config->ApiVersion);
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
diff --git a/yt/yt/client/driver/config.h b/yt/yt/client/driver/config.h
new file mode 100644
index 0000000000..1fe83c050a
--- /dev/null
+++ b/yt/yt/client/driver/config.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/client/chunk_client/public.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr int ApiVersion3 = 3;
+constexpr int ApiVersion4 = 4;
+
+class TDriverConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ NApi::TFileReaderConfigPtr FileReader;
+ NApi::TFileWriterConfigPtr FileWriter;
+ NTableClient::TTableReaderConfigPtr TableReader;
+ NTableClient::TTableWriterConfigPtr TableWriter;
+ NApi::TJournalReaderConfigPtr JournalReader;
+ NApi::TJournalWriterConfigPtr JournalWriter;
+ NChunkClient::TFetcherConfigPtr Fetcher;
+ NChunkClient::TChunkFragmentReaderConfigPtr ChunkFragmentReader;
+ int ApiVersion;
+
+ i64 ReadBufferRowCount;
+ i64 ReadBufferSize;
+ i64 WriteBufferSize;
+
+ TSlruCacheConfigPtr ClientCache;
+
+ std::optional<TString> Token;
+
+ TAsyncExpiringCacheConfigPtr ProxyDiscoveryCache;
+
+ bool EnableInternalCommands;
+
+ bool ExpectStructuredInputInStructuredBatchCommands;
+
+ REGISTER_YSON_STRUCT(TDriverConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TDriverConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
diff --git a/yt/yt/client/driver/cypress_commands.cpp b/yt/yt/client/driver/cypress_commands.cpp
new file mode 100644
index 0000000000..03a2ca791d
--- /dev/null
+++ b/yt/yt/client/driver/cypress_commands.cpp
@@ -0,0 +1,487 @@
+#include "cypress_commands.h"
+#include "config.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/client/object_client/helpers.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/ytree/helpers.h>
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NObjectClient;
+using namespace NApi;
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetCommand::TGetCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("attributes", Options.Attributes)
+ .Optional();
+ // TODO(babenko): rename to "limit"
+ RegisterParameter("max_size", Options.MaxSize)
+ .Optional();
+ RegisterParameter("return_only_value", ShouldReturnOnlyValue)
+ .Default(false);
+}
+
+void TGetCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Options = IAttributeDictionary::FromMap(GetUnrecognized());
+
+ auto asyncResult = context->GetClient()->GetNode(Path.GetPath(), Options);
+ auto result = WaitFor(asyncResult)
+ .ValueOrThrow();
+
+ // XXX(levysotsky): Python client doesn't want to parse the returned |map_node|.
+ if (ShouldReturnOnlyValue) {
+ context->ProduceOutputValue(result);
+ } else {
+ ProduceSingleOutputValue(context, "value", result);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSetCommand::TSetCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("recursive", Options.Recursive)
+ .Optional();
+ RegisterParameter("force", Options.Force)
+ .Optional();
+}
+
+void TSetCommand::DoExecute(ICommandContextPtr context)
+{
+ auto value = context->ConsumeInputValue();
+
+ auto asyncResult = context->GetClient()->SetNode(Path.GetPath(), value, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMultisetAttributesCommand::TMultisetAttributesCommand()
+{
+ RegisterParameter("path", Path);
+}
+
+void TMultisetAttributesCommand::DoExecute(ICommandContextPtr context)
+{
+ auto attributes = ConvertTo<IMapNodePtr>(context->ConsumeInputValue());
+
+ auto asyncResult = context->GetClient()->MultisetAttributesNode(Path.GetPath(), attributes, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRemoveCommand::TRemoveCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("recursive", Options.Recursive)
+ .Optional();
+ RegisterParameter("force", Options.Force)
+ .Optional();
+}
+
+void TRemoveCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->RemoveNode(
+ Path.GetPath(),
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TListCommand::TListCommand()
+{
+ RegisterParameter("path", Path);
+ // NB: default value is an empty filter in contrast to GetCommand, for which it is the universal filter.
+ // Refer to YT-5543 for details.
+ RegisterParameter("attributes", Options.Attributes)
+ .Default(TAttributeFilter({}, {}));
+ // TODO(babenko): rename to "limit"
+ RegisterParameter("max_size", Options.MaxSize)
+ .Optional();
+ RegisterParameter("return_only_value", ShouldReturnOnlyValue)
+ .Default(false);
+}
+
+void TListCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->ListNode(Path.GetPath(), Options);
+ auto result = WaitFor(asyncResult)
+ .ValueOrThrow();
+
+ // XXX(levysotsky): Python client doesn't want to parse the returned |map_node|.
+ if (ShouldReturnOnlyValue) {
+ context->ProduceOutputValue(result);
+ } else {
+ ProduceSingleOutputValue(context, "value", result);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TCreateCommand::Register(TRegistrar registrar)
+{
+ registrar.Parameter("type", &TThis::Type);
+}
+
+void TCreateCommand::Execute(ICommandContextPtr context)
+{
+ // For historical reasons, we handle both CreateNode and CreateObject requests
+ // in a single command. Here we route the request to an appropriate backend command.
+ Deserialize(*this, context->Request().Parameters);
+ auto backend = IsVersionedType(Type)
+ ? std::unique_ptr<ICommand>(new TCreateNodeCommand())
+ : std::unique_ptr<ICommand>(new TCreateObjectCommand());
+ backend->Execute(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCreateNodeCommand::TCreateNodeCommand()
+{
+ RegisterParameter("path", Path)
+ .Optional();
+ RegisterParameter("type", Type);
+ RegisterParameter("attributes", Attributes)
+ .Optional();
+ RegisterParameter("recursive", Options.Recursive)
+ .Optional();
+ RegisterParameter("ignore_existing", Options.IgnoreExisting)
+ .Optional();
+ RegisterParameter("lock_existing", Options.LockExisting)
+ .Optional();
+ RegisterParameter("force", Options.Force)
+ .Optional();
+ RegisterParameter("ignore_type_mismatch", Options.IgnoreTypeMismatch)
+ .Optional();
+}
+
+void TCreateNodeCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Attributes = Attributes
+ ? ConvertToAttributes(Attributes)
+ : CreateEphemeralAttributes();
+
+ auto asyncNodeId = context->GetClient()->CreateNode(Path.GetPath(), Type, Options);
+ auto nodeId = WaitFor(asyncNodeId)
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "node_id", nodeId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCreateObjectCommand::TCreateObjectCommand()
+{
+ RegisterParameter("type", Type);
+ RegisterParameter("ignore_existing", Options.IgnoreExisting)
+ .Optional();
+ RegisterParameter("sync", Options.Sync)
+ .Optional();
+ RegisterParameter("attributes", Attributes)
+ .Optional();
+}
+
+void TCreateObjectCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Attributes = Attributes
+ ? ConvertToAttributes(Attributes)
+ : CreateEphemeralAttributes();
+
+ auto asyncObjectId = context->GetClient()->CreateObject(
+ Type,
+ Options);
+ auto objectId = WaitFor(asyncObjectId)
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "object_id", objectId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLockCommand::TLockCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("mode", Mode)
+ .Default(NCypressClient::ELockMode::Exclusive);
+ RegisterParameter("waitable", Options.Waitable)
+ .Optional();
+ RegisterParameter("child_key", Options.ChildKey)
+ .Optional();
+ RegisterParameter("attribute_key", Options.AttributeKey)
+ .Optional();
+
+ RegisterPostprocessor([&] () {
+ if (Mode != NCypressClient::ELockMode::Shared) {
+ if (Options.ChildKey) {
+ THROW_ERROR_EXCEPTION("\"child_key\" can only be specified for shared locks");
+ }
+ if (Options.AttributeKey) {
+ THROW_ERROR_EXCEPTION("\"attribute_key\" can only be specified for shared locks");
+ }
+ }
+ if (Options.ChildKey && Options.AttributeKey) {
+ THROW_ERROR_EXCEPTION("Cannot specify both \"child_key\" and \"attribute_key\"");
+ }
+ });
+}
+
+void TLockCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncLockResult = context->GetClient()->LockNode(Path.GetPath(), Mode, Options);
+ auto lockResult = WaitFor(asyncLockResult)
+ .ValueOrThrow();
+
+ switch (context->GetConfig()->ApiVersion) {
+ case ApiVersion3:
+ ProduceSingleOutputValue(context, "lock_id", lockResult.LockId);
+ break;
+ default:
+ ProduceOutput(context, [&](NYson::IYsonConsumer* consumer) {
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("lock_id").Value(lockResult.LockId)
+ .Item("node_id").Value(lockResult.NodeId)
+ .Item("revision").Value(lockResult.Revision)
+ .EndMap();
+ });
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnlockCommand::TUnlockCommand()
+{
+ RegisterParameter("path", Path);
+}
+
+void TUnlockCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncUnlockResult = context->GetClient()->UnlockNode(Path.GetPath(), Options);
+ WaitFor(asyncUnlockResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCopyCommand::TCopyCommand()
+{
+ RegisterParameter("source_path", SourcePath);
+ RegisterParameter("destination_path", DestinationPath);
+ RegisterParameter("recursive", Options.Recursive)
+ .Optional();
+ RegisterParameter("ignore_existing", Options.IgnoreExisting)
+ .Optional();
+ RegisterParameter("lock_existing", Options.LockExisting)
+ .Optional();
+ RegisterParameter("force", Options.Force)
+ .Optional();
+ RegisterParameter("preserve_account", Options.PreserveAccount)
+ .Optional();
+ RegisterParameter("preserve_creation_time", Options.PreserveCreationTime)
+ .Optional();
+ RegisterParameter("preserve_modification_time", Options.PreserveModificationTime)
+ .Optional();
+ RegisterParameter("preserve_expiration_time", Options.PreserveExpirationTime)
+ .Optional();
+ RegisterParameter("preserve_expiration_timeout", Options.PreserveExpirationTimeout)
+ .Optional();
+ RegisterParameter("preserve_owner", Options.PreserveOwner)
+ .Optional();
+ RegisterParameter("preserve_acl", Options.PreserveAcl)
+ .Optional();
+ RegisterParameter("pessimistic_quota_check", Options.PessimisticQuotaCheck)
+ .Optional();
+}
+
+void TCopyCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncNodeId = context->GetClient()->CopyNode(
+ SourcePath.GetPath(),
+ DestinationPath.GetPath(),
+ Options);
+ auto nodeId = WaitFor(asyncNodeId)
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "node_id", nodeId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMoveCommand::TMoveCommand()
+{
+ RegisterParameter("source_path", SourcePath);
+ RegisterParameter("destination_path", DestinationPath);
+ RegisterParameter("recursive", Options.Recursive)
+ .Optional();
+ RegisterParameter("force", Options.Force)
+ .Optional();
+ RegisterParameter("preserve_account", Options.PreserveAccount)
+ .Optional();
+ RegisterParameter("preserve_creation_time", Options.PreserveCreationTime)
+ .Optional();
+ RegisterParameter("preserve_modification_time", Options.PreserveModificationTime)
+ .Optional();
+ RegisterParameter("preserve_expiration_time", Options.PreserveExpirationTime)
+ .Optional();
+ RegisterParameter("preserve_expiration_timeout", Options.PreserveExpirationTimeout)
+ .Optional();
+ RegisterParameter("preserve_owner", Options.PreserveOwner)
+ .Optional();
+ RegisterParameter("pessimistic_quota_check", Options.PessimisticQuotaCheck)
+ .Optional();
+}
+
+void TMoveCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncNodeId = context->GetClient()->MoveNode(
+ SourcePath.GetPath(),
+ DestinationPath.GetPath(),
+ Options);
+ auto nodeId = WaitFor(asyncNodeId)
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "node_id", nodeId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TExistsCommand::TExistsCommand()
+{
+ RegisterParameter("path", Path);
+}
+
+void TExistsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->NodeExists(Path.GetPath(), Options);
+ auto result = WaitFor(asyncResult)
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "value", result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLinkCommand::TLinkCommand()
+{
+ RegisterParameter("link_path", LinkPath);
+ RegisterParameter("target_path", TargetPath);
+ RegisterParameter("attributes", Attributes)
+ .Optional();
+ RegisterParameter("recursive", Options.Recursive)
+ .Optional();
+ RegisterParameter("ignore_existing", Options.IgnoreExisting)
+ .Optional();
+ RegisterParameter("lock_existing", Options.LockExisting)
+ .Optional();
+ RegisterParameter("force", Options.Force)
+ .Optional();
+}
+
+void TLinkCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Attributes = Attributes
+ ? ConvertToAttributes(Attributes)
+ : CreateEphemeralAttributes();
+
+ auto asyncNodeId = context->GetClient()->LinkNode(
+ TargetPath.GetPath(),
+ LinkPath.GetPath(),
+ Options);
+ auto nodeId = WaitFor(asyncNodeId)
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "node_id", nodeId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TConcatenateCommand::TConcatenateCommand()
+{
+ RegisterParameter("source_paths", SourcePaths);
+ RegisterParameter("destination_path", DestinationPath);
+
+ RegisterParameter("uniqualize_chunks", Options.UniqualizeChunks)
+ .Default(false);
+}
+
+void TConcatenateCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.ChunkMetaFetcherConfig = context->GetConfig()->Fetcher;
+
+ auto asyncResult = context->GetClient()->ConcatenateNodes(
+ SourcePaths,
+ DestinationPath,
+ Options);
+
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TExternalizeCommand::TExternalizeCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("cell_tag", CellTag);
+}
+
+void TExternalizeCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->ExternalizeNode(
+ Path,
+ CellTag,
+ Options);
+
+ WaitFor(asyncResult)
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TInternalizeCommand::TInternalizeCommand()
+{
+ RegisterParameter("path", Path);
+}
+
+void TInternalizeCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->InternalizeNode(
+ Path,
+ Options);
+
+ WaitFor(asyncResult)
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/cypress_commands.h b/yt/yt/client/driver/cypress_commands.h
new file mode 100644
index 0000000000..52bcd36097
--- /dev/null
+++ b/yt/yt/client/driver/cypress_commands.h
@@ -0,0 +1,266 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/cypress_client/public.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetCommand
+ : public TTypedCommand<NApi::TGetNodeOptions>
+{
+public:
+ TGetCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ bool ShouldReturnOnlyValue;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSetCommand
+ : public TTypedCommand<NApi::TSetNodeOptions>
+{
+public:
+ TSetCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMultisetAttributesCommand
+ : public TTypedCommand<NApi::TMultisetAttributesNodeOptions>
+{
+public:
+ TMultisetAttributesCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoveCommand
+ : public TTypedCommand<NApi::TRemoveNodeOptions>
+{
+public:
+ TRemoveCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TListCommand
+ : public TTypedCommand<NApi::TListNodeOptions>
+{
+public:
+ TListCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ bool ShouldReturnOnlyValue;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCreateCommand
+ : public NYTree::TYsonStructLite
+{
+public:
+ void Execute(ICommandContextPtr context);
+
+ REGISTER_YSON_STRUCT_LITE(TCreateCommand);
+
+ static void Register(TRegistrar);
+
+private:
+ NObjectClient::EObjectType Type;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCreateNodeCommand
+ : public TTypedCommand<NApi::TCreateNodeOptions>
+{
+public:
+ TCreateNodeCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NObjectClient::EObjectType Type;
+ NYTree::INodePtr Attributes;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCreateObjectCommand
+ : public TTypedCommand<NApi::TCreateObjectOptions>
+{
+public:
+ TCreateObjectCommand();
+
+private:
+ NObjectClient::EObjectType Type;
+ NYTree::INodePtr Attributes;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLockCommand
+ : public TTypedCommand<NApi::TLockNodeOptions>
+{
+public:
+ TLockCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NCypressClient::ELockMode Mode;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnlockCommand
+ : public TTypedCommand<NApi::TUnlockNodeOptions>
+{
+public:
+ TUnlockCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCopyCommand
+ : public TTypedCommand<NApi::TCopyNodeOptions>
+{
+public:
+ TCopyCommand();
+
+private:
+ NYPath::TRichYPath SourcePath;
+ NYPath::TRichYPath DestinationPath;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMoveCommand
+ : public TTypedCommand<NApi::TMoveNodeOptions>
+{
+public:
+ TMoveCommand();
+
+private:
+ NYPath::TRichYPath SourcePath;
+ NYPath::TRichYPath DestinationPath;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TExistsCommand
+ : public TTypedCommand<NApi::TNodeExistsOptions>
+{
+public:
+ TExistsCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLinkCommand
+ : public TTypedCommand<NApi::TLinkNodeOptions>
+{
+public:
+ TLinkCommand();
+
+private:
+ NYPath::TRichYPath LinkPath;
+ NYPath::TRichYPath TargetPath;
+ NYTree::INodePtr Attributes;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TConcatenateCommand
+ : public TTypedCommand<NApi::TConcatenateNodesOptions>
+{
+public:
+ TConcatenateCommand();
+
+private:
+ std::vector<NYPath::TRichYPath> SourcePaths;
+ NYPath::TRichYPath DestinationPath;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TExternalizeCommand
+ : public TTypedCommand<NApi::TExternalizeNodeOptions>
+{
+public:
+ TExternalizeCommand();
+
+private:
+ NYPath::TYPath Path;
+ NObjectClient::TCellTag CellTag;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TInternalizeCommand
+ : public TTypedCommand<NApi::TInternalizeNodeOptions>
+{
+public:
+ TInternalizeCommand();
+
+private:
+ NYPath::TYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
diff --git a/yt/yt/client/driver/driver.cpp b/yt/yt/client/driver/driver.cpp
new file mode 100644
index 0000000000..58e7749cee
--- /dev/null
+++ b/yt/yt/client/driver/driver.cpp
@@ -0,0 +1,683 @@
+#include "driver.h"
+
+#include "authentication_commands.h"
+#include "chaos_commands.h"
+#include "command.h"
+#include "config.h"
+#include "cypress_commands.h"
+#include "admin_commands.h"
+#include "etc_commands.h"
+#include "file_commands.h"
+#include "journal_commands.h"
+#include "queue_commands.h"
+#include "scheduler_commands.h"
+#include "table_commands.h"
+#include "transaction_commands.h"
+#include "internal_commands.h"
+#include "proxy_discovery_cache.h"
+#include "query_commands.h"
+
+#include <yt/yt/client/api/transaction.h>
+#include <yt/yt/client/api/connection.h>
+#include <yt/yt/client/api/sticky_transaction_pool.h>
+#include <yt/yt/client/api/client_cache.h>
+
+#include <yt/yt/client/api/rpc_proxy/connection_impl.h>
+
+#include <yt/yt/client/node_tracker_client/node_directory.h>
+
+#include <yt/yt/core/yson/null_consumer.h>
+
+#include <yt/yt/core/tracing/trace_context.h>
+
+#include <yt/yt/library/tvm/tvm_base.h>
+
+namespace NYT::NDriver {
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NRpc;
+using namespace NElection;
+using namespace NTransactionClient;
+using namespace NChunkClient;
+using namespace NScheduler;
+using namespace NFormats;
+using namespace NSecurityClient;
+using namespace NConcurrency;
+using namespace NHydra;
+using namespace NHiveClient;
+using namespace NTabletClient;
+using namespace NApi;
+using namespace NNodeTrackerClient;
+using namespace NTracing;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto& Logger = DriverLogger;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TCommandDescriptor& descriptor, NYson::IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("name").Value(descriptor.CommandName)
+ .Item("input_type").Value(descriptor.InputType)
+ .Item("output_type").Value(descriptor.OutputType)
+ .Item("is_volatile").Value(descriptor.Volatile)
+ .Item("is_heavy").Value(descriptor.Heavy)
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDriverRequest::TDriverRequest()
+ : ResponseParametersConsumer(GetNullYsonConsumer())
+{ }
+
+TDriverRequest::TDriverRequest(THolderPtr holder)
+ : ResponseParametersConsumer(GetNullYsonConsumer())
+ , Holder_(std::move(holder))
+{ }
+
+void TDriverRequest::Reset()
+{
+ Holder_.Reset();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCommandDescriptor IDriver::GetCommandDescriptor(const TString& commandName) const
+{
+ auto descriptor = FindCommandDescriptor(commandName);
+ YT_VERIFY(descriptor);
+ return *descriptor;
+}
+
+TCommandDescriptor IDriver::GetCommandDescriptorOrThrow(const TString& commandName) const
+{
+ auto descriptor = FindCommandDescriptor(commandName);
+ if (!descriptor) {
+ THROW_ERROR_EXCEPTION("Unknown command %Qv", commandName);
+ }
+ return *descriptor;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+class TDriver
+ : public IDriver
+{
+public:
+ TDriver(TDriverConfigPtr config, IConnectionPtr connection)
+ : Config_(std::move(config))
+ , Connection_(std::move(connection))
+ , ClientCache_(New<TClientCache>(Config_->ClientCache, Connection_))
+ , RootClient_(ClientCache_->Get(
+ GetRootAuthenticationIdentity(),
+ TClientOptions::FromAuthenticationIdentity(GetRootAuthenticationIdentity())))
+ , ProxyDiscoveryCache_(CreateProxyDiscoveryCache(
+ Config_->ProxyDiscoveryCache,
+ RootClient_))
+ , StickyTransactionPool_(CreateStickyTransactionPool(Logger))
+ {
+ // Register all commands.
+#define REGISTER(command, name, inDataType, outDataType, isVolatile, isHeavy, version) \
+ if (version == Config_->ApiVersion) { \
+ RegisterCommand<command>( \
+ TCommandDescriptor{name, EDataType::inDataType, EDataType::outDataType, isVolatile, isHeavy}); \
+ }
+#define REGISTER_ALL(command, name, inDataType, outDataType, isVolatile, isHeavy) \
+ RegisterCommand<command>( \
+ TCommandDescriptor{name, EDataType::inDataType, EDataType::outDataType, isVolatile, isHeavy}); \
+
+ REGISTER (TStartTransactionCommand, "start_tx", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TPingTransactionCommand, "ping_tx", Null, Null, true, false, ApiVersion3);
+ REGISTER (TCommitTransactionCommand, "commit_tx", Null, Null, true, false, ApiVersion3);
+ REGISTER (TAbortTransactionCommand, "abort_tx", Null, Null, true, false, ApiVersion3);
+
+ REGISTER (TStartTransactionCommand, "start_transaction", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TPingTransactionCommand, "ping_transaction", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TCommitTransactionCommand, "commit_transaction", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TAbortTransactionCommand, "abort_transaction", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER_ALL(TGenerateTimestampCommand, "generate_timestamp", Null, Structured, false, false);
+
+ REGISTER_ALL(TCreateCommand, "create", Null, Structured, true, false);
+ REGISTER_ALL(TGetCommand, "get", Null, Structured, false, false);
+ REGISTER_ALL(TListCommand, "list", Null, Structured, false, false);
+ REGISTER_ALL(TLockCommand, "lock", Null, Structured, true, false);
+
+ REGISTER (TUnlockCommand, "unlock", Null, Null, true, false, ApiVersion3);
+ REGISTER (TUnlockCommand, "unlock", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER_ALL(TCopyCommand, "copy", Null, Structured, true, false);
+ REGISTER_ALL(TMoveCommand, "move", Null, Structured, true, false);
+ REGISTER_ALL(TLinkCommand, "link", Null, Structured, true, false);
+ REGISTER_ALL(TExistsCommand, "exists", Null, Structured, false, false);
+
+ REGISTER (TConcatenateCommand, "concatenate", Null, Null, true, false, ApiVersion3);
+ REGISTER (TRemoveCommand, "remove", Null, Null, true, false, ApiVersion3);
+ REGISTER (TSetCommand, "set", Structured, Null, true, false, ApiVersion3);
+
+ REGISTER (TConcatenateCommand, "concatenate", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TRemoveCommand, "remove", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TSetCommand, "set", Structured, Structured, true, false, ApiVersion4);
+ REGISTER (TMultisetAttributesCommand, "multiset_attributes", Structured, Structured, true, false, ApiVersion4);
+ REGISTER (TExternalizeCommand, "externalize", Null, Null, true, false, ApiVersion4);
+ REGISTER (TInternalizeCommand, "internalize", Null, Null, true, false, ApiVersion4);
+
+ REGISTER (TWriteFileCommand, "write_file", Binary, Null, true, true, ApiVersion3);
+ REGISTER (TWriteFileCommand, "write_file", Binary, Structured, true, true, ApiVersion4);
+ REGISTER_ALL(TReadFileCommand, "read_file", Null, Binary, false, true );
+
+ REGISTER_ALL(TGetFileFromCacheCommand, "get_file_from_cache", Null, Structured, false, false);
+ REGISTER_ALL(TPutFileToCacheCommand, "put_file_to_cache", Null, Structured, true, false);
+
+ REGISTER (TWriteTableCommand, "write_table", Tabular, Null, true, true , ApiVersion3);
+ REGISTER (TWriteTableCommand, "write_table", Tabular, Structured, true, true , ApiVersion4);
+ REGISTER_ALL(TGetTableColumnarStatisticsCommand, "get_table_columnar_statistics", Null, Structured, false, false);
+ REGISTER_ALL(TReadTableCommand, "read_table", Null, Tabular, false, true );
+ REGISTER_ALL(TReadBlobTableCommand, "read_blob_table", Null, Binary, false, true );
+ REGISTER_ALL(TLocateSkynetShareCommand, "locate_skynet_share", Null, Structured, false, true );
+
+ REGISTER_ALL(TPartitionTablesCommand, "partition_tables", Null, Structured, false, false);
+
+ REGISTER (TInsertRowsCommand, "insert_rows", Tabular, Null, true, true , ApiVersion3);
+ REGISTER (TLockRowsCommand, "lock_rows", Tabular, Null, true, true , ApiVersion3);
+ REGISTER (TDeleteRowsCommand, "delete_rows", Tabular, Null, true, true , ApiVersion3);
+ REGISTER (TTrimRowsCommand, "trim_rows", Null, Null, true, true , ApiVersion3);
+
+ REGISTER (TInsertRowsCommand, "insert_rows", Tabular, Structured, true, true , ApiVersion4);
+ REGISTER (TLockRowsCommand, "lock_rows", Tabular, Structured, true, true , ApiVersion4);
+ REGISTER (TDeleteRowsCommand, "delete_rows", Tabular, Structured, true, true , ApiVersion4);
+ REGISTER (TTrimRowsCommand, "trim_rows", Null, Structured, true, true , ApiVersion4);
+
+ REGISTER_ALL(TExplainQueryCommand, "explain_query", Null, Structured, false, true );
+ REGISTER_ALL(TSelectRowsCommand, "select_rows", Null, Tabular, false, true );
+ REGISTER_ALL(TLookupRowsCommand, "lookup_rows", Tabular, Tabular, false, true );
+ REGISTER_ALL(TPullRowsCommand, "pull_rows", Null, Tabular, false, true );
+
+ REGISTER (TEnableTableReplicaCommand, "enable_table_replica", Null, Null, true, false, ApiVersion3);
+ REGISTER (TDisableTableReplicaCommand, "disable_table_replica", Null, Null, true, false, ApiVersion3);
+ REGISTER (TAlterTableReplicaCommand, "alter_table_replica", Null, Null, true, false, ApiVersion3);
+
+ REGISTER (TEnableTableReplicaCommand, "enable_table_replica", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TDisableTableReplicaCommand, "disable_table_replica", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TAlterTableReplicaCommand, "alter_table_replica", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER_ALL(TGetInSyncReplicasCommand, "get_in_sync_replicas", Tabular, Structured, false, true );
+
+ REGISTER (TMountTableCommand, "mount_table", Null, Null, true, false, ApiVersion3);
+ REGISTER (TUnmountTableCommand, "unmount_table", Null, Null, true, false, ApiVersion3);
+ REGISTER (TRemountTableCommand, "remount_table", Null, Null, true, false, ApiVersion3);
+ REGISTER (TFreezeTableCommand, "freeze_table", Null, Null, true, false, ApiVersion3);
+ REGISTER (TUnfreezeTableCommand, "unfreeze_table", Null, Null, true, false, ApiVersion3);
+ REGISTER (TReshardTableCommand, "reshard_table", Null, Null, true, false, ApiVersion3);
+ REGISTER (TAlterTableCommand, "alter_table", Null, Null, true, false, ApiVersion3);
+
+ REGISTER (TMountTableCommand, "mount_table", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TUnmountTableCommand, "unmount_table", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TRemountTableCommand, "remount_table", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TFreezeTableCommand, "freeze_table", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TUnfreezeTableCommand, "unfreeze_table", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TReshardTableCommand, "reshard_table", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TAlterTableCommand, "alter_table", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER (TGetTablePivotKeysCommand, "get_table_pivot_keys", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TGetTabletInfosCommand, "get_tablet_infos", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TGetTabletErrorsCommand, "get_tablet_errors", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER (TCreateTableBackupCommand, "create_table_backup", Null, Null, true, false, ApiVersion3);
+ REGISTER (TRestoreTableBackupCommand, "restore_table_backup", Null, Null, true, false, ApiVersion3);
+ REGISTER (TCreateTableBackupCommand, "create_table_backup", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TRestoreTableBackupCommand, "restore_table_backup", Null, Structured, true, false, ApiVersion4);
+
+
+ REGISTER_ALL(TReshardTableAutomaticCommand, "reshard_table_automatic", Null, Structured, true, false);
+ REGISTER_ALL(TBalanceTabletCellsCommand, "balance_tablet_cells", Null, Structured, true, false);
+
+ REGISTER (TUpdateChaosTableReplicaProgressCommand, "update_replication_progress", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TAlterReplicationCardCommand, "alter_replication_card", Null, Structured, false, false, ApiVersion4);
+
+ REGISTER (TMergeCommand, "merge", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TEraseCommand, "erase", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TMapCommand, "map", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TSortCommand, "sort", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TReduceCommand, "reduce", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TJoinReduceCommand, "join_reduce", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TMapReduceCommand, "map_reduce", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TRemoteCopyCommand, "remote_copy", Null, Structured, true, false, ApiVersion3);
+
+ REGISTER (TStartOperationCommand, "start_op", Null, Structured, true, false, ApiVersion3);
+ REGISTER (TAbortOperationCommand, "abort_op", Null, Null, true, false, ApiVersion3);
+ REGISTER (TSuspendOperationCommand, "suspend_op", Null, Null, true, false, ApiVersion3);
+ REGISTER (TResumeOperationCommand, "resume_op", Null, Null, true, false, ApiVersion3);
+ REGISTER (TCompleteOperationCommand, "complete_op", Null, Null, true, false, ApiVersion3);
+ REGISTER (TUpdateOperationParametersCommand, "update_op_parameters", Null, Null, true, false, ApiVersion3);
+
+ REGISTER (TStartOperationCommand, "start_operation", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TAbortOperationCommand, "abort_operation", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TSuspendOperationCommand, "suspend_operation", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TResumeOperationCommand, "resume_operation", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TCompleteOperationCommand, "complete_operation", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TUpdateOperationParametersCommand, "update_operation_parameters", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER_ALL(TParseYPathCommand, "parse_ypath", Null, Structured, false, false);
+
+ REGISTER (TAddMemberCommand, "add_member", Null, Null, true, false, ApiVersion3);
+ REGISTER (TRemoveMemberCommand, "remove_member", Null, Null, true, false, ApiVersion3);
+
+ REGISTER (TAddMemberCommand, "add_member", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TRemoveMemberCommand, "remove_member", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER_ALL(TCheckPermissionCommand, "check_permission", Null, Structured, false, false);
+ REGISTER_ALL(TCheckPermissionByAclCommand, "check_permission_by_acl", Null, Structured, false, false);
+
+ REGISTER (TTransferAccountResourcesCommand, "transfer_account_resources", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TTransferPoolResourcesCommand, "transfer_pool_resources", Null, Structured, true, false, ApiVersion4);
+
+ REGISTER (TWriteJournalCommand, "write_journal", Tabular, Null, true, true , ApiVersion3);
+ REGISTER (TWriteJournalCommand, "write_journal", Tabular, Structured, true, true , ApiVersion4);
+ REGISTER_ALL(TReadJournalCommand, "read_journal", Null, Tabular, false, true );
+ REGISTER (TTruncateJournalCommand, "truncate_journal", Null, Null, true, false, ApiVersion4);
+
+ REGISTER_ALL(TGetJobInputCommand, "get_job_input", Null, Binary, false, true );
+ REGISTER_ALL(TGetJobInputPathsCommand, "get_job_input_paths", Null, Structured, false, true );
+ REGISTER_ALL(TGetJobStderrCommand, "get_job_stderr", Null, Binary, false, true );
+ REGISTER_ALL(TGetJobFailContextCommand, "get_job_fail_context", Null, Binary, false, true );
+ REGISTER_ALL(TGetJobSpecCommand, "get_job_spec", Null, Structured, false, true );
+ REGISTER_ALL(TListOperationsCommand, "list_operations", Null, Structured, false, false);
+ REGISTER_ALL(TListJobsCommand, "list_jobs", Null, Structured, false, false);
+ REGISTER_ALL(TGetJobCommand, "get_job", Null, Structured, false, false);
+ REGISTER_ALL(TPollJobShellCommand, "poll_job_shell", Null, Structured, true, false);
+ REGISTER_ALL(TGetOperationCommand, "get_operation", Null, Structured, false, false);
+
+ REGISTER (TDumpJobContextCommand, "dump_job_context", Null, Null, true, false, ApiVersion3);
+ REGISTER (TAbandonJobCommand, "abandon_job", Null, Null, false, false, ApiVersion3);
+ REGISTER (TAbortJobCommand, "abort_job", Null, Null, false, false, ApiVersion3);
+
+ REGISTER (TDumpJobContextCommand, "dump_job_context", Null, Structured, true, false, ApiVersion4);
+ REGISTER (TAbandonJobCommand, "abandon_job", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TAbortJobCommand, "abort_job", Null, Structured, false, false, ApiVersion4);
+
+ REGISTER_ALL(TGetVersionCommand, "get_version", Null, Structured, false, false);
+ REGISTER_ALL(TGetSupportedFeaturesCommand, "get_supported_features", Null, Structured, false, false);
+
+ REGISTER_ALL(TExecuteBatchCommand, "execute_batch", Null, Structured, true, false);
+
+ REGISTER (TDiscoverProxiesCommand, "discover_proxies", Null, Structured, false, false, ApiVersion4);
+
+ REGISTER_ALL(TBuildSnapshotCommand, "build_snapshot", Null, Structured, true, false);
+ REGISTER_ALL(TBuildMasterSnapshotsCommand, "build_master_snapshots", Null, Structured, true, false);
+ REGISTER_ALL(TSwitchLeaderCommand, "switch_leader", Null, Structured, true, false);
+ REGISTER_ALL(TResetStateHashCommand, "reset_state_hash", Null, Structured, true, false);
+ REGISTER_ALL(THealExecNodeCommand, "heal_exec_node", Null, Structured, true, false);
+
+ REGISTER_ALL(TSuspendCoordinatorCommand, "suspend_coordinator", Null, Structured, false, false);
+ REGISTER_ALL(TResumeCoordinatorCommand, "resume_coordinator", Null, Structured, false, false);
+ REGISTER_ALL(TMigrateReplicationCardsCommand, "migrate_replication_cards", Null, Structured, false, false);
+ REGISTER_ALL(TSuspendChaosCellsCommand, "suspend_chaos_cells", Null, Structured, false, false);
+ REGISTER_ALL(TResumeChaosCellsCommand, "resume_chaos_cells", Null, Structured, false, false);
+
+ REGISTER_ALL(TSuspendTabletCellsCommand, "suspend_tablet_cells", Null, Structured, false, false);
+ REGISTER_ALL(TResumeTabletCellsCommand, "resume_tablet_cells", Null, Structured, false, false);
+
+ REGISTER_ALL(TAddMaintenanceCommand, "add_maintenance", Null, Structured, true, false);
+ REGISTER_ALL(TRemoveMaintenanceCommand, "remove_maintenance", Null, Structured, true, false);
+ REGISTER_ALL(TDisableChunkLocationsCommand, "disable_chunk_locations", Null, Structured, false, false);
+ REGISTER_ALL(TDestroyChunkLocationsCommand, "destroy_chunk_locations", Null, Structured, false, false);
+ REGISTER_ALL(TResurrectChunkLocationsCommand, "resurrect_chunk_locations", Null, Structured, false, false);
+ REGISTER_ALL(TRequestRebootCommand, "request_reboot", Null, Structured, false, false);
+
+ REGISTER_ALL(TSetUserPasswordCommand, "set_user_password", Null, Structured, false, false);
+ REGISTER_ALL(TIssueTokenCommand, "issue_token", Null, Structured, false, false);
+ REGISTER_ALL(TRevokeTokenCommand, "revoke_token", Null, Structured, false, false);
+ REGISTER_ALL(TListUserTokensCommand, "list_user_tokens", Null, Structured, false, false);
+
+ REGISTER (TRegisterQueueConsumerCommand, "register_queue_consumer", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TUnregisterQueueConsumerCommand, "unregister_queue_consumer", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TListQueueConsumerRegistrationsCommand, "list_queue_consumer_registrations", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TPullQueueCommand, "pull_queue", Null, Tabular, false, true, ApiVersion4);
+ REGISTER (TPullConsumerCommand, "pull_consumer", Null, Tabular, false, true, ApiVersion4);
+ REGISTER (TAdvanceConsumerCommand, "advance_consumer", Null, Structured, true, true, ApiVersion4);
+
+ REGISTER (TStartQueryCommand, "start_query", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TAbortQueryCommand, "abort_query", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TGetQueryCommand, "get_query", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TListQueriesCommand, "list_queries", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TGetQueryResultCommand, "get_query_result", Null, Structured, false, false, ApiVersion4);
+ REGISTER (TReadQueryResultCommand, "read_query_result", Null, Tabular, false, true, ApiVersion4);
+ REGISTER (TAlterQueryCommand, "alter_query", Null, Tabular, false, false, ApiVersion4);
+
+ if (Config_->EnableInternalCommands) {
+ REGISTER_ALL(TReadHunksCommand, "read_hunks", Null, Structured, false, true );
+ REGISTER_ALL(TWriteHunksCommand, "write_hunks", Null, Structured, false, true );
+ REGISTER_ALL(TLockHunkStoreCommand, "lock_hunk_store", Null, Structured, false, true );
+ REGISTER_ALL(TUnlockHunkStoreCommand, "unlock_hunk_store", Null, Structured, false, true );
+ REGISTER_ALL(TGetConnectionConfigCommand, "get_connection_config", Null, Structured, false, false);
+ }
+
+#undef REGISTER
+#undef REGISTER_ALL
+ }
+
+ TFuture<void> Execute(const TDriverRequest& request) override
+ {
+ auto traceContext = CreateTraceContextFromCurrent("Driver");
+ TTraceContextGuard guard(std::move(traceContext));
+
+ auto it = CommandNameToEntry_.find(request.CommandName);
+ if (it == CommandNameToEntry_.end()) {
+ return MakeFuture(TError(
+ "Unknown command %Qv",
+ request.CommandName));
+ }
+
+ const auto& entry = it->second;
+ TAuthenticationIdentity identity(
+ request.AuthenticatedUser,
+ request.UserTag.value_or(""));
+
+ YT_VERIFY(entry.Descriptor.InputType == EDataType::Null || request.InputStream);
+ YT_VERIFY(entry.Descriptor.OutputType == EDataType::Null || request.OutputStream);
+
+ YT_LOG_DEBUG("Command received (RequestId: %" PRIx64 ", Command: %v, User: %v)",
+ request.Id,
+ request.CommandName,
+ identity.User);
+
+ auto options = TClientOptions::FromAuthenticationIdentity(identity);
+ options.Token = request.UserToken;
+ options.ServiceTicketAuth = request.ServiceTicket
+ ? std::make_optional(New<NAuth::TServiceTicketFixedAuth>(*request.ServiceTicket))
+ : std::nullopt;
+
+ auto client = ClientCache_->Get(identity, options);
+
+ auto context = New<TCommandContext>(
+ this,
+ std::move(client),
+ RootClient_,
+ Config_,
+ entry.Descriptor,
+ request);
+
+ return BIND(&TDriver::DoExecute, entry.Execute, context)
+ .AsyncVia(Connection_->GetInvoker())
+ .Run();
+ }
+
+ std::optional<TCommandDescriptor> FindCommandDescriptor(const TString& commandName) const override
+ {
+ auto it = CommandNameToEntry_.find(commandName);
+ return it == CommandNameToEntry_.end() ? std::nullopt : std::make_optional(it->second.Descriptor);
+ }
+
+ const std::vector<TCommandDescriptor> GetCommandDescriptors() const override
+ {
+ std::vector<TCommandDescriptor> result;
+ result.reserve(CommandNameToEntry_.size());
+ for (const auto& [name, entry] : CommandNameToEntry_) {
+ result.push_back(entry.Descriptor);
+ }
+ return result;
+ }
+
+ void ClearMetadataCaches() override
+ {
+ ClientCache_->Clear();
+ Connection_->ClearMetadataCaches();
+ }
+
+ IStickyTransactionPoolPtr GetStickyTransactionPool() override
+ {
+ return StickyTransactionPool_;
+ }
+
+ IProxyDiscoveryCachePtr GetProxyDiscoveryCache() override
+ {
+ return ProxyDiscoveryCache_;
+ }
+
+ IConnectionPtr GetConnection() override
+ {
+ return Connection_;
+ }
+
+ void Terminate() override
+ {
+ // TODO(ignat): find and eliminate reference loop.
+ // Reset of the connection should be sufficient to release this connection.
+ // But there is some reference loop and it does not work.
+
+ ClearMetadataCaches();
+
+ // Release the connection with entire thread pools.
+ if (Connection_) {
+ Connection_->Terminate();
+ ClientCache_.Reset();
+ ProxyDiscoveryCache_.Reset();
+ Connection_.Reset();
+ }
+ }
+
+private:
+ const TDriverConfigPtr Config_;
+ IConnectionPtr Connection_;
+ TClientCachePtr ClientCache_;
+ const IClientPtr RootClient_;
+ IProxyDiscoveryCachePtr ProxyDiscoveryCache_;
+
+ class TCommandContext;
+ typedef TIntrusivePtr<TCommandContext> TCommandContextPtr;
+ typedef TCallback<void(ICommandContextPtr)> TExecuteCallback;
+
+ const IStickyTransactionPoolPtr StickyTransactionPool_;
+
+ struct TCommandEntry
+ {
+ TCommandDescriptor Descriptor;
+ TExecuteCallback Execute;
+ };
+
+ THashMap<TString, TCommandEntry> CommandNameToEntry_;
+
+
+ template <class TCommand>
+ void RegisterCommand(const TCommandDescriptor& descriptor)
+ {
+ TCommandEntry entry;
+ entry.Descriptor = descriptor;
+ entry.Execute = BIND_NO_PROPAGATE([] (ICommandContextPtr context) {
+ if constexpr (std::is_convertible<TCommand*, TYsonStructLite*>::value) {
+ TCommand command = TCommand::Create();
+ command.Execute(context);
+ } else {
+ TCommand command;
+ command.Execute(context);
+ }
+ });
+ YT_VERIFY(CommandNameToEntry_.emplace(descriptor.CommandName, entry).second);
+ }
+
+ static void DoExecute(TExecuteCallback executeCallback, TCommandContextPtr context)
+ {
+ const auto& request = context->Request();
+
+ auto Logger = DriverLogger;
+ if (request.LoggingTags) {
+ Logger.WithRawTag(*request.LoggingTags);
+ }
+
+ NTracing::TChildTraceContextGuard commandSpan(ConcatToString(TStringBuf("Driver:"), request.CommandName));
+ NTracing::AnnotateTraceContext([&] (const auto& traceContext) {
+ traceContext->AddTag("user", request.AuthenticatedUser);
+ traceContext->AddTag("request_id", request.Id);
+ });
+
+ YT_LOG_DEBUG("Command started (RequestId: %" PRIx64 ", Command: %v, User: %v)",
+ request.Id,
+ request.CommandName,
+ request.AuthenticatedUser);
+
+ TError result;
+ try {
+ executeCallback.Run(context);
+ } catch (const std::exception& ex) {
+ result = TError(ex);
+ }
+
+ if (result.IsOK()) {
+ YT_LOG_DEBUG("Command completed (RequestId: %" PRIx64 ", Command: %v, User: %v)",
+ request.Id,
+ request.CommandName,
+ request.AuthenticatedUser);
+ } else {
+ YT_LOG_DEBUG(result, "Command failed (RequestId: %" PRIx64 ", Command: %v, User: %v)",
+ request.Id,
+ request.CommandName,
+ request.AuthenticatedUser);
+ }
+
+ context->Finish();
+
+ THROW_ERROR_EXCEPTION_IF_FAILED(result);
+ }
+
+ class TCommandContext
+ : public ICommandContext
+ {
+ public:
+ TCommandContext(
+ IDriverPtr driver,
+ IClientPtr client,
+ IClientPtr rootClient,
+ TDriverConfigPtr config,
+ TCommandDescriptor descriptor,
+ const TDriverRequest& request)
+ : Driver_(std::move(driver))
+ , Client_(std::move(client))
+ , RootClient_(std::move(rootClient))
+ , Config_(std::move(config))
+ , Descriptor_(std::move(descriptor))
+ , Request_(std::move(request))
+ { }
+
+ const TDriverConfigPtr& GetConfig() const override
+ {
+ return Config_;
+ }
+
+ const IClientPtr& GetClient() const override
+ {
+ return Client_;
+ }
+
+ const IClientPtr& GetRootClient() const override
+ {
+ return RootClient_;
+ }
+
+ IInternalClientPtr GetInternalClientOrThrow() const override
+ {
+ auto internalClient = DynamicPointerCast<IInternalClient>(Client_);
+ if (!internalClient) {
+ THROW_ERROR_EXCEPTION("Client does not support internal API");
+ }
+ return internalClient;
+ }
+
+ const IDriverPtr& GetDriver() const override
+ {
+ return Driver_;
+ }
+
+ const TDriverRequest& Request() const override
+ {
+ return Request_;
+ }
+
+ const TFormat& GetInputFormat() override
+ {
+ if (!InputFormat_) {
+ InputFormat_ = ConvertTo<TFormat>(Request_.Parameters->GetChildOrThrow("input_format"));
+ }
+ return *InputFormat_;
+ }
+
+ const TFormat& GetOutputFormat() override
+ {
+ if (!OutputFormat_) {
+ OutputFormat_ = ConvertTo<TFormat>(Request_.Parameters->GetChildOrThrow("output_format"));
+ }
+ return *OutputFormat_;
+ }
+
+ TYsonString ConsumeInputValue() override
+ {
+ YT_VERIFY(Request_.InputStream);
+ auto syncInputStream = CreateSyncAdapter(Request_.InputStream);
+
+ auto producer = CreateProducerForFormat(
+ GetInputFormat(),
+ Descriptor_.InputType,
+ syncInputStream.get());
+
+ return ConvertToYsonString(producer);
+ }
+
+ void ProduceOutputValue(const TYsonString& yson) override
+ {
+ YT_VERIFY(Request_.OutputStream);
+ auto syncOutputStream = CreateBufferedSyncAdapter(Request_.OutputStream);
+
+ auto consumer = CreateConsumerForFormat(
+ GetOutputFormat(),
+ Descriptor_.OutputType,
+ syncOutputStream.get());
+
+ Serialize(yson, consumer.get());
+
+ consumer->Flush();
+ syncOutputStream->Flush();
+ }
+
+ void Finish()
+ {
+ Request_.Reset();
+ }
+
+ private:
+ const IDriverPtr Driver_;
+ const IClientPtr Client_;
+ const IClientPtr RootClient_;
+ const TDriverConfigPtr Config_;
+ const TCommandDescriptor Descriptor_;
+
+ TDriverRequest Request_;
+
+ std::optional<TFormat> InputFormat_;
+ std::optional<TFormat> OutputFormat_;
+ };
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IDriverPtr CreateDriver(
+ IConnectionPtr connection,
+ TDriverConfigPtr config)
+{
+ YT_VERIFY(connection);
+ YT_VERIFY(config);
+
+ return New<TDriver>(
+ std::move(config),
+ std::move(connection));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
diff --git a/yt/yt/client/driver/driver.h b/yt/yt/client/driver/driver.h
new file mode 100644
index 0000000000..00a40969fb
--- /dev/null
+++ b/yt/yt/client/driver/driver.h
@@ -0,0 +1,167 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/formats/format.h>
+
+#include <yt/yt/client/security_client/public.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/net/address.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+#include <yt/yt/core/yson/consumer.h>
+#include <yt/yt/core/yson/writer.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! An instance of driver request.
+struct TDriverRequest
+{
+ TDriverRequest();
+ explicit TDriverRequest(TRefCountedPtr holder);
+
+ //! Request identifier to be logged.
+ std::variant<ui64, TGuid> Id = static_cast<ui64>(0);
+
+ //! Command name to execute.
+ TString CommandName;
+
+ //! Stream used for reading command input.
+ //! The stream must stay alive for the duration of #IDriver::Execute.
+ NConcurrency::IAsyncInputStreamPtr InputStream;
+
+ //! Stream where the command output is written.
+ //! The stream must stay alive for the duration of #IDriver::Execute.
+ NConcurrency::IFlushableAsyncOutputStreamPtr OutputStream;
+
+ //! A map containing command parameters.
+ NYTree::IMapNodePtr Parameters;
+
+ //! Name of the user issuing the request.
+ TString AuthenticatedUser = NSecurityClient::RootUserName;
+
+ //! Provides an additional annotation to differentiate between
+ //! various clients that authenticate via the same effective user.
+ std::optional<TString> UserTag;
+
+ //! Filled in the context of http proxy.
+ std::optional<NNet::TNetworkAddress> UserRemoteAddress;
+
+ //! User token.
+ std::optional<TString> UserToken;
+
+ //! TVM service ticket.
+ std::optional<TString> ServiceTicket;
+
+ //! Additional logging tags.
+ std::optional<TString> LoggingTags;
+
+ //! Provides means to return arbitrary structured data from any command.
+ //! Must be filled before writing data to output stream.
+ NYson::IYsonConsumer* ResponseParametersConsumer;
+
+ //! Invoked after driver is done producing response parameters and
+ //! before first write to output stream.
+ std::function<void()> ResponseParametersFinishedCallback;
+
+ void Reset();
+
+private:
+ using THolderPtr = TRefCountedPtr;
+ THolderPtr Holder_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Command meta-descriptor.
+/*!
+ * Contains various meta-information describing a given command type.
+ */
+struct TCommandDescriptor
+{
+ //! Name of the command.
+ TString CommandName;
+
+ //! Type of data expected by the command at #TDriverRequest::InputStream.
+ NFormats::EDataType InputType;
+
+ //! Type of data written by the command to #TDriverRequest::OutputStream.
+ NFormats::EDataType OutputType;
+
+ //! Whether the command affects the state of the cluster.
+ bool Volatile;
+
+ //! Whether the execution of a command is lengthy and/or causes a heavy load.
+ bool Heavy;
+};
+
+void Serialize(const TCommandDescriptor& descriptor, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! An instance of command execution engine.
+/*!
+ * Each driver instance maintains a collection of cached connections to
+ * various YT subsystems (e.g. masters, scheduler).
+ *
+ * IDriver instances are thread-safe and reentrant.
+ */
+struct IDriver
+ : public virtual TRefCounted
+{
+ //! Asynchronously executes a given request.
+ virtual TFuture<void> Execute(const TDriverRequest& request) = 0;
+
+ //! Returns a descriptor for the command with a given name or
+ //! Null if no command with this name is registered.
+ virtual std::optional<TCommandDescriptor> FindCommandDescriptor(const TString& commandName) const = 0;
+
+ //! Returns a descriptor for then command with a given name.
+ //! Fails if no command with this name is registered.
+ TCommandDescriptor GetCommandDescriptor(const TString& commandName) const;
+
+ //! Returns a descriptor for then command with a given name.
+ //! Throws if no command with this name is registered.
+ TCommandDescriptor GetCommandDescriptorOrThrow(const TString& commandName) const;
+
+ //! Returns the list of descriptors for all supported commands.
+ virtual const std::vector<TCommandDescriptor> GetCommandDescriptors() const = 0;
+
+ virtual void ClearMetadataCaches() = 0;
+
+ //! Returns the pool of sticky transactions stored in the driver.
+ virtual NApi::IStickyTransactionPoolPtr GetStickyTransactionPool() = 0;
+
+ //! Returns the cache for proxy discovery requests.
+ virtual IProxyDiscoveryCachePtr GetProxyDiscoveryCache() = 0;
+
+ //! Returns the underlying connection.
+ virtual NApi::IConnectionPtr GetConnection() = 0;
+
+ //! Terminates the underlying connection.
+ virtual void Terminate() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IDriver)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IDriverPtr CreateDriver(
+ NApi::IConnectionPtr connection,
+ TDriverConfigPtr config);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
diff --git a/yt/yt/client/driver/etc_commands.cpp b/yt/yt/client/driver/etc_commands.cpp
new file mode 100644
index 0000000000..e22212bde3
--- /dev/null
+++ b/yt/yt/client/driver/etc_commands.cpp
@@ -0,0 +1,437 @@
+#include "etc_commands.h"
+#include "config.h"
+#include "proxy_discovery_cache.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/client/api/rpc_proxy/public.h>
+
+#include <yt/yt/build/build.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/core/yson/async_writer.h>
+
+namespace NYT::NDriver {
+
+using namespace NYPath;
+using namespace NYTree;
+using namespace NYson;
+using namespace NSecurityClient;
+using namespace NObjectClient;
+using namespace NConcurrency;
+using namespace NFormats;
+using namespace NApi;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TAddMemberCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->AddMember(
+ Group,
+ Member,
+ Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TRemoveMemberCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->RemoveMember(
+ Group,
+ Member,
+ Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TParseYPathCommand::TParseYPathCommand()
+{
+ RegisterParameter("path", Path);
+}
+
+void TParseYPathCommand::DoExecute(ICommandContextPtr context)
+{
+ auto richPath = TRichYPath::Parse(Path);
+ ProduceSingleOutputValue(context, "path", richPath);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TGetVersionCommand::DoExecute(ICommandContextPtr context)
+{
+ ProduceSingleOutputValue(context, "version", GetVersion());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TGetSupportedFeaturesCommand::DoExecute(ICommandContextPtr context)
+{
+ TGetClusterMetaOptions options;
+ options.PopulateFeatures = true;
+ options.ReadFrom = EMasterChannelKind::Cache;
+ options.DisablePerUserCache = true;
+
+ auto meta = WaitFor(context->GetClient()->GetClusterMeta(options))
+ .ValueOrThrow();
+ if (!meta.Features) {
+ THROW_ERROR_EXCEPTION("Feature querying is not supported by current master version");
+ }
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("features").Value(meta.Features)
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCheckPermissionCommand::TCheckPermissionCommand()
+{
+ RegisterParameter("user", User);
+ RegisterParameter("permission", Permission);
+ RegisterParameter("path", Path);
+ RegisterParameter("columns", Options.Columns)
+ .Optional();
+ RegisterParameter("vital", Options.Vital)
+ .Optional();
+}
+
+void TCheckPermissionCommand::DoExecute(ICommandContextPtr context)
+{
+ auto response =
+ WaitFor(context->GetClient()->CheckPermission(
+ User,
+ Path.GetPath(),
+ Permission,
+ Options))
+ .ValueOrThrow();
+
+ auto produceResult = [] (auto fluent, const auto& result) {
+ fluent
+ .Item("action").Value(result.Action)
+ .OptionalItem("object_id", result.ObjectId)
+ .OptionalItem("object_name", result.ObjectName)
+ .OptionalItem("subject_id", result.SubjectId)
+ .OptionalItem("subject_name", result.SubjectName);
+ };
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Do([&] (auto fluent) { produceResult(fluent, response); })
+ .DoIf(response.Columns.has_value(), [&] (auto fluent) {
+ fluent
+ .Item("columns").DoListFor(*response.Columns, [&] (auto fluent, const auto& result) {
+ fluent
+ .Item().BeginMap()
+ .Do([&] (auto fluent) { produceResult(fluent, result); })
+ .EndMap();
+ });
+ })
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCheckPermissionByAclCommand::TCheckPermissionByAclCommand()
+{
+ RegisterParameter("user", User);
+ RegisterParameter("permission", Permission);
+ RegisterParameter("acl", Acl);
+}
+
+void TCheckPermissionByAclCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result =
+ WaitFor(context->GetClient()->CheckPermissionByAcl(
+ User,
+ Permission,
+ Acl,
+ Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("action").Value(result.Action)
+ .OptionalItem("subject_id", result.SubjectId)
+ .OptionalItem("subject_name", result.SubjectName)
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTransferAccountResourcesCommand::TTransferAccountResourcesCommand()
+{
+ RegisterParameter("source_account", SourceAccount);
+ RegisterParameter("destination_account", DestinationAccount);
+ RegisterParameter("resource_delta", ResourceDelta);
+}
+
+void TTransferAccountResourcesCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->TransferAccountResources(
+ SourceAccount,
+ DestinationAccount,
+ ResourceDelta,
+ Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTransferPoolResourcesCommand::TTransferPoolResourcesCommand()
+{
+ RegisterParameter("source_pool", SourcePool);
+ RegisterParameter("destination_pool", DestinationPool);
+ RegisterParameter("pool_tree", PoolTree);
+ RegisterParameter("resource_delta", ResourceDelta);
+}
+
+void TTransferPoolResourcesCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->TransferPoolResources(
+ SourcePool,
+ DestinationPool,
+ PoolTree,
+ ResourceDelta,
+ Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TExecuteBatchCommandRequest::Register(TRegistrar registrar)
+{
+ registrar.Parameter("command", &TThis::Command);
+ registrar.Parameter("parameters", &TThis::Parameters);
+ registrar.Parameter("input", &TThis::Input)
+ .Default();
+}
+
+class TExecuteBatchCommand::TRequestExecutor
+ : public TRefCounted
+{
+public:
+ TRequestExecutor(
+ ICommandContextPtr context,
+ int requestIndex,
+ TRequestPtr request,
+ NRpc::TMutationId mutationId,
+ bool retry)
+ : Context_(std::move(context))
+ , RequestIndex_(requestIndex)
+ , Request_(std::move(request))
+ , MutationId_(mutationId)
+ , Retry_(retry)
+ , SyncInput_(Input_)
+ , AsyncInput_(CreateAsyncAdapter(
+ &SyncInput_,
+ Context_->GetClient()->GetConnection()->GetInvoker()))
+ , SyncOutput_(Output_)
+ , AsyncOutput_(CreateAsyncAdapter(
+ &SyncOutput_,
+ Context_->GetClient()->GetConnection()->GetInvoker()))
+ { }
+
+ TFuture<TYsonString> Run()
+ {
+ try {
+ auto driver = Context_->GetDriver();
+ Descriptor_ = driver->GetCommandDescriptorOrThrow(Request_->Command);
+
+ if (Descriptor_.InputType != EDataType::Null &&
+ Descriptor_.InputType != EDataType::Structured &&
+ Descriptor_.InputType != EDataType::Tabular)
+ {
+ THROW_ERROR_EXCEPTION("Command %Qv cannot be part of a batch since it has inappropriate input type %Qlv",
+ Request_->Command,
+ Descriptor_.InputType);
+ }
+
+ if (Descriptor_.OutputType != EDataType::Null &&
+ Descriptor_.OutputType != EDataType::Structured)
+ {
+ THROW_ERROR_EXCEPTION("Command %Qv cannot be part of a batch since it has inappropriate output type %Qlv",
+ Request_->Command,
+ Descriptor_.OutputType);
+ }
+
+ TDriverRequest driverRequest;
+ driverRequest.Id = Context_->Request().Id;
+ driverRequest.CommandName = Request_->Command;
+ auto parameters = IAttributeDictionary::FromMap(Request_->Parameters);
+ if (Descriptor_.InputType == EDataType::Tabular || Descriptor_.InputType == EDataType::Structured) {
+ if (!Request_->Input) {
+ THROW_ERROR_EXCEPTION("Command %Qv requires input",
+ Descriptor_.CommandName);
+ }
+ // COMPAT(ignat): disable this option in proxy configuration and remove it.
+ if ((Context_->GetConfig()->ExpectStructuredInputInStructuredBatchCommands && Descriptor_.InputType == EDataType::Structured) ||
+ !parameters->Contains("input_format"))
+ {
+ Input_ = ConvertToYsonString(Request_->Input).ToString();
+ parameters->Set("input_format", TFormat(EFormatType::Yson));
+ } else {
+ Input_ = Request_->Input->AsString()->GetValue();
+ }
+ driverRequest.InputStream = AsyncInput_;
+ }
+ if (Descriptor_.OutputType == EDataType::Structured) {
+ parameters->Set("output_format", TFormat(EFormatType::Yson));
+ driverRequest.OutputStream = AsyncOutput_;
+ }
+ if (Descriptor_.Volatile) {
+ parameters->Set("mutation_id", MutationId_);
+ parameters->Set("retry", Retry_);
+ }
+ driverRequest.Parameters = parameters->ToMap();
+ driverRequest.AuthenticatedUser = Context_->Request().AuthenticatedUser;
+ driverRequest.UserTag = Context_->Request().UserTag;
+ driverRequest.LoggingTags = Format("SubrequestIndex: %v", RequestIndex_);
+
+ return driver->Execute(driverRequest).Apply(
+ BIND(&TRequestExecutor::OnResponse, MakeStrong(this)));
+ } catch (const std::exception& ex) {
+ return MakeFuture<TYsonString>(ex);
+ }
+ }
+
+private:
+ const ICommandContextPtr Context_;
+ const int RequestIndex_;
+ const TRequestPtr Request_;
+ const NRpc::TMutationId MutationId_;
+ const bool Retry_;
+
+ TCommandDescriptor Descriptor_;
+
+ TString Input_;
+ TStringInput SyncInput_;
+ IAsyncInputStreamPtr AsyncInput_;
+
+ TString Output_;
+ TStringOutput SyncOutput_;
+ IFlushableAsyncOutputStreamPtr AsyncOutput_;
+
+ TYsonString OnResponse(const TError& error)
+ {
+ return BuildYsonStringFluently()
+ .BeginMap()
+ .DoIf(!error.IsOK(), [&] (TFluentMap fluent) {
+ fluent
+ .Item("error").Value(error);
+ })
+ .DoIf(error.IsOK() && Descriptor_.OutputType == EDataType::Structured, [&] (TFluentMap fluent) {
+ fluent
+ .Item("output").Value(TYsonStringBuf(Output_));
+ })
+ .EndMap();
+ }
+};
+
+TExecuteBatchCommand::TExecuteBatchCommand()
+{
+ RegisterParameter("concurrency", Options.Concurrency)
+ .Default(50)
+ .GreaterThan(0);
+ RegisterParameter("requests", Requests);
+}
+
+void TExecuteBatchCommand::DoExecute(ICommandContextPtr context)
+{
+ auto mutationId = Options.GetOrGenerateMutationId();
+
+ std::vector<TCallback<TFuture<TYsonString>()>> callbacks;
+ for (int requestIndex = 0; requestIndex < std::ssize(Requests); ++requestIndex) {
+ auto executor = New<TRequestExecutor>(
+ context,
+ requestIndex,
+ Requests[requestIndex],
+ mutationId,
+ Options.Retry);
+ mutationId = NRpc::GenerateNextBatchMutationId(mutationId);
+ callbacks.push_back(BIND(&TRequestExecutor::Run, executor));
+ }
+
+ auto results = WaitFor(RunWithBoundedConcurrency(callbacks, Options.Concurrency))
+ .ValueOrThrow();
+
+ ProduceSingleOutput(context, "results", [&] (NYson::IYsonConsumer* consumer) {
+ BuildYsonFluently(consumer)
+ .DoListFor(results, [&] (TFluentList fluent, const TErrorOr<TYsonString>& result) {
+ fluent.Item().Value(result.ValueOrThrow());
+ });
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDiscoverProxiesCommand::TDiscoverProxiesCommand()
+{
+ RegisterParameter("type", Type)
+ .Default(EProxyType::Rpc);
+ RegisterParameter("role", Role)
+ .Default(DefaultRpcProxyRole);
+ RegisterParameter("address_type", AddressType)
+ .Default(NApi::NRpcProxy::DefaultAddressType);
+ RegisterParameter("network_name", NetworkName)
+ .Default(NApi::NRpcProxy::DefaultNetworkName);
+ RegisterParameter("ignore_balancers", IgnoreBalancers)
+ .Default(false);
+}
+
+void TDiscoverProxiesCommand::DoExecute(ICommandContextPtr context)
+{
+ TProxyDiscoveryRequest request{
+ .Type = Type,
+ .Role = Role,
+ .AddressType = AddressType,
+ .NetworkName = NetworkName,
+ .IgnoreBalancers = IgnoreBalancers
+ };
+
+ const auto& proxyDiscoveryCache = context->GetDriver()->GetProxyDiscoveryCache();
+ auto response = WaitFor(proxyDiscoveryCache->Discover(request))
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "proxies", response.Addresses);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TBalanceTabletCellsCommand::TBalanceTabletCellsCommand()
+{
+ RegisterParameter("bundle", TabletCellBundle);
+ RegisterParameter("tables", MovableTables)
+ .Optional();
+ RegisterParameter("keep_actions", Options.KeepActions)
+ .Default(false);
+}
+
+void TBalanceTabletCellsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->BalanceTabletCells(
+ TabletCellBundle,
+ MovableTables,
+ Options);
+ auto tabletActions = WaitFor(asyncResult)
+ .ValueOrThrow();
+ context->ProduceOutputValue(BuildYsonStringFluently().List(tabletActions));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/etc_commands.h b/yt/yt/client/driver/etc_commands.h
new file mode 100644
index 0000000000..ad00e94211
--- /dev/null
+++ b/yt/yt/client/driver/etc_commands.h
@@ -0,0 +1,223 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/api/rpc_proxy/public.h>
+
+#include <yt/yt/core/ytree/permission.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+class TUpdateMembershipCommand
+ : public TTypedCommand<TOptions>
+{
+protected:
+ TString Group;
+ TString Member;
+
+ TUpdateMembershipCommand()
+ {
+ this->RegisterParameter("group", Group);
+ this->RegisterParameter("member", Member);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAddMemberCommand
+ : public TUpdateMembershipCommand<NApi::TAddMemberOptions>
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoveMemberCommand
+ : public TUpdateMembershipCommand<NApi::TRemoveMemberOptions>
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TParseYPathCommand
+ : public TCommandBase
+{
+public:
+ TParseYPathCommand();
+
+private:
+ TString Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetVersionCommand
+ : public TCommandBase
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetSupportedFeaturesCommand
+ : public TCommandBase
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCheckPermissionCommand
+ : public TTypedCommand<NApi::TCheckPermissionOptions>
+{
+public:
+ TCheckPermissionCommand();
+
+private:
+ TString User;
+ NYPath::TRichYPath Path;
+ NYTree::EPermission Permission;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCheckPermissionByAclCommand
+ : public TTypedCommand<NApi::TCheckPermissionByAclOptions>
+{
+public:
+ TCheckPermissionByAclCommand();
+
+private:
+ std::optional<TString> User;
+ NYTree::EPermission Permission;
+ NYTree::INodePtr Acl;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTransferAccountResourcesCommand
+ : public TTypedCommand<NApi::TTransferAccountResourcesOptions>
+{
+public:
+ TTransferAccountResourcesCommand();
+
+private:
+ TString SourceAccount;
+ TString DestinationAccount;
+ NYTree::INodePtr ResourceDelta;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTransferPoolResourcesCommand
+ : public TTypedCommand<NApi::TTransferPoolResourcesOptions>
+{
+public:
+ TTransferPoolResourcesCommand();
+
+private:
+ TString SourcePool;
+ TString DestinationPool;
+ TString PoolTree;
+ NYTree::INodePtr ResourceDelta;
+
+ virtual void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TExecuteBatchOptions
+ : public NApi::TMutatingOptions
+{
+ int Concurrency;
+};
+
+class TExecuteBatchCommandRequest
+ : public NYTree::TYsonStruct
+{
+public:
+ TString Command;
+ NYTree::IMapNodePtr Parameters;
+ NYTree::INodePtr Input;
+
+ REGISTER_YSON_STRUCT(TExecuteBatchCommandRequest);
+
+ static void Register(TRegistrar registrar);
+};
+
+class TExecuteBatchCommand
+ : public TTypedCommand<TExecuteBatchOptions>
+{
+public:
+ TExecuteBatchCommand();
+
+private:
+ using TRequestPtr = TIntrusivePtr<TExecuteBatchCommandRequest>;
+
+ std::vector<TRequestPtr> Requests;
+
+ class TRequestExecutor;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TDiscoverProxiesOptions
+ : public NApi::TTimeoutOptions
+{ };
+
+class TDiscoverProxiesCommand
+ : public TTypedCommand<TDiscoverProxiesOptions>
+{
+public:
+ TDiscoverProxiesCommand();
+
+private:
+ NApi::EProxyType Type;
+ TString Role;
+ NApi::NRpcProxy::EAddressType AddressType;
+ TString NetworkName;
+ bool IgnoreBalancers;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBalanceTabletCellsCommand
+ : public TTypedCommand<NApi::TBalanceTabletCellsOptions>
+{
+public:
+ TBalanceTabletCellsCommand();
+
+private:
+ TString TabletCellBundle;
+ std::vector<NYPath::TYPath> MovableTables;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/file_commands.cpp b/yt/yt/client/driver/file_commands.cpp
new file mode 100644
index 0000000000..996b8b5431
--- /dev/null
+++ b/yt/yt/client/driver/file_commands.cpp
@@ -0,0 +1,171 @@
+#include "file_commands.h"
+#include "config.h"
+#include "helpers.h"
+
+#include <yt/yt/client/api/config.h>
+#include <yt/yt/client/api/file_reader.h>
+#include <yt/yt/client/api/file_writer.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+using namespace NApi;
+using namespace NConcurrency;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadFileCommand::TReadFileCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("offset", Options.Offset)
+ .Optional();
+ RegisterParameter("length", Options.Length)
+ .Optional();
+ RegisterParameter("file_reader", FileReader)
+ .Default(nullptr);
+ RegisterParameter("etag", Etag)
+ .Alias("etag_revision")
+ .Default();
+}
+
+void TReadFileCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Config = UpdateYsonStruct(
+ context->GetConfig()->FileReader,
+ FileReader);
+
+ PutMethodInfoInTraceContext("read_file");
+
+ auto reader = WaitFor(context->GetClient()->CreateFileReader(Path.GetPath(), Options))
+ .ValueOrThrow();
+
+ ProduceResponseParameters(context, [&] (NYson::IYsonConsumer* consumer) {
+ BuildYsonMapFragmentFluently(consumer)
+ .Item("id").Value(reader->GetId())
+ .Item("revision").Value(reader->GetRevision());
+ });
+
+ if (!Etag.empty()) {
+ if (auto etag = ParseEtag(Etag);
+ etag.IsOK() &&
+ etag.Value().Id == reader->GetId() && etag.Value().Revision == reader->GetRevision())
+ {
+ return;
+ }
+
+ // COMPAT(shakurov)
+ NHydra::TRevision etagRevision;
+ if (TryFromString(Etag, etagRevision) && etagRevision == reader->GetRevision()) {
+ return;
+ }
+ }
+
+ auto output = context->Request().OutputStream;
+
+ while (true) {
+ auto block = WaitFor(reader->Read())
+ .ValueOrThrow();
+
+ if (!block)
+ break;
+
+ WaitFor(output->Write(block))
+ .ThrowOnError();
+ }
+}
+
+bool TReadFileCommand::HasResponseParameters() const
+{
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TWriteFileCommand::TWriteFileCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("file_writer", FileWriter)
+ .Default();
+ RegisterParameter("compute_md5", ComputeMD5)
+ .Default(false);
+}
+
+void TWriteFileCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Config = UpdateYsonStruct(
+ context->GetConfig()->FileWriter,
+ FileWriter);
+ Options.ComputeMD5 = ComputeMD5;
+
+ PutMethodInfoInTraceContext("write_file");
+
+ auto writer = context->GetClient()->CreateFileWriter(Path, Options);
+
+ WaitFor(writer->Open())
+ .ThrowOnError();
+
+ struct TWriteBufferTag { };
+
+ auto buffer = TSharedMutableRef::Allocate<TWriteBufferTag>(context->GetConfig()->WriteBufferSize, {.InitializeStorage = false});
+
+ auto input = context->Request().InputStream;
+
+ while (true) {
+ auto bytesRead = WaitFor(input->Read(buffer))
+ .ValueOrThrow();
+
+ if (bytesRead == 0)
+ break;
+
+ WaitFor(writer->Write(buffer.Slice(0, bytesRead)))
+ .ThrowOnError();
+ }
+
+ WaitFor(writer->Close())
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetFileFromCacheCommand::TGetFileFromCacheCommand()
+{
+ RegisterParameter("md5", MD5);
+ RegisterParameter("cache_path", Options.CachePath);
+}
+
+void TGetFileFromCacheCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->GetFileFromCache(MD5, Options);
+ auto result = WaitFor(asyncResult)
+ .ValueOrThrow();
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .Value(result.Path));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TPutFileToCacheCommand::TPutFileToCacheCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("md5", MD5);
+ RegisterParameter("cache_path", Options.CachePath);
+ RegisterParameter("preserve_expiration_timeout", Options.PreserveExpirationTimeout)
+ .Optional();
+}
+
+void TPutFileToCacheCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->PutFileToCache(Path, MD5, Options);
+ auto result = WaitFor(asyncResult)
+ .ValueOrThrow();
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .Value(result.Path));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/file_commands.h b/yt/yt/client/driver/file_commands.h
new file mode 100644
index 0000000000..a422b9bd39
--- /dev/null
+++ b/yt/yt/client/driver/file_commands.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadFileCommand
+ : public TTypedCommand<NApi::TFileReaderOptions>
+{
+public:
+ TReadFileCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NYTree::INodePtr FileReader;
+ TString Etag;
+
+ void DoExecute(ICommandContextPtr context) override;
+ bool HasResponseParameters() const override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWriteFileCommand
+ : public TTypedCommand<NApi::TFileWriterOptions>
+{
+public:
+ TWriteFileCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NYTree::INodePtr FileWriter;
+ bool ComputeMD5;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetFileFromCacheCommand
+ : public TTypedCommand<NApi::TGetFileFromCacheOptions>
+{
+public:
+ TGetFileFromCacheCommand();
+
+private:
+ TString MD5;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPutFileToCacheCommand
+ : public TTypedCommand<NApi::TPutFileToCacheOptions>
+{
+public:
+ TPutFileToCacheCommand();
+
+private:
+ NYPath::TYPath Path;
+ TString MD5;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
diff --git a/yt/yt/client/driver/helpers.cpp b/yt/yt/client/driver/helpers.cpp
new file mode 100644
index 0000000000..007fba71e8
--- /dev/null
+++ b/yt/yt/client/driver/helpers.cpp
@@ -0,0 +1,66 @@
+#include "helpers.h"
+
+#include <yt/yt/client/misc/io_tags.h>
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/guid.h>
+
+#include <yt/yt/core/tracing/trace_context.h>
+
+#include <yt/yt/core/ytree/helpers.h>
+
+namespace NYT::NDriver {
+
+using namespace NObjectClient;
+using namespace NTracing;
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TEtag& lhs, const TEtag& rhs)
+{
+ return lhs.Id == rhs.Id && lhs.Revision == rhs.Revision;
+}
+
+TErrorOr<TEtag> ParseEtag(TStringBuf etagString)
+{
+ static const TErrorOr<TEtag> parseError(TError("Failed to parse etag"));
+
+ TStringBuf idString;
+ TStringBuf revisionString;
+ if (!etagString.TrySplit(':', idString, revisionString)) {
+ return parseError;
+ }
+
+ TEtag result;
+
+ if (!TObjectId::FromString(idString, &result.Id)) {
+ return parseError;
+ }
+
+ if (!TryFromString(revisionString, result.Revision)) {
+ return parseError;
+ }
+
+ return result;
+}
+
+TString ToString(const TEtag& Etag)
+{
+ return Format("%v:%v", Etag.Id, Etag.Revision);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void PutMethodInfoInTraceContext(const TStringBuf& methodName)
+{
+ if (auto* traceContext = TryGetCurrentTraceContext()) {
+ auto baggage = traceContext->UnpackOrCreateBaggage();
+ AddTagToBaggage(baggage, EAggregateIOTag::ApiMethod, methodName);
+ AddTagToBaggage(baggage, EAggregateIOTag::ProxyKind, "http");
+ traceContext->PackBaggage(baggage);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/helpers.h b/yt/yt/client/driver/helpers.h
new file mode 100644
index 0000000000..b10b9bd543
--- /dev/null
+++ b/yt/yt/client/driver/helpers.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/object_client/public.h>
+#include <yt/yt/client/hydra/public.h>
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NDriver {
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct TEtag
+{
+ NObjectClient::TObjectId Id = NObjectClient::NullObjectId;
+ NHydra::TRevision Revision = NHydra::NullRevision;
+};
+
+bool operator==(const TEtag& lhs, const TEtag& rhs);
+
+TErrorOr<TEtag> ParseEtag(TStringBuf etagString);
+TString ToString(const TEtag& Etag);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void PutMethodInfoInTraceContext(const TStringBuf& methodName);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/internal_commands.cpp b/yt/yt/client/driver/internal_commands.cpp
new file mode 100644
index 0000000000..5f4cdc9f24
--- /dev/null
+++ b/yt/yt/client/driver/internal_commands.cpp
@@ -0,0 +1,144 @@
+#include "internal_commands.h"
+
+#include "config.h"
+
+#include <yt/yt/client/chunk_client/config.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+using namespace NConcurrency;
+using namespace NApi;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadHunksCommand::TReadHunksCommand()
+{
+ RegisterParameter("descriptors", Descriptors);
+
+ RegisterParameter("parse_header", ParseHeader)
+ .Default(true);
+}
+
+void TReadHunksCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Config = UpdateYsonStruct(
+ context->GetConfig()->ChunkFragmentReader,
+ ChunkFragmentReader);
+ Options.ParseHeader = ParseHeader;
+
+ std::vector<THunkDescriptor> descriptors;
+ descriptors.reserve(Descriptors.size());
+ for (const auto& descriptor : Descriptors) {
+ descriptors.push_back(*descriptor);
+ }
+
+ auto internalClient = context->GetInternalClientOrThrow();
+ auto responses = WaitFor(internalClient->ReadHunks(descriptors, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("hunks").DoListFor(responses, [&] (auto fluent, const TSharedRef& response) {
+ fluent
+ .Item().BeginMap()
+ .Item(HunkPayloadKey).Value(TStringBuf(response.Begin(), response.End()))
+ .EndMap();
+ })
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TWriteHunksCommand::TWriteHunksCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("tablet_index", TabletIndex);
+ RegisterParameter("payloads", Payloads);
+}
+
+void TWriteHunksCommand::DoExecute(ICommandContextPtr context)
+{
+ auto internalClient = context->GetInternalClientOrThrow();
+
+ std::vector<TSharedRef> payloads;
+ payloads.reserve(Payloads.size());
+ for (const auto& payload : Payloads) {
+ payloads.push_back(TSharedRef::FromString(payload));
+ }
+
+ auto descriptors = WaitFor(internalClient->WriteHunks(Path, TabletIndex, payloads))
+ .ValueOrThrow();
+
+ std::vector<NApi::TSerializableHunkDescriptorPtr> serializableDescriptors;
+ serializableDescriptors.reserve(descriptors.size());
+ for (const auto& descriptor : descriptors) {
+ serializableDescriptors.push_back(New<NApi::TSerializableHunkDescriptor>(descriptor));
+ }
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .Value(serializableDescriptors));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLockHunkStoreCommand::TLockHunkStoreCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("tablet_index", TabletIndex);
+ RegisterParameter("store_id", StoreId);
+ RegisterParameter("locker_tablet_id", LockerTabletId);
+}
+
+void TLockHunkStoreCommand::DoExecute(ICommandContextPtr context)
+{
+ auto internalClient = context->GetInternalClientOrThrow();
+
+ WaitFor(internalClient->LockHunkStore(
+ Path,
+ TabletIndex,
+ StoreId,
+ LockerTabletId))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnlockHunkStoreCommand::TUnlockHunkStoreCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("tablet_index", TabletIndex);
+ RegisterParameter("store_id", StoreId);
+ RegisterParameter("locker_tablet_id", LockerTabletId);
+}
+
+void TUnlockHunkStoreCommand::DoExecute(ICommandContextPtr context)
+{
+ auto internalClient = context->GetInternalClientOrThrow();
+
+ WaitFor(internalClient->UnlockHunkStore(
+ Path,
+ TabletIndex,
+ StoreId,
+ LockerTabletId))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TGetConnectionConfigCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetRootClient();
+
+ context->ProduceOutputValue(client->GetConnection()->GetConfigYson());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/internal_commands.h b/yt/yt/client/driver/internal_commands.h
new file mode 100644
index 0000000000..2d3f83bfde
--- /dev/null
+++ b/yt/yt/client/driver/internal_commands.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/api/internal_client.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadHunksCommand
+ : public TTypedCommand<NApi::TReadHunksOptions>
+{
+public:
+ TReadHunksCommand();
+
+private:
+ std::vector<NApi::TSerializableHunkDescriptorPtr> Descriptors;
+ NYTree::INodePtr ChunkFragmentReader;
+
+ bool ParseHeader;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWriteHunksCommand
+ : public TTypedCommand<NApi::TWriteHunksOptions>
+{
+public:
+ TWriteHunksCommand();
+
+private:
+ NYTree::TYPath Path;
+ int TabletIndex;
+ std::vector<TString> Payloads;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLockHunkStoreCommand
+ : public TTypedCommand<NApi::TLockHunkStoreOptions>
+{
+public:
+ TLockHunkStoreCommand();
+
+private:
+ NYTree::TYPath Path;
+ int TabletIndex;
+ NTabletClient::TStoreId StoreId;
+ NTabletClient::TTabletId LockerTabletId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnlockHunkStoreCommand
+ : public TTypedCommand<NApi::TUnlockHunkStoreOptions>
+{
+public:
+ TUnlockHunkStoreCommand();
+
+private:
+ NYTree::TYPath Path;
+ int TabletIndex;
+ NTabletClient::TStoreId StoreId;
+ NTabletClient::TTabletId LockerTabletId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TGetConnectionConfigCommandOptions
+{ };
+
+class TGetConnectionConfigCommand
+ : public TTypedCommand<TGetConnectionConfigCommandOptions>
+{
+public:
+ TGetConnectionConfigCommand() = default;
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/journal_commands.cpp b/yt/yt/client/driver/journal_commands.cpp
new file mode 100644
index 0000000000..a3a4406e8d
--- /dev/null
+++ b/yt/yt/client/driver/journal_commands.cpp
@@ -0,0 +1,343 @@
+#include "journal_commands.h"
+#include "config.h"
+
+#include <yt/yt/client/api/config.h>
+#include <yt/yt/client/api/journal_reader.h>
+#include <yt/yt/client/api/journal_writer.h>
+
+#include <yt/yt/client/chunk_client/read_limit.h>
+
+#include <yt/yt/client/formats/format.h>
+#include <yt/yt/client/formats/parser.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NFormats;
+using namespace NConcurrency;
+using namespace NChunkClient;
+using namespace NApi;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadJournalCommand::TReadJournalCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("journal_reader", JournalReader)
+ .Default();
+}
+
+void TReadJournalCommand::DoExecute(ICommandContextPtr context)
+{
+ auto checkLimit = [] (const TReadLimit& limit) {
+ if (limit.KeyBound()) {
+ THROW_ERROR_EXCEPTION("Reading key range is not supported in journals");
+ }
+ if (limit.GetChunkIndex()) {
+ THROW_ERROR_EXCEPTION("Reading chunk index range is not supported in journals");
+ }
+ if (limit.GetOffset()) {
+ THROW_ERROR_EXCEPTION("Reading offset range is not supported in journals");
+ }
+ };
+
+ if (Path.GetNewRanges().size() > 1) {
+ THROW_ERROR_EXCEPTION("Reading multiple ranges is not supported in journals");
+ }
+
+ Options.Config = UpdateYsonStruct(
+ context->GetConfig()->JournalReader,
+ JournalReader);
+
+ if (Path.GetNewRanges().size() == 1) {
+ auto range = Path.GetNewRanges()[0];
+
+ checkLimit(range.LowerLimit());
+ checkLimit(range.UpperLimit());
+
+ Options.FirstRowIndex = range.LowerLimit().GetRowIndex().value_or(0);
+
+ if (auto upperRowIndex = range.UpperLimit().GetRowIndex()) {
+ Options.RowCount = *upperRowIndex - *Options.FirstRowIndex;
+ }
+ }
+
+ auto reader = context->GetClient()->CreateJournalReader(
+ Path.GetPath(),
+ Options);
+
+ WaitFor(reader->Open())
+ .ThrowOnError();
+
+ auto output = context->Request().OutputStream;
+
+ // TODO(babenko): provide custom allocation tag
+ TBlobOutput buffer;
+ auto flushBuffer = [&] () {
+ WaitFor(output->Write(buffer.Flush()))
+ .ThrowOnError();
+ };
+
+ auto format = context->GetOutputFormat();
+ auto consumer = CreateConsumerForFormat(format, EDataType::Tabular, &buffer);
+
+ while (true) {
+ auto rowsOrError = WaitFor(reader->Read());
+ const auto& rows = rowsOrError.ValueOrThrow();
+
+ if (rows.empty())
+ break;
+
+ for (auto row : rows) {
+ BuildYsonListFragmentFluently(consumer.get())
+ .Item().BeginMap()
+ .Item(JournalPayloadKey).Value(TStringBuf(row.Begin(), row.Size()))
+ .EndMap();
+ }
+
+ if (std::ssize(buffer) > context->GetConfig()->ReadBufferSize) {
+ flushBuffer();
+ }
+ }
+
+ consumer->Flush();
+
+ if (buffer.Size() > 0) {
+ flushBuffer();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EJournalConsumerState,
+ (Root)
+ (AtItem)
+ (InsideMap)
+ (AtData)
+);
+
+class TJournalConsumer
+ : public TYsonConsumerBase
+{
+public:
+ explicit TJournalConsumer(IJournalWriterPtr writer)
+ : Writer_(std::move(writer))
+ { }
+
+ void Flush()
+ {
+ if (BufferedRows_.empty()) {
+ return;
+ }
+
+ WaitFor(Writer_->Write(BufferedRows_))
+ .ThrowOnError();
+
+ BufferedRows_.clear();
+ BufferedByteSize_ = 0;
+ }
+
+private:
+ const IJournalWriterPtr Writer_;
+
+ EJournalConsumerState State_ = EJournalConsumerState::Root;
+
+ std::vector<TSharedRef> BufferedRows_;
+ i64 BufferedByteSize_ = 0;
+ static constexpr i64 MaxBufferedSize = 64_KB;
+
+
+ void OnStringScalar(TStringBuf value) override
+ {
+ if (State_ != EJournalConsumerState::AtData) {
+ ThrowMalformedPayload();
+ }
+
+ auto row = TSharedRef::FromString(TString(value));
+ BufferedByteSize_ += row.Size();
+ BufferedRows_.push_back(std::move(row));
+
+ State_ = EJournalConsumerState::InsideMap;
+ }
+
+ void OnInt64Scalar(i64 /*value*/) override
+ {
+ ThrowMalformedPayload();
+ }
+
+ void OnUint64Scalar(ui64 /*value*/) override
+ {
+ ThrowMalformedPayload();
+ }
+
+ void OnDoubleScalar(double /*value*/) override
+ {
+ ThrowMalformedPayload();
+ }
+
+ void OnBooleanScalar(bool /*value*/) override
+ {
+ ThrowMalformedPayload();
+ }
+
+ void OnEntity() override
+ {
+ ThrowMalformedPayload();
+ }
+
+ void OnBeginList() override
+ {
+ ThrowMalformedPayload();
+ }
+
+ void OnListItem() override
+ {
+ if (State_ != EJournalConsumerState::Root) {
+ ThrowMalformedPayload();
+ }
+ State_ = EJournalConsumerState::AtItem;
+ }
+
+ void OnEndList() override
+ {
+ YT_ABORT();
+ }
+
+ void OnBeginMap() override
+ {
+ if (State_ != EJournalConsumerState::AtItem) {
+ ThrowMalformedPayload();
+ }
+ State_ = EJournalConsumerState::InsideMap;
+ }
+
+ void OnKeyedItem(TStringBuf key) override
+ {
+ if (State_ != EJournalConsumerState::InsideMap) {
+ ThrowMalformedPayload();
+ }
+ if (key != JournalPayloadKey) {
+ ThrowMalformedPayload();
+ }
+ State_ = EJournalConsumerState::AtData;
+ }
+
+ void OnEndMap() override
+ {
+ if (State_ != EJournalConsumerState::InsideMap) {
+ ThrowMalformedPayload();
+ }
+ State_ = EJournalConsumerState::Root;
+ }
+
+ void OnBeginAttributes() override
+ {
+ ThrowMalformedPayload();
+ }
+
+ void OnEndAttributes() override
+ {
+ YT_ABORT();
+ }
+
+
+ void ThrowMalformedPayload()
+ {
+ THROW_ERROR_EXCEPTION("Malformed journal payload");
+ }
+
+ void MaybeFlush()
+ {
+ if (BufferedByteSize_ >= MaxBufferedSize) {
+ Flush();
+ }
+ }
+};
+
+TWriteJournalCommand::TWriteJournalCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("journal_writer", JournalWriter)
+ .Default();
+ RegisterParameter("enable_chunk_preallocation", Options.EnableChunkPreallocation)
+ .Optional();
+ RegisterParameter("replica_lag_limit", Options.ReplicaLagLimit)
+ .Optional();
+}
+
+void TWriteJournalCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Config = UpdateYsonStruct(
+ context->GetConfig()->JournalWriter,
+ JournalWriter);
+
+ auto writer = context->GetClient()->CreateJournalWriter(
+ Path.GetPath(),
+ Options);
+
+ WaitFor(writer->Open())
+ .ThrowOnError();
+
+ TJournalConsumer consumer(writer);
+
+ auto format = context->GetInputFormat();
+ auto parser = CreateParserForFormat(format, EDataType::Tabular, &consumer);
+
+ struct TWriteBufferTag { };
+
+ auto buffer = TSharedMutableRef::Allocate<TWriteBufferTag>(context->GetConfig()->WriteBufferSize, {.InitializeStorage = false});
+
+ auto input = context->Request().InputStream;
+
+ while (true) {
+ auto bytesRead = WaitFor(input->Read(buffer))
+ .ValueOrThrow();
+
+ if (bytesRead == 0)
+ break;
+
+ parser->Read(TStringBuf(buffer.Begin(), bytesRead));
+ }
+
+ parser->Finish();
+
+ consumer.Flush();
+
+ WaitFor(writer->Close())
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTruncateJournalCommand::TTruncateJournalCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("row_count", RowCount);
+}
+
+void TTruncateJournalCommand::DoExecute(NYT::NDriver::ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->TruncateJournal(
+ Path,
+ RowCount,
+ Options);
+
+ WaitFor(asyncResult)
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/journal_commands.h b/yt/yt/client/driver/journal_commands.h
new file mode 100644
index 0000000000..8730d1873d
--- /dev/null
+++ b/yt/yt/client/driver/journal_commands.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/formats/format.h>
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadJournalCommand
+ : public TTypedCommand<NApi::TJournalReaderOptions>
+{
+public:
+ TReadJournalCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NYTree::INodePtr JournalReader;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWriteJournalCommand
+ : public TTypedCommand<NApi::TJournalWriterOptions>
+{
+public:
+ TWriteJournalCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NYTree::INodePtr JournalWriter;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTruncateJournalCommand
+ : public TTypedCommand<NApi::TTruncateJournalOptions>
+{
+public:
+ TTruncateJournalCommand();
+
+private:
+ NYPath::TYPath Path;
+ i64 RowCount;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/private.h b/yt/yt/client/driver/private.h
new file mode 100644
index 0000000000..d7fa243b11
--- /dev/null
+++ b/yt/yt/client/driver/private.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ICommand;
+DECLARE_REFCOUNTED_STRUCT(ICommandContext)
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger DriverLogger("Driver");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/proxy_discovery_cache.cpp b/yt/yt/client/driver/proxy_discovery_cache.cpp
new file mode 100644
index 0000000000..1a58eeaa5d
--- /dev/null
+++ b/yt/yt/client/driver/proxy_discovery_cache.cpp
@@ -0,0 +1,204 @@
+#include "proxy_discovery_cache.h"
+
+#include "private.h"
+
+#include <iterator>
+#include <yt/yt/client/api/client.h>
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/api/rpc_proxy/address_helpers.h>
+
+#include <yt/yt/core/misc/async_expiring_cache.h>
+
+#include <util/digest/multi.h>
+
+namespace NYT::NDriver {
+
+using namespace NYPath;
+using namespace NYTree;
+using namespace NYson;
+using namespace NApi;
+using namespace NConcurrency;
+using namespace NApi::NRpcProxy;
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TProxyDiscoveryRequest::operator==(const TProxyDiscoveryRequest& other) const
+{
+ return
+ Type == other.Type &&
+ Role == other.Role &&
+ AddressType == other.AddressType &&
+ NetworkName == other.NetworkName &&
+ IgnoreBalancers == other.IgnoreBalancers;
+}
+
+bool TProxyDiscoveryRequest::operator!=(const TProxyDiscoveryRequest& other) const
+{
+ return !(*this == other);
+}
+
+TProxyDiscoveryRequest::operator size_t() const
+{
+ return MultiHash(
+ Type,
+ Role,
+ AddressType,
+ NetworkName,
+ IgnoreBalancers);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TProxyDiscoveryRequest& request, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("{Type: %v, Role: %v, AddressType: %v, NetworkName: %v, IgnoreBalancers: %v}",
+ request.Type,
+ request.Role,
+ request.AddressType,
+ request.NetworkName,
+ request.IgnoreBalancers);
+}
+
+TString ToString(const TProxyDiscoveryRequest& request)
+{
+ return ToStringViaBuilder(request);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProxyDiscoveryCache
+ : public TAsyncExpiringCache<TProxyDiscoveryRequest, TProxyDiscoveryResponse>
+ , public IProxyDiscoveryCache
+{
+public:
+ TProxyDiscoveryCache(
+ TAsyncExpiringCacheConfigPtr config,
+ IClientPtr client)
+ : TAsyncExpiringCache(
+ std::move(config),
+ DriverLogger.WithTag("Cache: ProxyDiscovery"))
+ , Client_(std::move(client))
+ { }
+
+ TFuture<TProxyDiscoveryResponse> Discover(
+ const TProxyDiscoveryRequest& request) override
+ {
+ return Get(request);
+ }
+
+private:
+ const IClientPtr Client_;
+
+ TFuture<TProxyDiscoveryResponse> DoGet(
+ const TProxyDiscoveryRequest& request,
+ bool /*isPeriodicUpdate*/) noexcept override
+ {
+ return GetResponseByBalancers(request).Apply(
+ BIND([=, this, this_ = MakeStrong(this)] (const std::optional<TProxyDiscoveryResponse>& response) {
+ if (response) {
+ return MakeFuture<TProxyDiscoveryResponse>(std::move(*response));
+ }
+ return GetResponseByAddresses(request);
+ }).AsyncVia(Client_->GetConnection()->GetInvoker()));
+ }
+
+ TFuture<std::optional<TProxyDiscoveryResponse>> GetResponseByBalancers(const TProxyDiscoveryRequest& request)
+ {
+ if (request.IgnoreBalancers) {
+ return MakeFuture<std::optional<TProxyDiscoveryResponse>>(std::nullopt);
+ }
+
+ TGetNodeOptions options;
+ options.ReadFrom = EMasterChannelKind::LocalCache;
+ options.Attributes = {BalancersAttributeName};
+
+ auto path = GetProxyRegistryPath(request.Type) + "/@";
+ return Client_->GetNode(path, options).Apply(
+ BIND([=] (const TYsonString& yson) -> std::optional<TProxyDiscoveryResponse> {
+ auto attributes = ConvertTo<IMapNodePtr>(yson);
+
+ auto balancers = attributes->GetChildValueOrDefault<TBalancersMap>(BalancersAttributeName, {});
+
+ auto responseBalancers = GetBalancersOrNull(balancers, request.Role, request.AddressType, request.NetworkName);
+
+ if (!responseBalancers) {
+ return std::nullopt;
+ }
+
+ TProxyDiscoveryResponse response;
+ std::move(responseBalancers->begin(), responseBalancers->end(), std::back_inserter(response.Addresses));
+ return response;
+ }).AsyncVia(Client_->GetConnection()->GetInvoker()));
+ }
+
+ TFuture<TProxyDiscoveryResponse> GetResponseByAddresses(const TProxyDiscoveryRequest& request)
+ {
+ TGetNodeOptions options;
+ options.ReadFrom = EMasterChannelKind::LocalCache;
+ options.SuppressUpstreamSync = true;
+ options.SuppressTransactionCoordinatorSync = true;
+ options.Attributes = {BannedAttributeName, RoleAttributeName, AddressesAttributeName};
+
+ auto path = GetProxyRegistryPath(request.Type);
+ return Client_->GetNode(path, options).Apply(BIND([=] (const TYsonString& yson) {
+ TProxyDiscoveryResponse response;
+
+ for (const auto& [proxyAddress, proxyNode] : ConvertTo<THashMap<TString, IMapNodePtr>>(yson)) {
+ if (!proxyNode->FindChild(AliveNodeName)) {
+ continue;
+ }
+
+ if (proxyNode->Attributes().Get(BannedAttributeName, false)) {
+ continue;
+ }
+
+ if (proxyNode->Attributes().Get<TString>(RoleAttributeName, DefaultRpcProxyRole) != request.Role) {
+ continue;
+ }
+
+ auto addresses = proxyNode->Attributes().Get<TProxyAddressMap>(AddressesAttributeName, {});
+ auto address = GetAddressOrNull(addresses, request.AddressType, request.NetworkName);
+
+ if (address) {
+ response.Addresses.push_back(*address);
+ } else {
+ // COMPAT(verytable): Drop it after all rpc proxies migrate to 22.3.
+ if (!proxyNode->Attributes().Contains(AddressesAttributeName)) {
+ response.Addresses.push_back(proxyAddress);
+ }
+ }
+ }
+ return response;
+ }).AsyncVia(Client_->GetConnection()->GetInvoker()));
+ }
+
+
+ static TYPath GetProxyRegistryPath(EProxyType type)
+ {
+ switch (type) {
+ case EProxyType::Rpc:
+ return RpcProxiesPath;
+ case EProxyType::Grpc:
+ return GrpcProxiesPath;
+ default:
+ THROW_ERROR_EXCEPTION("Proxy type %Qlv is not supported",
+ type);
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IProxyDiscoveryCachePtr CreateProxyDiscoveryCache(
+ TAsyncExpiringCacheConfigPtr config,
+ IClientPtr client)
+{
+ return New<TProxyDiscoveryCache>(
+ std::move(config),
+ std::move(client));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/proxy_discovery_cache.h b/yt/yt/client/driver/proxy_discovery_cache.h
new file mode 100644
index 0000000000..f99b75cefa
--- /dev/null
+++ b/yt/yt/client/driver/proxy_discovery_cache.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/api/rpc_proxy/address_helpers.h>
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TProxyDiscoveryRequest
+{
+ NApi::EProxyType Type;
+ TString Role = NApi::DefaultRpcProxyRole;
+ NApi::NRpcProxy::EAddressType AddressType = NApi::NRpcProxy::DefaultAddressType;
+ TString NetworkName = NApi::NRpcProxy::DefaultNetworkName;
+ bool IgnoreBalancers = false;
+
+ bool operator==(const TProxyDiscoveryRequest& other) const;
+ bool operator!=(const TProxyDiscoveryRequest& other) const;
+
+ operator size_t() const;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TProxyDiscoveryRequest& request, TStringBuf spec);
+TString ToString(const TProxyDiscoveryRequest& request);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TProxyDiscoveryResponse
+{
+ std::vector<TString> Addresses;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IProxyDiscoveryCache
+ : public virtual TRefCounted
+{
+ virtual TFuture<TProxyDiscoveryResponse> Discover(
+ const TProxyDiscoveryRequest& request) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IProxyDiscoveryCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IProxyDiscoveryCachePtr CreateProxyDiscoveryCache(
+ TAsyncExpiringCacheConfigPtr config,
+ NApi::IClientPtr client);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/public.h b/yt/yt/client/driver/public.h
new file mode 100644
index 0000000000..6f4b5618da
--- /dev/null
+++ b/yt/yt/client/driver/public.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(IDriver)
+DECLARE_REFCOUNTED_STRUCT(IProxyDiscoveryCache)
+
+DECLARE_REFCOUNTED_CLASS(TDriverConfig)
+
+struct TCommandDescriptor;
+struct TDriverRequest;
+struct TEtag;
+struct TProxyDiscoveryRequest;
+struct TProxyDiscoveryResponse;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/query_commands.cpp b/yt/yt/client/driver/query_commands.cpp
new file mode 100644
index 0000000000..756c273175
--- /dev/null
+++ b/yt/yt/client/driver/query_commands.cpp
@@ -0,0 +1,198 @@
+#include "query_commands.h"
+
+#include <yt/yt/client/api/rowset.h>
+
+#include <yt/yt/client/formats/config.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT::NDriver {
+
+using namespace NYTree;
+using namespace NConcurrency;
+using namespace NYson;
+using namespace NTableClient;
+using namespace NFormats;
+
+//////////////////////////////////////////////////////////////////////////////
+
+TStartQueryCommand::TStartQueryCommand()
+{
+ RegisterParameter("engine", Engine);
+ RegisterParameter("query", Query);
+ RegisterParameter("stage", Options.QueryTrackerStage)
+ .Optional();
+ RegisterParameter("settings", Options.Settings)
+ .Optional();
+ RegisterParameter("draft", Options.Draft)
+ .Optional();
+ RegisterParameter("annotations", Options.Annotations)
+ .Optional();
+}
+
+void TStartQueryCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->StartQuery(Engine, Query, Options);
+ auto queryId = WaitFor(asyncResult)
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("query_id").Value(queryId)
+ .EndMap());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TAbortQueryCommand::TAbortQueryCommand()
+{
+ RegisterParameter("query_id", QueryId);
+ RegisterParameter("stage", Options.QueryTrackerStage)
+ .Optional();
+}
+
+void TAbortQueryCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->AbortQuery(QueryId, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+ ProduceEmptyOutput(context);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TGetQueryResultCommand::TGetQueryResultCommand()
+{
+ RegisterParameter("query_id", QueryId);
+ RegisterParameter("result_index", ResultIndex)
+ .Default(0);
+ RegisterParameter("stage", Options.QueryTrackerStage)
+ .Optional();
+}
+
+void TGetQueryResultCommand::DoExecute(ICommandContextPtr context)
+{
+ auto queryResult = WaitFor(context->GetClient()->GetQueryResult(QueryId, ResultIndex, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(ConvertToYsonString(queryResult));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TReadQueryResultCommand::TReadQueryResultCommand()
+{
+ RegisterParameter("query_id", QueryId);
+ RegisterParameter("result_index", ResultIndex)
+ .Default(0);
+ RegisterParameter("stage", Options.QueryTrackerStage)
+ .Optional();
+ RegisterParameter("columns", Options.Columns)
+ .Default();
+ RegisterParameter("lower_row_index", Options.LowerRowIndex)
+ .Default();
+ RegisterParameter("upper_row_index", Options.UpperRowIndex)
+ .Default();
+}
+
+void TReadQueryResultCommand::DoExecute(ICommandContextPtr context)
+{
+ auto rowset = WaitFor(context->GetClient()->ReadQueryResult(QueryId, ResultIndex, Options))
+ .ValueOrThrow();
+
+ auto writer = CreateStaticTableWriterForFormat(
+ context->GetOutputFormat(),
+ rowset->GetNameTable(),
+ {rowset->GetSchema()},
+ context->Request().OutputStream,
+ /*enableContextSaving*/ false,
+ New<TControlAttributesConfig>(),
+ /*keyColumnCount*/ 0);
+
+ writer->Write(rowset->GetRows());
+ WaitFor(writer->Close())
+ .ThrowOnError();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TGetQueryCommand::TGetQueryCommand()
+{
+ RegisterParameter("query_id", QueryId);
+ RegisterParameter("attributes", Options.Attributes)
+ .Optional();
+ RegisterParameter("stage", Options.QueryTrackerStage)
+ .Optional();
+}
+
+void TGetQueryCommand::DoExecute(ICommandContextPtr context)
+{
+ auto query = WaitFor(context->GetClient()->GetQuery(QueryId, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(ConvertToYsonString(query));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TListQueriesCommand::TListQueriesCommand()
+{
+ RegisterParameter("stage", Options.QueryTrackerStage)
+ .Default("production");
+ RegisterParameter("from_time", Options.FromTime)
+ .Optional();
+ RegisterParameter("to_time", Options.ToTime)
+ .Optional();
+ RegisterParameter("cursor_time", Options.CursorTime)
+ .Optional();
+ RegisterParameter("cursor_direction", Options.CursorDirection)
+ .Optional();
+ RegisterParameter("user", Options.UserFilter)
+ .Optional();
+ RegisterParameter("state", Options.StateFilter)
+ .Optional();
+ RegisterParameter("engine", Options.EngineFilter)
+ .Optional();
+ RegisterParameter("filter", Options.SubstrFilter)
+ .Optional();
+ RegisterParameter("limit", Options.Limit)
+ .Optional();
+ RegisterParameter("attributes", Options.Attributes)
+ .Optional();
+}
+
+void TListQueriesCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->ListQueries(Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("queries").Value(result.Queries)
+ .Item("incomplete").Value(result.Incomplete)
+ .Item("timestamp").Value(result.Timestamp)
+ .EndMap());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TAlterQueryCommand::TAlterQueryCommand()
+{
+ RegisterParameter("query_id", QueryId);
+ RegisterParameter("annotations", Options.Annotations)
+ .Optional();
+}
+
+void TAlterQueryCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->AlterQuery(QueryId, Options))
+ .ThrowOnError();
+ ProduceEmptyOutput(context);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/query_commands.h b/yt/yt/client/driver/query_commands.h
new file mode 100644
index 0000000000..dfffcfd059
--- /dev/null
+++ b/yt/yt/client/driver/query_commands.h
@@ -0,0 +1,112 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/client/query_tracker_client/public.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////
+
+class TStartQueryCommand
+ : public TTypedCommand<NApi::TStartQueryOptions>
+{
+public:
+ TStartQueryCommand();
+
+private:
+ NQueryTrackerClient::EQueryEngine Engine;
+ TString Query;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class TAbortQueryCommand
+ : public TTypedCommand<NApi::TAbortQueryOptions>
+{
+public:
+ TAbortQueryCommand();
+
+private:
+ NQueryTrackerClient::TQueryId QueryId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class TGetQueryResultCommand
+ : public TTypedCommand<NApi::TGetQueryResultOptions>
+{
+public:
+ TGetQueryResultCommand();
+
+private:
+ NQueryTrackerClient::TQueryId QueryId;
+ i64 ResultIndex;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class TReadQueryResultCommand
+ : public TTypedCommand<NApi::TReadQueryResultOptions>
+{
+public:
+ TReadQueryResultCommand();
+
+private:
+ NQueryTrackerClient::TQueryId QueryId;
+ i64 ResultIndex;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class TGetQueryCommand
+ : public TTypedCommand<NApi::TGetQueryOptions>
+{
+public:
+ TGetQueryCommand();
+
+private:
+ NQueryTrackerClient::TQueryId QueryId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class TListQueriesCommand
+ : public TTypedCommand<NApi::TListQueriesOptions>
+{
+public:
+ TListQueriesCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class TAlterQueryCommand
+ : public TTypedCommand<NApi::TAlterQueryOptions>
+{
+public:
+ TAlterQueryCommand();
+
+private:
+ NQueryTrackerClient::TQueryId QueryId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/queue_commands.cpp b/yt/yt/client/driver/queue_commands.cpp
new file mode 100644
index 0000000000..771fce09e1
--- /dev/null
+++ b/yt/yt/client/driver/queue_commands.cpp
@@ -0,0 +1,206 @@
+#include "queue_commands.h"
+#include "config.h"
+
+#include <yt/yt/client/api/config.h>
+
+namespace NYT::NDriver {
+
+using namespace NConcurrency;
+using namespace NApi;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRegisterQueueConsumerCommand::TRegisterQueueConsumerCommand()
+{
+ RegisterParameter("queue_path", QueuePath);
+ RegisterParameter("consumer_path", ConsumerPath);
+ RegisterParameter("vital", Vital);
+ RegisterParameter("partitions", Partitions)
+ .Default();
+}
+
+void TRegisterQueueConsumerCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Partitions = Partitions;
+
+ auto client = context->GetClient();
+ auto asyncResult = client->RegisterQueueConsumer(
+ QueuePath,
+ ConsumerPath,
+ Vital,
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnregisterQueueConsumerCommand::TUnregisterQueueConsumerCommand()
+{
+ RegisterParameter("queue_path", QueuePath);
+ RegisterParameter("consumer_path", ConsumerPath);
+}
+
+void TUnregisterQueueConsumerCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->UnregisterQueueConsumer(
+ QueuePath,
+ ConsumerPath,
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TListQueueConsumerRegistrationsCommand::TListQueueConsumerRegistrationsCommand()
+{
+ RegisterParameter("queue_path", QueuePath)
+ .Default();
+ RegisterParameter("consumer_path", ConsumerPath)
+ .Default();
+}
+
+void TListQueueConsumerRegistrationsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->ListQueueConsumerRegistrations(
+ QueuePath,
+ ConsumerPath,
+ Options);
+ auto registrations = WaitFor(asyncResult)
+ .ValueOrThrow();
+
+ ProduceOutput(context, [&](NYson::IYsonConsumer* consumer) {
+ BuildYsonFluently(consumer)
+ .DoListFor(registrations, [=] (TFluentList fluent, const TListQueueConsumerRegistrationsResult& registration) {
+ fluent
+ .Item().BeginMap()
+ .Item("queue_path").Value(registration.QueuePath)
+ .Item("consumer_path").Value(registration.ConsumerPath)
+ .Item("vital").Value(registration.Vital)
+ .Item("partitions").Value(registration.Partitions)
+ .EndMap();
+ });
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TPullQueueCommand::TPullQueueCommand()
+{
+ RegisterParameter("queue_path", QueuePath);
+ RegisterParameter("offset", Offset);
+ RegisterParameter("partition_index", PartitionIndex);
+
+ RegisterParameter("max_row_count", RowBatchReadOptions.MaxRowCount)
+ .Optional();
+ RegisterParameter("max_data_weight", RowBatchReadOptions.MaxDataWeight)
+ .Optional();
+ RegisterParameter("data_weight_per_row_hint", RowBatchReadOptions.DataWeightPerRowHint)
+ .Optional();
+
+ RegisterParameter("replica_consistency", Options.ReplicaConsistency)
+ .Optional();
+}
+
+void TPullQueueCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+
+ auto result = WaitFor(client->PullQueue(
+ QueuePath,
+ Offset,
+ PartitionIndex,
+ RowBatchReadOptions,
+ Options))
+ .ValueOrThrow();
+
+ auto format = context->GetOutputFormat();
+ auto output = context->Request().OutputStream;
+ auto writer = CreateSchemafulWriterForFormat(format, result->GetSchema(), output);
+
+ writer->Write(result->GetRows());
+
+ WaitFor(writer->Close())
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TPullConsumerCommand::TPullConsumerCommand()
+{
+ RegisterParameter("consumer_path", ConsumerPath);
+ RegisterParameter("queue_path", QueuePath);
+ RegisterParameter("offset", Offset);
+ RegisterParameter("partition_index", PartitionIndex);
+
+ RegisterParameter("max_row_count", RowBatchReadOptions.MaxRowCount)
+ .Optional();
+ RegisterParameter("max_data_weight", RowBatchReadOptions.MaxDataWeight)
+ .Optional();
+ RegisterParameter("data_weight_per_row_hint", RowBatchReadOptions.DataWeightPerRowHint)
+ .Optional();
+
+ RegisterParameter("replica_consistency", Options.ReplicaConsistency)
+ .Optional();
+}
+
+void TPullConsumerCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+
+ auto result = WaitFor(client->PullConsumer(
+ ConsumerPath,
+ QueuePath,
+ Offset,
+ PartitionIndex,
+ RowBatchReadOptions,
+ Options))
+ .ValueOrThrow();
+
+ auto format = context->GetOutputFormat();
+ auto output = context->Request().OutputStream;
+ auto writer = CreateSchemafulWriterForFormat(format, result->GetSchema(), output);
+
+ writer->Write(result->GetRows());
+
+ WaitFor(writer->Close())
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAdvanceConsumerCommand::TAdvanceConsumerCommand()
+{
+ RegisterParameter("consumer_path", ConsumerPath);
+ RegisterParameter("queue_path", QueuePath);
+ RegisterParameter("partition_index", PartitionIndex);
+ RegisterParameter("old_offset", OldOffset)
+ .Optional();
+ RegisterParameter("new_offset", NewOffset);
+}
+
+void TAdvanceConsumerCommand::DoExecute(ICommandContextPtr context)
+{
+ auto transaction = GetTransaction(context);
+
+ transaction->AdvanceConsumer(ConsumerPath, QueuePath, PartitionIndex, OldOffset, NewOffset);
+
+ if (ShouldCommitTransaction()) {
+ WaitFor(transaction->Commit())
+ .ThrowOnError();
+ }
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/queue_commands.h b/yt/yt/client/driver/queue_commands.h
new file mode 100644
index 0000000000..0cc85c6444
--- /dev/null
+++ b/yt/yt/client/driver/queue_commands.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRegisterQueueConsumerCommand
+ : public TTypedCommand<NApi::TRegisterQueueConsumerOptions>
+{
+public:
+ TRegisterQueueConsumerCommand();
+
+private:
+ NYPath::TRichYPath QueuePath;
+ NYPath::TRichYPath ConsumerPath;
+ bool Vital;
+ std::optional<std::vector<int>> Partitions;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnregisterQueueConsumerCommand
+ : public TTypedCommand<NApi::TUnregisterQueueConsumerOptions>
+{
+public:
+ TUnregisterQueueConsumerCommand();
+
+private:
+ NYPath::TRichYPath QueuePath;
+ NYPath::TRichYPath ConsumerPath;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TListQueueConsumerRegistrationsCommand
+ : public TTypedCommand<NApi::TListQueueConsumerRegistrationsOptions>
+{
+public:
+ TListQueueConsumerRegistrationsCommand();
+
+private:
+ std::optional<NYPath::TRichYPath> QueuePath;
+ std::optional<NYPath::TRichYPath> ConsumerPath;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPullQueueCommand
+ : public TTypedCommand<NApi::TPullQueueOptions>
+{
+public:
+ TPullQueueCommand();
+
+private:
+ NYPath::TRichYPath QueuePath;
+ i64 Offset;
+ int PartitionIndex;
+ NQueueClient::TQueueRowBatchReadOptions RowBatchReadOptions;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPullConsumerCommand
+ : public TTypedCommand<NApi::TPullConsumerOptions>
+{
+public:
+ TPullConsumerCommand();
+
+private:
+ NYPath::TRichYPath ConsumerPath;
+ NYPath::TRichYPath QueuePath;
+ i64 Offset;
+ int PartitionIndex;
+ NQueueClient::TQueueRowBatchReadOptions RowBatchReadOptions;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TAdvanceConsumerOptions
+ : public TTabletWriteOptions
+{ };
+
+class TAdvanceConsumerCommand
+ : public TTypedCommand<TAdvanceConsumerOptions>
+{
+public:
+ TAdvanceConsumerCommand();
+
+private:
+ NYPath::TRichYPath ConsumerPath;
+ NYPath::TRichYPath QueuePath;
+ int PartitionIndex;
+ std::optional<i64> OldOffset;
+ i64 NewOffset;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/scheduler_commands.cpp b/yt/yt/client/driver/scheduler_commands.cpp
new file mode 100644
index 0000000000..fd871a3b6d
--- /dev/null
+++ b/yt/yt/client/driver/scheduler_commands.cpp
@@ -0,0 +1,611 @@
+#include "scheduler_commands.h"
+#include "driver.h"
+
+#include <yt/yt/client/api/file_reader.h>
+#include <yt/yt/client/api/rowset.h>
+
+#include <yt/yt/client/table_client/unversioned_writer.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/logging/fluent_log.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+using namespace NApi;
+using namespace NConcurrency;
+using namespace NScheduler;
+using namespace NTableClient;
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static inline const NLogging::TLogger JobShellStructuredLogger("JobShell");
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDumpJobContextCommand::TDumpJobContextCommand()
+{
+ RegisterParameter("job_id", JobId);
+ RegisterParameter("path", Path);
+}
+
+void TDumpJobContextCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->DumpJobContext(JobId, Path))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetJobInputCommand::TGetJobInputCommand()
+{
+ RegisterParameter("job_id", JobId);
+ RegisterParameter("job_spec_source", Options.JobSpecSource)
+ .Default(EJobSpecSource::Auto);
+}
+
+void TGetJobInputCommand::DoExecute(ICommandContextPtr context)
+{
+ auto jobInputReader = WaitFor(context->GetClient()->GetJobInput(JobId, Options))
+ .ValueOrThrow();
+
+ auto output = context->Request().OutputStream;
+ PipeInputToOutput(jobInputReader, output);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetJobInputPathsCommand::TGetJobInputPathsCommand()
+{
+ RegisterParameter("job_id", JobId);
+ RegisterParameter("job_spec_source", Options.JobSpecSource)
+ .Default(EJobSpecSource::Auto);
+}
+
+void TGetJobInputPathsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto inputPaths = WaitFor(context->GetClient()->GetJobInputPaths(JobId, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(std::move(inputPaths));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetJobSpecCommand::TGetJobSpecCommand()
+{
+ RegisterParameter("job_id", JobId);
+ RegisterParameter("job_spec_source", Options.JobSpecSource)
+ .Default(EJobSpecSource::Auto);
+ RegisterParameter("omit_node_directory", Options.OmitNodeDirectory)
+ .Default(true);
+ RegisterParameter("omit_input_table_specs", Options.OmitInputTableSpecs)
+ .Default(false);
+ RegisterParameter("omit_output_table_specs", Options.OmitOutputTableSpecs)
+ .Default(false);
+}
+
+void TGetJobSpecCommand::DoExecute(ICommandContextPtr context)
+{
+ auto jobSpec = WaitFor(context->GetClient()->GetJobSpec(JobId, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(std::move(jobSpec));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetJobStderrCommand::TGetJobStderrCommand()
+{
+ RegisterParameter("job_id", JobId);
+}
+
+void TGetJobStderrCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->GetJobStderr(OperationIdOrAlias, JobId, Options))
+ .ValueOrThrow();
+
+ auto output = context->Request().OutputStream;
+ WaitFor(output->Write(result))
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetJobFailContextCommand::TGetJobFailContextCommand()
+{
+ RegisterParameter("job_id", JobId);
+}
+
+void TGetJobFailContextCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->GetJobFailContext(OperationIdOrAlias, JobId, Options))
+ .ValueOrThrow();
+
+ auto output = context->Request().OutputStream;
+ WaitFor(output->Write(result))
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TListOperationsCommand::TListOperationsCommand()
+{
+ RegisterParameter("from_time", Options.FromTime)
+ .Optional();
+ RegisterParameter("to_time", Options.ToTime)
+ .Optional();
+ RegisterParameter("cursor_time", Options.CursorTime)
+ .Optional();
+ RegisterParameter("cursor_direction", Options.CursorDirection)
+ .Optional();
+ RegisterParameter("user", Options.UserFilter)
+ .Optional();
+ RegisterParameter("access", Options.AccessFilter)
+ .Optional();
+ RegisterParameter("state", Options.StateFilter)
+ .Optional();
+ RegisterParameter("type", Options.TypeFilter)
+ .Optional();
+ RegisterParameter("filter", Options.SubstrFilter)
+ .Optional();
+ RegisterParameter("pool_tree", Options.PoolTree)
+ .Optional();
+ RegisterParameter("pool", Options.Pool)
+ .Optional();
+ RegisterParameter("with_failed_jobs", Options.WithFailedJobs)
+ .Optional();
+ RegisterParameter("include_archive", Options.IncludeArchive)
+ .Optional();
+ RegisterParameter("include_counters", Options.IncludeCounters)
+ .Optional();
+ RegisterParameter("limit", Options.Limit)
+ .Optional();
+ RegisterParameter("attributes", Options.Attributes)
+ .Optional();
+ RegisterParameter("enable_ui_mode", EnableUIMode)
+ .Optional();
+ RegisterParameter("archive_fetching_timeout", Options.ArchiveFetchingTimeout)
+ .Optional();
+}
+
+void TListOperationsCommand::BuildOperations(const TListOperationsResult& result, TFluentMap fluent)
+{
+ bool needType = !Options.Attributes || Options.Attributes->contains("type");
+ // COMPAT(levysotsky): "operation_type" is a deprecated synonym for "type".
+ bool needOperationType = !Options.Attributes || Options.Attributes->contains("operation_type");
+
+ if (EnableUIMode) {
+ fluent
+ .Item("operations")
+ .BeginAttributes()
+ .Item("incomplete").Value(result.Incomplete)
+ .EndAttributes()
+ .DoListFor(result.Operations, [&] (TFluentList fluent, const TOperation& operation) {
+ fluent.Item().Do([&] (TFluentAny fluent) {
+ Serialize(operation, fluent.GetConsumer(), needType, needOperationType, /* idWithAttributes */ true);
+ });
+ });
+ } else {
+ fluent
+ .Item("operations")
+ .DoListFor(result.Operations, [&] (TFluentList fluent, const TOperation& operation) {
+ fluent.Item().Do([&] (TFluentAny fluent) {
+ Serialize(operation, fluent.GetConsumer(), needType, needOperationType);
+ });
+ })
+ .Item("incomplete").Value(result.Incomplete);
+ }
+}
+
+void TListOperationsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->ListOperations(Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Do(std::bind(&TListOperationsCommand::BuildOperations, this, result, std::placeholders::_1))
+ .DoIf(result.PoolTreeCounts.operator bool(), [&] (TFluentMap fluent) {
+ fluent.Item("pool_tree_counts").BeginMap()
+ .DoFor(*result.PoolTreeCounts, [] (TFluentMap fluent, const auto& item) {
+ fluent.Item(item.first).Value(item.second);
+ })
+ .EndMap();
+ })
+ .DoIf(result.PoolCounts.operator bool(), [&] (TFluentMap fluent) {
+ fluent.Item("pool_counts").BeginMap()
+ .DoFor(*result.PoolCounts, [] (TFluentMap fluent, const auto& item) {
+ fluent.Item(item.first).Value(item.second);
+ })
+ .EndMap();
+ })
+ .DoIf(result.UserCounts.operator bool(), [&] (TFluentMap fluent) {
+ fluent.Item("user_counts").BeginMap()
+ .DoFor(*result.UserCounts, [] (TFluentMap fluent, const auto& item) {
+ fluent.Item(item.first).Value(item.second);
+ })
+ .EndMap();
+ })
+ .DoIf(result.StateCounts.operator bool(), [&] (TFluentMap fluent) {
+ fluent.Item("state_counts").BeginMap()
+ .DoFor(TEnumTraits<EOperationState>::GetDomainValues(), [&result] (TFluentMap fluent, const EOperationState& item) {
+ i64 count = (*result.StateCounts)[item];
+ if (count) {
+ fluent.Item(FormatEnum(item)).Value(count);
+ }
+ })
+ .EndMap();
+ })
+ .DoIf(result.TypeCounts.operator bool(), [&] (TFluentMap fluent) {
+ fluent.Item("type_counts").BeginMap()
+ .DoFor(TEnumTraits<EOperationType>::GetDomainValues(), [&result] (TFluentMap fluent, const EOperationType& item) {
+ i64 count = (*result.TypeCounts)[item];
+ if (count) {
+ fluent.Item(FormatEnum(item)).Value(count);
+ }
+ })
+ .EndMap();
+ })
+ .OptionalItem("failed_jobs_count", result.FailedJobsCount)
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TListJobsCommand::TListJobsCommand()
+{
+ RegisterParameter("type", Options.Type)
+ .Alias("job_type")
+ .Optional();
+ RegisterParameter("state", Options.State)
+ .Alias("job_state")
+ .Optional();
+ RegisterParameter("address", Options.Address)
+ .Optional();
+ RegisterParameter("with_stderr", Options.WithStderr)
+ .Optional();
+ RegisterParameter("with_spec", Options.WithSpec)
+ .Optional();
+ RegisterParameter("with_fail_context", Options.WithFailContext)
+ .Optional();
+ RegisterParameter("with_competitors", Options.WithCompetitors)
+ .Optional();
+ RegisterParameter("job_competition_id", Options.JobCompetitionId)
+ .Optional();
+ RegisterParameter("task_name", Options.TaskName)
+ .Optional();
+
+ RegisterParameter("sort_field", Options.SortField)
+ .Optional();
+ RegisterParameter("sort_order", Options.SortOrder)
+ .Optional();
+
+ RegisterParameter("limit", Options.Limit)
+ .Optional();
+ RegisterParameter("offset", Options.Offset)
+ .Optional();
+
+ RegisterParameter("data_source", Options.DataSource)
+ .Optional();
+
+ RegisterParameter("include_cypress", Options.IncludeCypress)
+ .Optional();
+ RegisterParameter("include_controller_agent", Options.IncludeControllerAgent)
+ .Alias("include_runtime")
+ .Alias("include_scheduler")
+ .Optional();
+ RegisterParameter("include_archive", Options.IncludeArchive)
+ .Optional();
+
+ RegisterParameter("running_jobs_lookbehind_period", Options.RunningJobsLookbehindPeriod)
+ .Optional();
+}
+
+void TListJobsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto result = WaitFor(context->GetClient()->ListJobs(OperationIdOrAlias, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("jobs").DoListFor(result.Jobs, [] (TFluentList fluent, const TJob& job) {
+ fluent
+ .Item().Do([&] (TFluentAny innerFluent) {
+ Serialize(job, innerFluent.GetConsumer(), TStringBuf("id"));
+ });
+ })
+ .Item("cypress_job_count").Value(result.CypressJobCount)
+ // COMPAT(asaitgalin): Remove it in favor of controller_agent_job_count
+ .Item("scheduler_job_count").Value(result.ControllerAgentJobCount)
+ .Item("controller_agent_job_count").Value(result.ControllerAgentJobCount)
+ .Item("archive_job_count").Value(result.ArchiveJobCount)
+ .Item("type_counts").DoMapFor(TEnumTraits<NJobTrackerClient::EJobType>::GetDomainValues(), [&] (TFluentMap fluent, const auto& item) {
+ i64 count = result.Statistics.TypeCounts[item];
+ if (count) {
+ fluent.Item(FormatEnum(item)).Value(count);
+ }
+ })
+ .Item("state_counts").DoMapFor(TEnumTraits<NJobTrackerClient::EJobState>::GetDomainValues(), [&] (TFluentMap fluent, const auto& item) {
+ i64 count = result.Statistics.StateCounts[item];
+ if (count) {
+ fluent.Item(FormatEnum(item)).Value(count);
+ }
+ })
+ .Item("errors").Value(result.Errors)
+ .EndMap());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetJobCommand::TGetJobCommand()
+{
+ RegisterParameter("job_id", JobId);
+ RegisterParameter("attributes", Options.Attributes)
+ .Optional();
+}
+
+void TGetJobCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->GetJob(OperationIdOrAlias, JobId, Options);
+ auto result = WaitFor(asyncResult)
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAbandonJobCommand::TAbandonJobCommand()
+{
+ RegisterParameter("job_id", JobId);
+}
+
+void TAbandonJobCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->AbandonJob(JobId))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TPollJobShellCommand::TPollJobShellCommand()
+{
+ RegisterParameter("job_id", JobId);
+ RegisterParameter("parameters", Parameters);
+ RegisterParameter("shell_name", ShellName)
+ .Default();
+
+ RegisterPostprocessor([&] {
+ // Compatibility with initial job shell protocol.
+ if (Parameters->GetType() == NYTree::ENodeType::String) {
+ Parameters = NYTree::ConvertToNode(NYson::TYsonString(Parameters->AsString()->GetValue()));
+ }
+ });
+}
+
+void TPollJobShellCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResponse = context->GetClient()->PollJobShell(
+ JobId,
+ ShellName,
+ ConvertToYsonString(Parameters),
+ Options);
+ auto response = WaitFor(asyncResponse)
+ .ValueOrThrow();
+
+ if (response.LoggingContext) {
+ LogStructuredEventFluently(JobShellStructuredLogger, NLogging::ELogLevel::Info)
+ .Do([&] (TFluentMap fluent) {
+ fluent.GetConsumer()->OnRaw(response.LoggingContext);
+ })
+ .Item("user").Value(context->Request().AuthenticatedUser)
+ .DoIf(static_cast<bool>(context->Request().UserRemoteAddress), [&] (TFluentMap fluent) {
+ fluent.Item("remote_address").Value(ToString(context->Request().UserRemoteAddress));
+ });
+ }
+
+ ProduceSingleOutputValue(context, "result", response.Result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAbortJobCommand::TAbortJobCommand()
+{
+ RegisterParameter("job_id", JobId);
+ RegisterParameter("interrupt_timeout", Options.InterruptTimeout)
+ .Optional();
+}
+
+void TAbortJobCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->AbortJob(JobId, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStartOperationCommand::TStartOperationCommand(std::optional<NScheduler::EOperationType> operationType)
+{
+ RegisterParameter("spec", Spec);
+ if (operationType) {
+ OperationType = *operationType;
+ } else {
+ RegisterParameter("operation_type", OperationType);
+ }
+}
+
+void TStartOperationCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncOperationId = context->GetClient()->StartOperation(
+ OperationType,
+ ConvertToYsonString(Spec),
+ Options);
+
+ auto operationId = WaitFor(asyncOperationId)
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "operation_id", operationId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMapCommand::TMapCommand()
+ : TStartOperationCommand(EOperationType::Map)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMergeCommand::TMergeCommand()
+ : TStartOperationCommand(EOperationType::Merge)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSortCommand::TSortCommand()
+ : TStartOperationCommand(EOperationType::Sort)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEraseCommand::TEraseCommand()
+ : TStartOperationCommand(EOperationType::Erase)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReduceCommand::TReduceCommand()
+ : TStartOperationCommand(EOperationType::Reduce)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TJoinReduceCommand::TJoinReduceCommand()
+ : TStartOperationCommand(EOperationType::JoinReduce)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMapReduceCommand::TMapReduceCommand()
+ : TStartOperationCommand(EOperationType::MapReduce)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRemoteCopyCommand::TRemoteCopyCommand()
+ : TStartOperationCommand(EOperationType::RemoteCopy)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAbortOperationCommand::TAbortOperationCommand()
+{
+ RegisterParameter("abort_message", Options.AbortMessage)
+ .Optional();
+}
+
+void TAbortOperationCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->AbortOperation(OperationIdOrAlias, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSuspendOperationCommand::TSuspendOperationCommand()
+{
+ RegisterParameter("abort_running_jobs", Options.AbortRunningJobs)
+ .Optional();
+}
+
+void TSuspendOperationCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->SuspendOperation(OperationIdOrAlias, Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TResumeOperationCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->ResumeOperation(OperationIdOrAlias))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TCompleteOperationCommand::DoExecute(ICommandContextPtr context)
+{
+ WaitFor(context->GetClient()->CompleteOperation(OperationIdOrAlias))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUpdateOperationParametersCommand::TUpdateOperationParametersCommand()
+{
+ RegisterParameter("parameters", Parameters);
+}
+
+void TUpdateOperationParametersCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->UpdateOperationParameters(
+ OperationIdOrAlias,
+ ConvertToYsonString(Parameters),
+ Options);
+
+ WaitFor(asyncResult)
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetOperationCommand::TGetOperationCommand()
+{
+ RegisterParameter("attributes", Options.Attributes)
+ .Optional();
+ RegisterParameter("include_runtime", Options.IncludeRuntime)
+ .Alias("include_scheduler")
+ .Optional();
+ RegisterParameter("maximum_cypress_progress_age", Options.MaximumCypressProgressAge)
+ .Optional();
+}
+
+void TGetOperationCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->GetOperation(OperationIdOrAlias, Options);
+ auto operation = WaitFor(asyncResult)
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .Do([&] (TFluentAny fluent) {
+ Serialize(
+ operation,
+ fluent.GetConsumer(),
+ /* needType */ true,
+ /* needOperationType */ true);
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/scheduler_commands.h b/yt/yt/client/driver/scheduler_commands.h
new file mode 100644
index 0000000000..450e45be14
--- /dev/null
+++ b/yt/yt/client/driver/scheduler_commands.h
@@ -0,0 +1,382 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/job_tracker_client/public.h>
+
+#include <yt/yt/client/scheduler/operation_id_or_alias.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+class TSimpleOperationCommandBase
+ : public virtual TTypedCommandBase<TOptions>
+{
+private:
+ NScheduler::TOperationId OperationId;
+ std::optional<TString> OperationAlias;
+
+protected:
+ // Is calculated by two fields above.
+ NScheduler::TOperationIdOrAlias OperationIdOrAlias;
+
+public:
+ TSimpleOperationCommandBase()
+ {
+ this->RegisterParameter("operation_id", OperationId)
+ .Default();
+ this->RegisterParameter("operation_alias", OperationAlias)
+ .Default();
+
+ this->RegisterPostprocessor([&] {
+ if (!OperationId.IsEmpty() && OperationAlias.operator bool() ||
+ OperationId.IsEmpty() && !OperationAlias.operator bool())
+ {
+ THROW_ERROR_EXCEPTION("Exactly one of \"operation_id\" and \"operation_alias\" should be set")
+ << TErrorAttribute("operation_id", OperationId)
+ << TErrorAttribute("operation_alias", OperationAlias);
+ }
+
+ if (OperationId) {
+ OperationIdOrAlias = OperationId;
+ } else {
+ OperationIdOrAlias = *OperationAlias;
+ }
+ });
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDumpJobContextCommand
+ : public TTypedCommand<NApi::TDumpJobContextOptions>
+{
+public:
+ TDumpJobContextCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+ NYPath::TYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetJobInputCommand
+ : public TTypedCommand<NApi::TGetJobInputOptions>
+{
+public:
+ TGetJobInputCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetJobInputPathsCommand
+ : public TTypedCommand<NApi::TGetJobInputPathsOptions>
+{
+public:
+ TGetJobInputPathsCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetJobSpecCommand
+ : public TTypedCommand<NApi::TGetJobSpecOptions>
+{
+public:
+ TGetJobSpecCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetJobStderrCommand
+ : public TSimpleOperationCommandBase<NApi::TGetJobStderrOptions>
+{
+public:
+ TGetJobStderrCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetJobFailContextCommand
+ : public TSimpleOperationCommandBase<NApi::TGetJobFailContextOptions>
+{
+public:
+ TGetJobFailContextCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TListOperationsCommand
+ : public TTypedCommand<NApi::TListOperationsOptions>
+{
+public:
+ TListOperationsCommand();
+
+private:
+ bool EnableUIMode = false;
+
+ void BuildOperations(const NApi::TListOperationsResult& result, NYTree::TFluentMap fluent);
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TListJobsCommand
+ : public TSimpleOperationCommandBase<NApi::TListJobsOptions>
+{
+public:
+ TListJobsCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetJobCommand
+ : public TSimpleOperationCommandBase<NApi::TGetJobOptions>
+{
+public:
+ TGetJobCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAbandonJobCommand
+ : public TTypedCommand<NApi::TAbandonJobOptions>
+{
+public:
+ TAbandonJobCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPollJobShellCommand
+ : public TTypedCommand<NApi::TPollJobShellOptions>
+{
+public:
+ TPollJobShellCommand();
+
+private:
+ NJobTrackerClient::TJobId JobId;
+ NYTree::INodePtr Parameters;
+ std::optional<TString> ShellName;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAbortJobCommand
+ : public TTypedCommand<NApi::TAbortJobOptions>
+{
+private:
+ NJobTrackerClient::TJobId JobId;
+
+public:
+ TAbortJobCommand();
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TStartOperationCommand
+ : public TTypedCommand<NApi::TStartOperationOptions>
+{
+public:
+ explicit TStartOperationCommand(
+ std::optional<NScheduler::EOperationType> operationType = std::optional<NScheduler::EOperationType>());
+
+private:
+ NYTree::INodePtr Spec;
+ NScheduler::EOperationType OperationType;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMapCommand
+ : public TStartOperationCommand
+{
+public:
+ TMapCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMergeCommand
+ : public TStartOperationCommand
+{
+public:
+ TMergeCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSortCommand
+ : public TStartOperationCommand
+{
+public:
+ TSortCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEraseCommand
+ : public TStartOperationCommand
+{
+public:
+ TEraseCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReduceCommand
+ : public TStartOperationCommand
+{
+public:
+ TReduceCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TJoinReduceCommand
+ : public TStartOperationCommand
+{
+public:
+ TJoinReduceCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMapReduceCommand
+ : public TStartOperationCommand
+{
+public:
+ TMapReduceCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoteCopyCommand
+ : public TStartOperationCommand
+{
+public:
+ TRemoteCopyCommand();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAbortOperationCommand
+ : public TSimpleOperationCommandBase<NApi::TAbortOperationOptions>
+{
+public:
+ TAbortOperationCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSuspendOperationCommand
+ : public TSimpleOperationCommandBase<NApi::TSuspendOperationOptions>
+{
+public:
+ TSuspendOperationCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TResumeOperationCommand
+ : public TSimpleOperationCommandBase<NApi::TResumeOperationOptions>
+{
+public:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCompleteOperationCommand
+ : public TSimpleOperationCommandBase<NApi::TCompleteOperationOptions>
+{
+public:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUpdateOperationParametersCommand
+ : public TSimpleOperationCommandBase<NApi::TUpdateOperationParametersOptions>
+{
+public:
+ TUpdateOperationParametersCommand();
+
+private:
+ NYTree::INodePtr Parameters;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetOperationCommand
+ : public TSimpleOperationCommandBase<NApi::TGetOperationOptions>
+{
+public:
+ TGetOperationCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/table_commands.cpp b/yt/yt/client/driver/table_commands.cpp
new file mode 100644
index 0000000000..71e8d252ea
--- /dev/null
+++ b/yt/yt/client/driver/table_commands.cpp
@@ -0,0 +1,1396 @@
+#include "table_commands.h"
+#include "config.h"
+#include "helpers.h"
+
+#include <yt/yt/client/api/rowset.h>
+#include <yt/yt/client/api/skynet.h>
+
+#include <yt/yt/client/chaos_client/replication_card_serialization.h>
+
+#include <yt/yt/client/table_client/adapters.h>
+#include <yt/yt/client/table_client/blob_reader.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/table_consumer.h>
+#include <yt/yt/client/table_client/table_output.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+#include <yt/yt/client/table_client/versioned_writer.h>
+#include <yt/yt/client/table_client/wire_protocol.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+
+#include <yt/yt/client/formats/config.h>
+#include <yt/yt/client/formats/parser.h>
+
+#include <yt/yt/client/ypath/public.h>
+
+#include <yt/yt/core/concurrency/periodic_executor.h>
+#include <yt/yt/core/misc/finally.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT::NDriver {
+
+using namespace NApi;
+using namespace NChunkClient;
+using namespace NConcurrency;
+using namespace NFormats;
+using namespace NHiveClient;
+using namespace NQueryClient;
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NTransactionClient;
+using namespace NYTree;
+using namespace NYson;
+using namespace NTracing;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NLogging::TLogger WithCommandTag(
+ const NLogging::TLogger& logger,
+ const ICommandContextPtr& context)
+{
+ return logger.WithTag("Command: %v",
+ context->Request().CommandName);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadTableCommand::TReadTableCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("table_reader", TableReader)
+ .Default();
+ RegisterParameter("control_attributes", ControlAttributes)
+ .DefaultNew();
+ RegisterParameter("unordered", Unordered)
+ .Default(false);
+ RegisterParameter("start_row_index_only", StartRowIndexOnly)
+ .Default(false);
+ RegisterParameter("omit_inaccessible_columns", Options.OmitInaccessibleColumns)
+ .Default(false);
+}
+
+void TReadTableCommand::DoExecute(ICommandContextPtr context)
+{
+ YT_LOG_DEBUG("Executing \"read_table\" command (Path: %v, Unordered: %v, StartRowIndexOnly: %v, "
+ "OmitInaccessibleColumns: %v)",
+ Path,
+ Unordered,
+ StartRowIndexOnly,
+ Options.OmitInaccessibleColumns);
+ Options.Ping = true;
+ Options.EnableTableIndex = ControlAttributes->EnableTableIndex;
+ Options.EnableRowIndex = ControlAttributes->EnableRowIndex;
+ Options.EnableRangeIndex = ControlAttributes->EnableRangeIndex;
+ Options.EnableTabletIndex = ControlAttributes->EnableTabletIndex;
+ Options.Config = UpdateYsonStruct(
+ context->GetConfig()->TableReader,
+ TableReader);
+
+ if (StartRowIndexOnly) {
+ Options.Config->WindowSize = 1;
+ Options.Config->GroupSize = 1;
+ }
+
+ PutMethodInfoInTraceContext("read_table");
+
+ auto reader = WaitFor(context->GetClient()->CreateTableReader(
+ Path,
+ Options))
+ .ValueOrThrow();
+
+ ProduceResponseParameters(context, [&] (IYsonConsumer* consumer) {
+ BuildYsonMapFragmentFluently(consumer)
+ .Item("approximate_row_count").Value(reader->GetTotalRowCount())
+ .Item("omitted_inaccessible_columns").Value(reader->GetOmittedInaccessibleColumns())
+ .DoIf(reader->GetTotalRowCount() > 0, [&](auto fluent) {
+ fluent
+ .Item("start_row_index").Value(reader->GetStartRowIndex());
+ });
+ });
+
+ if (StartRowIndexOnly) {
+ return;
+ }
+
+ auto writer = CreateStaticTableWriterForFormat(
+ context->GetOutputFormat(),
+ reader->GetNameTable(),
+ {reader->GetTableSchema()},
+ context->Request().OutputStream,
+ false,
+ ControlAttributes,
+ 0);
+
+ auto finally = Finally([&] () {
+ auto dataStatistics = reader->GetDataStatistics();
+ YT_LOG_DEBUG("Command statistics (RowCount: %v, WrittenSize: %v, "
+ "ReadUncompressedDataSize: %v, ReadCompressedDataSize: %v, "
+ "OmittedInaccessibleColumns: %v)",
+ dataStatistics.row_count(),
+ writer->GetWrittenSize(),
+ dataStatistics.uncompressed_data_size(),
+ dataStatistics.compressed_data_size(),
+ reader->GetOmittedInaccessibleColumns());
+ });
+
+ TRowBatchReadOptions options{
+ .MaxRowsPerRead = context->GetConfig()->ReadBufferRowCount
+ };
+ PipeReaderToWriterByBatches(
+ reader,
+ writer,
+ options);
+}
+
+bool TReadTableCommand::HasResponseParameters() const
+{
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReadBlobTableCommand::TReadBlobTableCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("part_size", PartSize);
+ RegisterParameter("table_reader", TableReader)
+ .Default();
+ RegisterParameter("part_index_column_name", PartIndexColumnName)
+ .Default();
+ RegisterParameter("data_column_name", DataColumnName)
+ .Default();
+ RegisterParameter("start_part_index", StartPartIndex)
+ .Default(0);
+ RegisterParameter("offset", Offset)
+ .Default(0);
+}
+
+void TReadBlobTableCommand::DoExecute(ICommandContextPtr context)
+{
+ if (Offset < 0) {
+ THROW_ERROR_EXCEPTION("Offset must be nonnegative");
+ }
+
+ if (PartSize <= 0) {
+ THROW_ERROR_EXCEPTION("Part size must be positive");
+ }
+ Options.Ping = true;
+
+ auto config = UpdateYsonStruct(
+ context->GetConfig()->TableReader,
+ TableReader);
+
+ Options.Config = config;
+
+ auto reader = WaitFor(context->GetClient()->CreateTableReader(
+ Path,
+ Options))
+ .ValueOrThrow();
+
+ auto input = CreateBlobTableReader(
+ std::move(reader),
+ PartIndexColumnName,
+ DataColumnName,
+ StartPartIndex,
+ Offset,
+ PartSize);
+
+ auto output = context->Request().OutputStream;
+
+ // TODO(ignat): implement proper Pipe* function.
+ while (true) {
+ auto block = WaitFor(input->Read())
+ .ValueOrThrow();
+
+ if (!block)
+ break;
+
+ WaitFor(output->Write(block))
+ .ThrowOnError();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLocateSkynetShareCommand::TLocateSkynetShareCommand()
+{
+ RegisterParameter("path", Path);
+}
+
+void TLocateSkynetShareCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Config = context->GetConfig()->TableReader;
+
+ auto asyncSkynetPartsLocations = context->GetClient()->LocateSkynetShare(
+ Path,
+ Options);
+
+ auto skynetPartsLocations = WaitFor(asyncSkynetPartsLocations);
+
+ auto format = context->GetOutputFormat();
+ auto syncOutputStream = CreateBufferedSyncAdapter(context->Request().OutputStream);
+
+ auto consumer = CreateConsumerForFormat(
+ format,
+ EDataType::Structured,
+ syncOutputStream.get());
+
+ Serialize(*skynetPartsLocations.ValueOrThrow(), consumer.get());
+ consumer->Flush();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TWriteTableCommand::TWriteTableCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("table_writer", TableWriter)
+ .Default();
+ RegisterParameter("max_row_buffer_size", MaxRowBufferSize)
+ .Default(1_MB);
+}
+
+void TWriteTableCommand::DoExecute(ICommandContextPtr context)
+{
+ auto transaction = AttachTransaction(context, false);
+
+ auto config = UpdateYsonStruct(
+ context->GetConfig()->TableWriter,
+ TableWriter);
+
+ // XXX(babenko): temporary workaround; this is how it actually works but not how it is intended to be.
+ Options.PingAncestors = true;
+ Options.Config = config;
+
+ PutMethodInfoInTraceContext("write_table");
+
+ auto apiWriter = WaitFor(context->GetClient()->CreateTableWriter(
+ Path,
+ Options))
+ .ValueOrThrow();
+
+ auto schemalessWriter = CreateSchemalessFromApiWriterAdapter(std::move(apiWriter));
+
+ TWritingValueConsumer valueConsumer(
+ schemalessWriter,
+ ConvertTo<TTypeConversionConfigPtr>(context->GetInputFormat().Attributes()),
+ MaxRowBufferSize);
+
+ TTableOutput output(CreateParserForFormat(
+ context->GetInputFormat(),
+ &valueConsumer));
+
+ PipeInputToOutput(context->Request().InputStream, &output, MaxRowBufferSize);
+
+ WaitFor(valueConsumer.Flush())
+ .ThrowOnError();
+
+ WaitFor(schemalessWriter->Close())
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetTableColumnarStatisticsCommand::TGetTableColumnarStatisticsCommand()
+{
+ RegisterParameter("paths", Paths);
+ RegisterParameter("fetcher_mode", FetcherMode)
+ .Default(EColumnarStatisticsFetcherMode::FromNodes);
+ RegisterParameter("max_chunks_per_node_fetch", MaxChunksPerNodeFetch)
+ .Default();
+ RegisterParameter("enable_early_finish", EnableEarlyFinish)
+ .Default(true);
+}
+
+void TGetTableColumnarStatisticsCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.FetchChunkSpecConfig = context->GetConfig()->TableReader;
+ Options.FetcherConfig = context->GetConfig()->Fetcher;
+ Options.EnableEarlyFinish = EnableEarlyFinish;
+
+ if (MaxChunksPerNodeFetch) {
+ Options.FetcherConfig = CloneYsonStruct(Options.FetcherConfig);
+ Options.FetcherConfig->MaxChunksPerNodeFetch = *MaxChunksPerNodeFetch;
+ }
+
+ Options.FetcherMode = FetcherMode;
+
+ auto transaction = AttachTransaction(context, false);
+
+ std::vector<TFuture<NYson::TYsonString>> asyncSchemaYsons;
+ for (int index = 0; index < std::ssize(Paths); ++index) {
+ if (Paths[index].GetColumns()) {
+ continue;
+ }
+ TGetNodeOptions options;
+ static_cast<TTransactionalOptions&>(options) = Options;
+ static_cast<TTimeoutOptions&>(options) = Options;
+ asyncSchemaYsons.push_back(context->GetClient()->GetNode(Paths[index].GetPath() + "/@schema", options));
+ }
+
+ if (!asyncSchemaYsons.empty()) {
+ YT_LOG_DEBUG("Fetching schemas for tables without column selectors (TableCount: %v)", asyncSchemaYsons.size());
+ auto allSchemas = WaitFor(AllSucceeded(asyncSchemaYsons))
+ .ValueOrThrow();
+ for (int pathIndex = 0, missingColumnIndex = 0; pathIndex < std::ssize(Paths); ++pathIndex) {
+ if (!Paths[pathIndex].GetColumns()) {
+ auto columnNames = ConvertTo<TTableSchema>(allSchemas[missingColumnIndex]).GetColumnNames();
+ if (columnNames.empty()) {
+ THROW_ERROR_EXCEPTION("Table %Qv does not have schema and column selector is not specified", Paths[pathIndex]);
+ }
+ Paths[pathIndex].SetColumns(columnNames);
+ ++missingColumnIndex;
+ }
+ }
+ }
+
+ YT_LOG_DEBUG("Starting fetching columnar statistics");
+
+ auto allStatisticsOrError = WaitFor(context->GetClient()->GetColumnarStatistics(Paths, Options));
+
+ YT_LOG_DEBUG("Finished fetching columnar statistics");
+
+ auto allStatistics = allStatisticsOrError.ValueOrThrow();
+
+ YT_VERIFY(allStatistics.size() == Paths.size());
+ for (int index = 0; index < std::ssize(allStatistics); ++index) {
+ YT_VERIFY(std::ssize(*Paths[index].GetColumns()) == allStatistics[index].GetColumnCount());
+ }
+
+ ProduceOutput(context, [&] (IYsonConsumer* consumer) {
+ BuildYsonFluently(consumer)
+ .DoList([&] (TFluentList fluent) {
+ for (int index = 0; index < std::ssize(Paths); ++index) {
+ auto columns = *Paths[index].GetColumns();
+ const auto& statistics = allStatistics[index];
+ fluent
+ .Item()
+ .BeginMap()
+ .Item("column_data_weights").DoMap([&] (TFluentMap fluent) {
+ for (int index = 0; index < statistics.GetColumnCount(); ++index) {
+ fluent.Item(columns[index]).Value(statistics.ColumnDataWeights[index]);
+ }
+ })
+ .OptionalItem("timestamp_total_weight", statistics.TimestampTotalWeight)
+ .Item("legacy_chunks_data_weight").Value(statistics.LegacyChunkDataWeight)
+ .DoIf(statistics.HasValueStatistics(), [&] (TFluentMap fluent) {
+ fluent
+ .Item("column_min_values").DoMap([&] (TFluentMap fluent) {
+ for (int index = 0; index < statistics.GetColumnCount(); ++index) {
+ fluent.Item(columns[index]).Value(statistics.ColumnMinValues[index]);
+ }
+ })
+ .Item("column_max_values").DoMap([&] (TFluentMap fluent) {
+ for (int index = 0; index < statistics.GetColumnCount(); ++index) {
+ fluent.Item(columns[index]).Value(statistics.ColumnMaxValues[index]);
+ }
+ })
+ .Item("column_non_null_value_counts").DoMap([&] (TFluentMap fluent) {
+ for (int index = 0; index < statistics.GetColumnCount(); ++index) {
+ fluent.Item(columns[index]).Value(statistics.ColumnNonNullValueCounts[index]);
+ }
+ });
+ })
+ .OptionalItem("chunk_row_count", statistics.ChunkRowCount)
+ .OptionalItem("legacy_chunk_row_count", statistics.LegacyChunkRowCount)
+ .EndMap();
+ }
+ });
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TPartitionTablesCommand::TPartitionTablesCommand()
+{
+ RegisterParameter("paths", Paths);
+ RegisterParameter("partition_mode", PartitionMode)
+ .Default(ETablePartitionMode::Unordered);
+ RegisterParameter("data_weight_per_partition", DataWeightPerPartition);
+ RegisterParameter("max_partition_count", MaxPartitionCount)
+ .Default();
+ RegisterParameter("enable_key_guarantee", EnableKeyGuarantee)
+ .Default(false);
+ RegisterParameter("adjust_data_weight_per_partition", AdjustDataWeightPerPartition)
+ .Default(true);
+}
+
+void TPartitionTablesCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.FetchChunkSpecConfig = context->GetConfig()->TableReader;
+ Options.FetcherConfig = context->GetConfig()->Fetcher;
+ Options.ChunkSliceFetcherConfig = New<TChunkSliceFetcherConfig>();
+
+ Options.PartitionMode = PartitionMode;
+ Options.DataWeightPerPartition = DataWeightPerPartition;
+ Options.MaxPartitionCount = MaxPartitionCount;
+ Options.EnableKeyGuarantee = EnableKeyGuarantee;
+ Options.AdjustDataWeightPerPartition = AdjustDataWeightPerPartition;
+
+ auto partitions = WaitFor(context->GetClient()->PartitionTables(Paths, Options))
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(ConvertToYsonString(partitions));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMountTableCommand::TMountTableCommand()
+{
+ RegisterParameter("cell_id", Options.CellId)
+ .Optional();
+ RegisterParameter("freeze", Options.Freeze)
+ .Optional();
+ RegisterParameter("target_cell_ids", Options.TargetCellIds)
+ .Optional();
+}
+
+void TMountTableCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->MountTable(
+ Path.GetPath(),
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnmountTableCommand::TUnmountTableCommand()
+{
+ RegisterParameter("force", Options.Force)
+ .Optional();
+}
+
+void TUnmountTableCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->UnmountTable(
+ Path.GetPath(),
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TRemountTableCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->RemountTable(
+ Path.GetPath(),
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TFreezeTableCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->FreezeTable(
+ Path.GetPath(),
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TUnfreezeTableCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->UnfreezeTable(
+ Path.GetPath(),
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReshardTableCommand::TReshardTableCommand()
+{
+ RegisterParameter("pivot_keys", PivotKeys)
+ .Default();
+ RegisterParameter("tablet_count", TabletCount)
+ .Default()
+ .GreaterThan(0);
+ RegisterParameter("uniform", Options.Uniform)
+ .Default();
+ RegisterParameter("enable_slicing", Options.EnableSlicing)
+ .Default();
+ RegisterParameter("slicing_accuracy", Options.SlicingAccuracy)
+ .Default()
+ .GreaterThan(0);
+
+ RegisterPostprocessor([&] () {
+ if (PivotKeys && TabletCount) {
+ THROW_ERROR_EXCEPTION("Cannot specify both \"pivot_keys\" and \"tablet_count\"");
+ }
+ if (!PivotKeys && !TabletCount) {
+ THROW_ERROR_EXCEPTION("Must specify either \"pivot_keys\" or \"tablet_count\"");
+ }
+ if (Options.Uniform && PivotKeys) {
+ THROW_ERROR_EXCEPTION("\"uniform\" can be specified only with \"tablet_count\"");
+ }
+ if (Options.EnableSlicing && PivotKeys) {
+ THROW_ERROR_EXCEPTION("\"enable_slicing\" can be specified only with \"tablet_count\"");
+ }
+ if (Options.EnableSlicing && Options.Uniform) {
+ THROW_ERROR_EXCEPTION("Cannot specify both \"enable_slicing\" and \"uniform\"");
+ }
+ if (Options.SlicingAccuracy && !Options.EnableSlicing) {
+ THROW_ERROR_EXCEPTION("\"slicing_accuracy\" can be specified only with \"enable_slicing\"");
+ }
+ });
+}
+
+void TReshardTableCommand::DoExecute(ICommandContextPtr context)
+{
+ TFuture<void> asyncResult;
+ if (PivotKeys) {
+ asyncResult = context->GetClient()->ReshardTable(
+ Path.GetPath(),
+ *PivotKeys,
+ Options);
+ } else {
+ asyncResult = context->GetClient()->ReshardTable(
+ Path.GetPath(),
+ *TabletCount,
+ Options);
+ }
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReshardTableAutomaticCommand::TReshardTableAutomaticCommand()
+{
+ RegisterParameter("keep_actions", Options.KeepActions)
+ .Default(false);
+}
+
+void TReshardTableAutomaticCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->ReshardTableAutomatic(
+ Path.GetPath(),
+ Options);
+ auto tabletActions = WaitFor(asyncResult)
+ .ValueOrThrow();
+ context->ProduceOutputValue(BuildYsonStringFluently().List(tabletActions));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAlterTableCommand::TAlterTableCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("schema", Options.Schema)
+ .Optional();
+ RegisterParameter("schema_id", Options.SchemaId)
+ .Optional();
+ RegisterParameter("dynamic", Options.Dynamic)
+ .Optional();
+ RegisterParameter("upstream_replica_id", Options.UpstreamReplicaId)
+ .Optional();
+ RegisterParameter("schema_modification", Options.SchemaModification)
+ .Optional();
+ RegisterParameter("replication_progress", Options.ReplicationProgress)
+ .Optional();
+}
+
+void TAlterTableCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->AlterTable(
+ Path.GetPath(),
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSelectRowsCommand::TSelectRowsCommand()
+{
+ RegisterParameter("query", Query);
+ RegisterParameter("input_row_limit", Options.InputRowLimit)
+ .Optional();
+ RegisterParameter("output_row_limit", Options.OutputRowLimit)
+ .Optional();
+ RegisterParameter("allow_full_scan", Options.AllowFullScan)
+ .Optional();
+ RegisterParameter("allow_join_without_index", Options.AllowJoinWithoutIndex)
+ .Optional();
+ RegisterParameter("execution_pool", Options.ExecutionPool)
+ .Optional();
+ RegisterParameter("fail_on_incomplete_result", Options.FailOnIncompleteResult)
+ .Optional();
+ RegisterParameter("enable_code_cache", Options.EnableCodeCache)
+ .Optional();
+ RegisterParameter("workload_descriptor", Options.WorkloadDescriptor)
+ .Optional();
+ RegisterParameter("enable_statistics", EnableStatistics)
+ .Optional();
+ RegisterParameter("replica_consistency", Options.ReplicaConsistency)
+ .Optional();
+ RegisterParameter("placeholder_values", PlaceholderValues)
+ .Optional();
+}
+
+bool TSelectRowsCommand::HasResponseParameters() const
+{
+ return true;
+}
+
+void TSelectRowsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto clientBase = GetClientBase(context);
+
+ if (PlaceholderValues) {
+ Options.PlaceholderValues = ConvertToYsonString(PlaceholderValues);
+ }
+
+ auto result = WaitFor(clientBase->SelectRows(Query, Options))
+ .ValueOrThrow();
+
+ const auto& rowset = result.Rowset;
+ const auto& statistics = result.Statistics;
+
+ YT_LOG_INFO("Query result statistics (%v)", statistics);
+
+ if (EnableStatistics) {
+ ProduceResponseParameters(context, [&] (NYson::IYsonConsumer* consumer) {
+ Serialize(statistics, consumer);
+ });
+ }
+
+ auto format = context->GetOutputFormat();
+ auto output = context->Request().OutputStream;
+ auto writer = CreateSchemafulWriterForFormat(format, rowset->GetSchema(), output);
+
+ writer->Write(rowset->GetRows());
+
+ WaitFor(writer->Close())
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TExplainQueryCommand::TExplainQueryCommand()
+{
+ RegisterParameter("query", Query);
+ RegisterParameter("verbose_output", Options.VerboseOutput)
+ .Optional();
+}
+
+void TExplainQueryCommand::DoExecute(ICommandContextPtr context)
+{
+ auto clientBase = GetClientBase(context);
+ auto result = WaitFor(clientBase->ExplainQuery(Query, Options))
+ .ValueOrThrow();
+ context->ProduceOutputValue(result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static std::vector<TUnversionedRow> ParseRows(
+ ICommandContextPtr context,
+ TBuildingValueConsumer* valueConsumer)
+{
+ TTableOutput output(CreateParserForFormat(
+ context->GetInputFormat(),
+ valueConsumer));
+
+ PipeInputToOutput(context->Request().InputStream, &output, 64_KB);
+ return valueConsumer->GetRows();
+}
+
+TInsertRowsCommand::TInsertRowsCommand()
+{
+ RegisterParameter("require_sync_replica", Options.RequireSyncReplica)
+ .Optional();
+ RegisterParameter("sequence_number", Options.SequenceNumber)
+ .Optional();
+ RegisterParameter("table_writer", TableWriter)
+ .Default();
+ RegisterParameter("path", Path);
+ RegisterParameter("update", Update)
+ .Default(false);
+ RegisterParameter("aggregate", Aggregate)
+ .Default(false);
+ RegisterParameter("allow_missing_key_columns", Options.AllowMissingKeyColumns)
+ .Default(false);
+}
+
+void TInsertRowsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto config = UpdateYsonStruct(
+ context->GetConfig()->TableWriter,
+ TableWriter);
+
+ auto tableMountCache = context->GetClient()->GetTableMountCache();
+ auto tableInfo = WaitFor(tableMountCache->GetTableInfo(Path.GetPath()))
+ .ValueOrThrow();
+
+ tableInfo->ValidateDynamic();
+
+ if (!tableInfo->IsSorted() && Update) {
+ THROW_ERROR_EXCEPTION("Cannot use \"update\" mode for ordered tables");
+ }
+
+ struct TInsertRowsBufferTag
+ { };
+
+ auto insertRowsFormatConfig = ConvertTo<TInsertRowsFormatConfigPtr>(context->GetInputFormat().Attributes());
+ auto typeConversionConfig = ConvertTo<TTypeConversionConfigPtr>(context->GetInputFormat().Attributes());
+ // Parse input data.
+ TBuildingValueConsumer valueConsumer(
+ tableInfo->Schemas[ETableSchemaKind::Write],
+ WithCommandTag(Logger, context),
+ insertRowsFormatConfig->EnableNullToYsonEntityConversion,
+ typeConversionConfig);
+ valueConsumer.SetAggregate(Aggregate);
+ valueConsumer.SetTreatMissingAsNull(!Update);
+ valueConsumer.SetAllowMissingKeyColumns(Options.AllowMissingKeyColumns);
+
+ auto rows = ParseRows(context, &valueConsumer);
+ auto rowBuffer = New<TRowBuffer>(TInsertRowsBufferTag());
+ auto capturedRows = rowBuffer->CaptureRows(rows);
+ auto rowRange = MakeSharedRange(
+ std::vector<TUnversionedRow>(capturedRows.begin(), capturedRows.end()),
+ std::move(rowBuffer));
+
+ // Run writes.
+ auto transaction = GetTransaction(context);
+
+ transaction->WriteRows(
+ Path.GetPath(),
+ valueConsumer.GetNameTable(),
+ std::move(rowRange),
+ Options);
+
+ if (ShouldCommitTransaction()) {
+ WaitFor(transaction->Commit())
+ .ThrowOnError();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLookupRowsCommand::TLookupRowsCommand()
+{
+ RegisterParameter("table_writer", TableWriter)
+ .Default();
+ RegisterParameter("path", Path);
+ RegisterParameter("column_names", ColumnNames)
+ .Default();
+ RegisterParameter("versioned", Versioned)
+ .Default(false);
+ RegisterParameter("retention_config", RetentionConfig)
+ .Optional();
+ RegisterParameter("keep_missing_rows", Options.KeepMissingRows)
+ .Optional();
+ RegisterParameter("enable_partial_result", Options.EnablePartialResult)
+ .Optional();
+ RegisterParameter("use_lookup_cache", Options.UseLookupCache)
+ .Optional();
+ RegisterParameter("cached_sync_replicas_timeout", Options.CachedSyncReplicasTimeout)
+ .Optional();
+ RegisterParameter("replica_consistency", Options.ReplicaConsistency)
+ .Optional();
+}
+
+void TLookupRowsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto tableMountCache = context->GetClient()->GetTableMountCache();
+ auto asyncTableInfo = tableMountCache->GetTableInfo(Path.GetPath());
+ auto tableInfo = WaitFor(asyncTableInfo)
+ .ValueOrThrow();
+
+ tableInfo->ValidateDynamic();
+
+ auto config = UpdateYsonStruct(
+ context->GetConfig()->TableWriter,
+ TableWriter);
+
+ if (Path.GetColumns()) {
+ THROW_ERROR_EXCEPTION("Columns cannot be specified with table path, use \"column_names\" instead")
+ << TErrorAttribute("rich_ypath", Path);
+ }
+ if (Path.HasNontrivialRanges()) {
+ THROW_ERROR_EXCEPTION("Ranges cannot be specified")
+ << TErrorAttribute("rich_ypath", Path);
+ }
+
+ struct TLookupRowsBufferTag
+ { };
+
+ // Parse input data.
+ TBuildingValueConsumer valueConsumer(
+ tableInfo->Schemas[ETableSchemaKind::Lookup],
+ WithCommandTag(Logger, context),
+ /*convertNullToEntity*/ false,
+ ConvertTo<TTypeConversionConfigPtr>(context->GetInputFormat().Attributes()));
+ auto keys = ParseRows(context, &valueConsumer);
+ auto rowBuffer = New<TRowBuffer>(TLookupRowsBufferTag());
+ auto capturedKeys = rowBuffer->CaptureRows(keys);
+ auto mutableKeyRange = MakeSharedRange(std::move(capturedKeys), std::move(rowBuffer));
+ auto keyRange = TSharedRange<TUnversionedRow>(
+ static_cast<const TUnversionedRow*>(mutableKeyRange.Begin()),
+ static_cast<const TUnversionedRow*>(mutableKeyRange.End()),
+ mutableKeyRange.GetHolder());
+ auto nameTable = valueConsumer.GetNameTable();
+
+ if (ColumnNames) {
+ TColumnFilter::TIndexes columnFilterIndexes;
+ for (const auto& name : *ColumnNames) {
+ auto optionalIndex = nameTable->FindId(name);
+ if (!optionalIndex) {
+ if (!tableInfo->Schemas[ETableSchemaKind::Primary]->FindColumn(name)) {
+ THROW_ERROR_EXCEPTION("No such column %Qv",
+ name);
+ }
+ optionalIndex = nameTable->GetIdOrRegisterName(name);
+ }
+ columnFilterIndexes.push_back(*optionalIndex);
+ }
+ Options.ColumnFilter = TColumnFilter(std::move(columnFilterIndexes));
+ }
+
+ // Run lookup.
+ auto format = context->GetOutputFormat();
+ auto output = context->Request().OutputStream;
+
+ auto clientBase = GetClientBase(context);
+
+ if (Versioned) {
+ TVersionedLookupRowsOptions versionedOptions;
+ versionedOptions.ColumnFilter = Options.ColumnFilter;
+ versionedOptions.KeepMissingRows = Options.KeepMissingRows;
+ versionedOptions.EnablePartialResult = Options.EnablePartialResult;
+ versionedOptions.UseLookupCache = Options.UseLookupCache;
+ versionedOptions.Timestamp = Options.Timestamp;
+ versionedOptions.CachedSyncReplicasTimeout = Options.CachedSyncReplicasTimeout;
+ versionedOptions.RetentionConfig = RetentionConfig;
+ versionedOptions.ReplicaConsistency = Options.ReplicaConsistency;
+ auto asyncRowset = clientBase->VersionedLookupRows(Path.GetPath(), std::move(nameTable), std::move(keyRange), versionedOptions);
+ auto rowset = WaitFor(asyncRowset)
+ .ValueOrThrow();
+ auto writer = CreateVersionedWriterForFormat(format, rowset->GetSchema(), output);
+ writer->Write(rowset->GetRows());
+ WaitFor(writer->Close())
+ .ThrowOnError();
+ } else {
+ auto asyncRowset = clientBase->LookupRows(Path.GetPath(), std::move(nameTable), std::move(keyRange), Options);
+ auto rowset = WaitFor(asyncRowset)
+ .ValueOrThrow();
+
+ auto writer = CreateSchemafulWriterForFormat(format, rowset->GetSchema(), output);
+ writer->Write(rowset->GetRows());
+ WaitFor(writer->Close())
+ .ThrowOnError();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TPullRowsCommand::TPullRowsCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("upstream_replica_id", Options.UpstreamReplicaId);
+ RegisterParameter("replication_progress", Options.ReplicationProgress);
+ RegisterParameter("upper_timestamp", Options.UpperTimestamp)
+ .Optional();
+ RegisterParameter("order_rows_by_timestamp", Options.OrderRowsByTimestamp)
+ .Default(false);
+}
+
+void TPullRowsCommand::DoExecute(ICommandContextPtr context)
+{
+ if (Path.HasNontrivialRanges()) {
+ THROW_ERROR_EXCEPTION("Ranges cannot be specified")
+ << TErrorAttribute("rich_ypath", Path);
+ }
+
+ auto format = context->GetOutputFormat();
+ auto output = context->Request().OutputStream;
+
+ auto client = context->GetClient();
+ auto pullRowsFuture = client->PullRows(Path.GetPath(), Options);
+ auto pullResult = WaitFor(pullRowsFuture)
+ .ValueOrThrow();
+
+ ProduceResponseParameters(context, [&] (IYsonConsumer* consumer) {
+ BuildYsonMapFragmentFluently(consumer)
+ .Item("replication_progress").Value(pullResult.ReplicationProgress)
+ .Item("end_replication_row_indexes")
+ .DoMapFor(pullResult.EndReplicationRowIndexes, [] (auto fluent, const auto& pair) {
+ fluent
+ .Item(ToString(pair.first)).Value(pair.second);
+ });
+ });
+
+ if (pullResult.Versioned) {
+ auto writer = CreateVersionedWriterForFormat(format, pullResult.Rowset->GetSchema(), output);
+ writer->Write(ReinterpretCastRange<TVersionedRow>(pullResult.Rowset->GetRows()));
+ WaitFor(writer->Close())
+ .ThrowOnError();
+ } else {
+ auto writer = CreateSchemafulWriterForFormat(format, pullResult.Rowset->GetSchema(), output);
+ writer->Write(ReinterpretCastRange<TUnversionedRow>(pullResult.Rowset->GetRows()));
+ WaitFor(writer->Close())
+ .ThrowOnError();
+ }
+}
+
+bool TPullRowsCommand::HasResponseParameters() const
+{
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetInSyncReplicasCommand::TGetInSyncReplicasCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("timestamp", Options.Timestamp);
+ RegisterParameter("all_keys", AllKeys)
+ .Default(false);
+ RegisterParameter("cached_sync_replicas_timeout", Options.CachedSyncReplicasTimeout)
+ .Optional();
+}
+
+void TGetInSyncReplicasCommand::DoExecute(ICommandContextPtr context)
+{
+ auto tableMountCache = context->GetClient()->GetTableMountCache();
+ auto asyncTableInfo = tableMountCache->GetTableInfo(Path.GetPath());
+ auto tableInfo = WaitFor(asyncTableInfo)
+ .ValueOrThrow();
+
+ tableInfo->ValidateDynamic();
+ if (!tableInfo->IsChaosReplica() && !tableInfo->IsChaosReplicated()) {
+ tableInfo->ValidateReplicated();
+ }
+
+ TFuture<std::vector<NTabletClient::TTableReplicaId>> asyncReplicas;
+ if (AllKeys) {
+ asyncReplicas = context->GetClient()->GetInSyncReplicas(
+ Path.GetPath(),
+ Options);
+ } else {
+ struct TInSyncBufferTag
+ { };
+
+ // Parse input data.
+ TBuildingValueConsumer valueConsumer(
+ tableInfo->Schemas[ETableSchemaKind::Lookup],
+ WithCommandTag(Logger, context),
+ /*convertNullToEntity*/ false,
+ ConvertTo<TTypeConversionConfigPtr>(context->GetInputFormat().Attributes()));
+ auto keys = ParseRows(context, &valueConsumer);
+ auto rowBuffer = New<TRowBuffer>(TInSyncBufferTag());
+ auto capturedKeys = rowBuffer->CaptureRows(keys);
+ auto mutableKeyRange = MakeSharedRange(std::move(capturedKeys), std::move(rowBuffer));
+ auto keyRange = TSharedRange<TUnversionedRow>(
+ static_cast<const TUnversionedRow*>(mutableKeyRange.Begin()),
+ static_cast<const TUnversionedRow*>(mutableKeyRange.End()),
+ mutableKeyRange.GetHolder());
+ auto nameTable = valueConsumer.GetNameTable();
+ asyncReplicas = context->GetClient()->GetInSyncReplicas(
+ Path.GetPath(),
+ std::move(nameTable),
+ std::move(keyRange),
+ Options);
+ }
+
+ auto replicas = WaitFor(asyncReplicas)
+ .ValueOrThrow();
+
+ context->ProduceOutputValue(BuildYsonStringFluently()
+ .List(replicas));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDeleteRowsCommand::TDeleteRowsCommand()
+{
+ RegisterParameter("sequence_number", Options.SequenceNumber)
+ .Optional();
+ RegisterParameter("require_sync_replica", Options.RequireSyncReplica)
+ .Optional();
+ RegisterParameter("table_writer", TableWriter)
+ .Default();
+ RegisterParameter("path", Path);
+}
+
+void TDeleteRowsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto config = UpdateYsonStruct(
+ context->GetConfig()->TableWriter,
+ TableWriter);
+
+ auto tableMountCache = context->GetClient()->GetTableMountCache();
+ auto asyncTableInfo = tableMountCache->GetTableInfo(Path.GetPath());
+ auto tableInfo = WaitFor(asyncTableInfo)
+ .ValueOrThrow();
+
+ tableInfo->ValidateDynamic();
+ tableInfo->ValidateSorted();
+
+ struct TDeleteRowsBufferTag
+ { };
+
+ // Parse input data.
+ TBuildingValueConsumer valueConsumer(
+ tableInfo->Schemas[ETableSchemaKind::Delete],
+ WithCommandTag(Logger, context),
+ /*convertNullToEntity*/ false,
+ ConvertTo<TTypeConversionConfigPtr>(context->GetInputFormat().Attributes()));
+ auto keys = ParseRows(context, &valueConsumer);
+ auto rowBuffer = New<TRowBuffer>(TDeleteRowsBufferTag());
+ auto capturedKeys = rowBuffer->CaptureRows(keys);
+ auto keyRange = MakeSharedRange(
+ std::vector<TLegacyKey>(capturedKeys.begin(), capturedKeys.end()),
+ std::move(rowBuffer));
+
+ // Run deletes.
+ auto transaction = GetTransaction(context);
+
+ transaction->DeleteRows(
+ Path.GetPath(),
+ valueConsumer.GetNameTable(),
+ std::move(keyRange),
+ Options);
+
+ if (ShouldCommitTransaction()) {
+ WaitFor(transaction->Commit())
+ .ThrowOnError();
+ }
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLockRowsCommand::TLockRowsCommand()
+{
+ RegisterParameter("table_writer", TableWriter)
+ .Default();
+ RegisterParameter("path", Path);
+ RegisterParameter("locks", Locks);
+ RegisterParameter("lock_type", LockType)
+ .Default(NTableClient::ELockType::SharedStrong);
+}
+
+void TLockRowsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto config = UpdateYsonStruct(
+ context->GetConfig()->TableWriter,
+ TableWriter);
+
+ auto tableMountCache = context->GetClient()->GetTableMountCache();
+ auto asyncTableInfo = tableMountCache->GetTableInfo(Path.GetPath());
+ auto tableInfo = WaitFor(asyncTableInfo)
+ .ValueOrThrow();
+
+ tableInfo->ValidateDynamic();
+
+ auto transactionPool = context->GetDriver()->GetStickyTransactionPool();
+ auto transaction = transactionPool->GetTransactionAndRenewLeaseOrThrow(Options.TransactionId);
+
+ struct TLockRowsBufferTag
+ { };
+
+ // Parse input data.
+ TBuildingValueConsumer valueConsumer(
+ tableInfo->Schemas[ETableSchemaKind::Write],
+ WithCommandTag(Logger, context),
+ /*convertNullToEntity*/ false,
+ ConvertTo<TTypeConversionConfigPtr>(context->GetInputFormat().Attributes()));
+ auto keys = ParseRows(context, &valueConsumer);
+ auto rowBuffer = New<TRowBuffer>(TLockRowsBufferTag());
+ auto capturedKeys = rowBuffer->CaptureRows(keys);
+ auto keyRange = MakeSharedRange(
+ std::vector<TLegacyKey>(capturedKeys.begin(), capturedKeys.end()),
+ std::move(rowBuffer));
+
+ // Run locks.
+ transaction->LockRows(
+ Path.GetPath(),
+ valueConsumer.GetNameTable(),
+ std::move(keyRange),
+ Locks,
+ LockType);
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTrimRowsCommand::TTrimRowsCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("tablet_index", TabletIndex);
+ RegisterParameter("trimmed_row_count", TrimmedRowCount);
+}
+
+void TTrimRowsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->TrimTable(
+ Path.GetPath(),
+ TabletIndex,
+ TrimmedRowCount,
+ Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEnableTableReplicaCommand::TEnableTableReplicaCommand()
+{
+ RegisterParameter("replica_id", ReplicaId);
+ Options.Enabled = true;
+}
+
+void TEnableTableReplicaCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->AlterTableReplica(ReplicaId, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDisableTableReplicaCommand::TDisableTableReplicaCommand()
+{
+ RegisterParameter("replica_id", ReplicaId);
+ Options.Enabled = false;
+}
+
+void TDisableTableReplicaCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->AlterTableReplica(ReplicaId, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAlterTableReplicaCommand::TAlterTableReplicaCommand()
+{
+ RegisterParameter("replica_id", ReplicaId);
+ RegisterParameter("enabled", Options.Enabled)
+ .Optional();
+ RegisterParameter("mode", Options.Mode)
+ .Optional();
+ RegisterParameter("preserve_timestamps", Options.PreserveTimestamps)
+ .Optional();
+ RegisterParameter("atomicity", Options.Atomicity)
+ .Optional();
+ RegisterParameter("enable_replicated_table_tracker", Options.EnableReplicatedTableTracker)
+ .Optional();
+}
+
+void TAlterTableReplicaCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncResult = client->AlterTableReplica(ReplicaId, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetTablePivotKeysCommand::TGetTablePivotKeysCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("represent_key_as_list", Options.RepresentKeyAsList)
+ .Default(false);
+}
+
+void TGetTablePivotKeysCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->GetTablePivotKeys(Path, Options);
+ auto result = WaitFor(asyncResult)
+ .ValueOrThrow();
+ context->ProduceOutputValue(result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCreateTableBackupCommand::TCreateTableBackupCommand()
+{
+ RegisterParameter("manifest", Manifest);
+ RegisterParameter("checkpoint_timestamp_delay", Options.CheckpointTimestampDelay)
+ .Default(TDuration::Seconds(5));
+ RegisterParameter("checkpoint_check_period", Options.CheckpointCheckPeriod)
+ .Default(TDuration::Seconds(1));
+ RegisterParameter("checkpoint_check_timeout", Options.CheckpointCheckTimeout)
+ .Default(TDuration::Seconds(10));
+ RegisterParameter("force", Options.Force)
+ .Default(false);
+}
+
+void TCreateTableBackupCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->CreateTableBackup(Manifest, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRestoreTableBackupCommand::TRestoreTableBackupCommand()
+{
+ RegisterParameter("manifest", Manifest);
+ RegisterParameter("force", Options.Force)
+ .Default(false);
+ RegisterParameter("mount", Options.Mount)
+ .Default(false);
+ RegisterParameter("enable_replicas", Options.EnableReplicas)
+ .Default(false);
+}
+
+void TRestoreTableBackupCommand::DoExecute(ICommandContextPtr context)
+{
+ auto asyncResult = context->GetClient()->RestoreTableBackup(Manifest, Options);
+ WaitFor(asyncResult)
+ .ThrowOnError();
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetTabletInfosCommand::TGetTabletInfosCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("tablet_indexes", TabletIndexes);
+ RegisterParameter("request_errors", Options.RequestErrors)
+ .Default(false);
+}
+
+void TGetTabletInfosCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncTablets = client->GetTabletInfos(Path, TabletIndexes, Options);
+ auto tablets = WaitFor(asyncTablets)
+ .ValueOrThrow();
+
+ auto addErrors = [] (const std::vector<TError>& errors, TFluentMap fluent) {
+ fluent
+ .Item("tablet_errors").DoListFor(errors, [] (TFluentList fluent, const auto& error) {
+ fluent
+ .Item().Value(error);
+ });
+ };
+
+ ProduceOutput(context, [&] (IYsonConsumer* consumer) {
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("tablets").DoListFor(tablets, [&] (auto fluent, const auto& tablet) {
+ fluent
+ .Item().BeginMap()
+ .Item("total_row_count").Value(tablet.TotalRowCount)
+ .Item("trimmed_row_count").Value(tablet.TrimmedRowCount)
+ .Item("delayed_lockless_row_count").Value(tablet.DelayedLocklessRowCount)
+ .Item("barrier_timestamp").Value(tablet.BarrierTimestamp)
+ .Item("last_write_timestamp").Value(tablet.LastWriteTimestamp)
+ .DoIf(Options.RequestErrors, BIND(addErrors, tablet.TabletErrors))
+ .DoIf(tablet.TableReplicaInfos.has_value(), [&] (TFluentMap fluent) {
+ fluent
+ .Item("replica_infos").DoListFor(
+ *tablet.TableReplicaInfos,
+ [&] (TFluentList fluent, const auto& replicaInfo) {
+ fluent
+ .Item()
+ .BeginMap()
+ .Item("replica_id").Value(replicaInfo.ReplicaId)
+ .Item("last_replication_timestamp").Value(replicaInfo.LastReplicationTimestamp)
+ .Item("mode").Value(replicaInfo.Mode)
+ .Item("current_replication_row_index").Value(replicaInfo.CurrentReplicationRowIndex)
+ .Item("committed_replication_row_index").Value(replicaInfo.CommittedReplicationRowIndex)
+ .DoIf(Options.RequestErrors, [&] (TFluentMap fluent) {
+ fluent
+ .Item("replication_error").Value(replicaInfo.ReplicationError);
+ })
+ .EndMap();
+ });
+ })
+ .EndMap();
+ })
+ .EndMap();
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGetTabletErrorsCommand::TGetTabletErrorsCommand()
+{
+ RegisterParameter("path", Path);
+ RegisterParameter("limit", Options.Limit)
+ .Default()
+ .GreaterThan(0);
+}
+
+void TGetTabletErrorsCommand::DoExecute(ICommandContextPtr context)
+{
+ auto client = context->GetClient();
+ auto asyncErrors = client->GetTabletErrors(Path, Options);
+ auto errors = WaitFor(asyncErrors)
+ .ValueOrThrow();
+
+ ProduceOutput(context, [&] (IYsonConsumer* consumer) {
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("tablet_errors").DoMapFor(errors.TabletErrors, [] (auto fluent, const auto& pair) {
+ fluent
+ .Item(ToString(pair.first)).DoListFor(pair.second, [] (auto fluent, const auto& error) {
+ fluent.Item().Value(error);
+ });
+ })
+ .Item("replication_errors").DoMapFor(errors.ReplicationErrors, [] (auto fluent, const auto& pair) {
+ fluent
+ .Item(ToString(pair.first)).DoListFor(pair.second, [] (auto fluent, const auto& error) {
+ fluent.Item().Value(error);
+ });
+ })
+ .DoIf(errors.Incomplete, [&] (TFluentMap fluent) {
+ fluent
+ .Item("incomplete").Value(errors.Incomplete);
+ })
+ .EndMap();
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/table_commands.h b/yt/yt/client/driver/table_commands.h
new file mode 100644
index 0000000000..2c5d0304d4
--- /dev/null
+++ b/yt/yt/client/driver/table_commands.h
@@ -0,0 +1,518 @@
+#pragma once
+
+#include "command.h"
+
+#include <yt/yt/client/formats/format.h>
+
+#include <yt/yt/client/table_client/config.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadTableCommand
+ : public TTypedCommand<NApi::TTableReaderOptions>
+{
+public:
+ TReadTableCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NYTree::INodePtr TableReader;
+ NFormats::TControlAttributesConfigPtr ControlAttributes;
+ bool Unordered;
+ bool StartRowIndexOnly;
+
+ void DoExecute(ICommandContextPtr context) override;
+ bool HasResponseParameters() const override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReadBlobTableCommand
+ : public TTypedCommand<NApi::TTableReaderOptions>
+{
+public:
+ TReadBlobTableCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NYTree::INodePtr TableReader;
+
+ std::optional<TString> PartIndexColumnName;
+ std::optional<TString> DataColumnName;
+
+ i64 StartPartIndex;
+ i64 Offset;
+ i64 PartSize;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLocateSkynetShareCommand
+ : public TTypedCommand<NApi::TLocateSkynetShareOptions>
+{
+public:
+ TLocateSkynetShareCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWriteTableCommand
+ : public TTypedCommand<NApi::TTableWriterOptions>
+{
+public:
+ TWriteTableCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ NYTree::INodePtr TableWriter;
+ i64 MaxRowBufferSize;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetTableColumnarStatisticsCommand
+ : public TTypedCommand<NApi::TGetColumnarStatisticsOptions>
+{
+public:
+ TGetTableColumnarStatisticsCommand();
+
+private:
+ std::vector<NYPath::TRichYPath> Paths;
+ NTableClient::EColumnarStatisticsFetcherMode FetcherMode;
+ std::optional<int> MaxChunksPerNodeFetch;
+ bool EnableEarlyFinish;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPartitionTablesCommand
+ : public TTypedCommand<NApi::TPartitionTablesOptions>
+{
+public:
+ TPartitionTablesCommand();
+
+private:
+ std::vector<NYPath::TRichYPath> Paths;
+ NTableClient::ETablePartitionMode PartitionMode;
+ i64 DataWeightPerPartition;
+ std::optional<int> MaxPartitionCount;
+ bool EnableKeyGuarantee;
+
+ //! Treat the #DataWeightPerPartition as a hint and not as a maximum limit.
+ //! Consider the situation when the #MaxPartitionCount is given
+ //! and the total data weight exceeds #MaxPartitionCount * #DataWeightPerPartition.
+ //! If #AdjustDataWeightPerPartition is |true|
+ //! the #partition_tables command will yield partitions exceeding the #DataWeightPerPartition.
+ //! If #AdjustDataWeightPerPartition is |false|
+ //! the #partition_tables command will throw an exception.
+ bool AdjustDataWeightPerPartition;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOptions>
+class TTabletCommandBase
+ : public TTypedCommand<TOptions>
+{
+protected:
+ NYPath::TRichYPath Path;
+
+ TTabletCommandBase()
+ {
+ this->RegisterParameter("path", Path);
+ this->RegisterParameter("first_tablet_index", this->Options.FirstTabletIndex)
+ .Default();
+ this->RegisterParameter("last_tablet_index", this->Options.LastTabletIndex)
+ .Default();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMountTableCommand
+ : public TTabletCommandBase<NApi::TMountTableOptions>
+{
+public:
+ TMountTableCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnmountTableCommand
+ : public TTabletCommandBase<NApi::TUnmountTableOptions>
+{
+public:
+ TUnmountTableCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemountTableCommand
+ : public TTabletCommandBase<NApi::TRemountTableOptions>
+{
+public:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFreezeTableCommand
+ : public TTabletCommandBase<NApi::TFreezeTableOptions>
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnfreezeTableCommand
+ : public TTabletCommandBase<NApi::TUnfreezeTableOptions>
+{
+public:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReshardTableCommand
+ : public TTabletCommandBase<NApi::TReshardTableOptions>
+{
+public:
+ TReshardTableCommand();
+
+private:
+ std::optional<std::vector<NTableClient::TLegacyOwningKey>> PivotKeys;
+ std::optional<int> TabletCount;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReshardTableAutomaticCommand
+ : public TTabletCommandBase<NApi::TReshardTableAutomaticOptions>
+{
+public:
+ TReshardTableAutomaticCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAlterTableCommand
+ : public TTypedCommand<NApi::TAlterTableOptions>
+{
+public:
+ TAlterTableCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSelectRowsOptions
+ : public NApi::TSelectRowsOptions
+ , public TTabletTransactionOptions
+{ };
+
+class TSelectRowsCommand
+ : public TTypedCommand<TSelectRowsOptions>
+{
+public:
+ TSelectRowsCommand();
+
+private:
+ TString Query;
+ NYTree::IMapNodePtr PlaceholderValues;
+ bool EnableStatistics = false;
+
+ void DoExecute(ICommandContextPtr context) override;
+ bool HasResponseParameters() const override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TExplainQueryOptions
+ : public NApi::TExplainQueryOptions
+ , public TTabletTransactionOptions
+{ };
+
+class TExplainQueryCommand
+ : public TTypedCommand<TExplainQueryOptions>
+{
+public:
+ TExplainQueryCommand();
+
+private:
+ TString Query;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TInsertRowsCommand
+ : public TTypedCommand<TInsertRowsOptions>
+{
+public:
+ TInsertRowsCommand();
+
+private:
+ NYTree::INodePtr TableWriter;
+ NYPath::TRichYPath Path;
+ bool Update;
+ bool Aggregate;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TLookupRowsOptions
+ : public NApi::TLookupRowsOptions
+ , public TTabletTransactionOptions
+{ };
+
+class TLookupRowsCommand
+ : public TTypedCommand<TLookupRowsOptions>
+{
+public:
+ TLookupRowsCommand();
+
+private:
+ NYTree::INodePtr TableWriter;
+ NYPath::TRichYPath Path;
+ std::optional<std::vector<TString>> ColumnNames;
+ bool Versioned;
+ NTableClient::TRetentionConfigPtr RetentionConfig;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPullRowsOptions
+ : public NApi::TPullRowsOptions
+{ };
+
+class TPullRowsCommand
+ : public TTypedCommand<TPullRowsOptions>
+{
+public:
+ TPullRowsCommand();
+
+private:
+ NYPath::TRichYPath Path;
+
+ virtual void DoExecute(ICommandContextPtr context) override;
+ bool HasResponseParameters() const override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetInSyncReplicasCommand
+ : public TTypedCommand<NApi::TGetInSyncReplicasOptions>
+{
+public:
+ TGetInSyncReplicasCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ bool AllKeys;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDeleteRowsCommand
+ : public TTypedCommand<TDeleteRowsOptions>
+{
+public:
+ TDeleteRowsCommand();
+
+private:
+ NYTree::INodePtr TableWriter;
+ NYPath::TRichYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLockRowsCommand
+ : public TTypedCommand<TLockRowsOptions>
+{
+public:
+ TLockRowsCommand();
+
+private:
+ NYTree::INodePtr TableWriter;
+ NYPath::TRichYPath Path;
+ std::vector<TString> Locks;
+ NTableClient::ELockType LockType;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTrimRowsCommand
+ : public TTypedCommand<NApi::TTrimTableOptions>
+{
+public:
+ TTrimRowsCommand();
+
+private:
+ NYPath::TRichYPath Path;
+ int TabletIndex;
+ i64 TrimmedRowCount;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEnableTableReplicaCommand
+ : public TTypedCommand<NApi::TAlterTableReplicaOptions>
+{
+public:
+ TEnableTableReplicaCommand();
+
+private:
+ NTabletClient::TTableReplicaId ReplicaId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDisableTableReplicaCommand
+ : public TTypedCommand<NApi::TAlterTableReplicaOptions>
+{
+public:
+ TDisableTableReplicaCommand();
+
+private:
+ NTabletClient::TTableReplicaId ReplicaId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAlterTableReplicaCommand
+ : public TTypedCommand<NApi::TAlterTableReplicaOptions>
+{
+public:
+ TAlterTableReplicaCommand();
+
+private:
+ NTabletClient::TTableReplicaId ReplicaId;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetTabletInfosCommand
+ : public TTypedCommand<NApi::TGetTabletInfosOptions>
+{
+public:
+ TGetTabletInfosCommand();
+
+private:
+ NYPath::TYPath Path;
+ std::vector<int> TabletIndexes;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetTabletErrorsCommand
+ : public TTypedCommand<NApi::TGetTabletErrorsOptions>
+{
+public:
+ TGetTabletErrorsCommand();
+
+private:
+ NYPath::TYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGetTablePivotKeysCommand
+ : public TTypedCommand<NApi::TGetTablePivotKeysOptions>
+{
+public:
+ TGetTablePivotKeysCommand();
+
+private:
+ NYPath::TYPath Path;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCreateTableBackupCommand
+ : public TTypedCommand<NApi::TCreateTableBackupOptions>
+{
+public:
+ TCreateTableBackupCommand();
+
+private:
+ NApi::TBackupManifestPtr Manifest;
+
+ virtual void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRestoreTableBackupCommand
+ : public TTypedCommand<NApi::TRestoreTableBackupOptions>
+{
+public:
+ TRestoreTableBackupCommand();
+
+private:
+ NApi::TBackupManifestPtr Manifest;
+
+ virtual void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/transaction_commands.cpp b/yt/yt/client/driver/transaction_commands.cpp
new file mode 100644
index 0000000000..112bd50943
--- /dev/null
+++ b/yt/yt/client/driver/transaction_commands.cpp
@@ -0,0 +1,141 @@
+#include "config.h"
+#include "transaction_commands.h"
+
+#include <yt/yt/client/transaction_client/timestamp_provider.h>
+
+#include <yt/yt/client/api/transaction.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/ytree/helpers.h>
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NDriver {
+
+using namespace NYTree;
+using namespace NTransactionClient;
+using namespace NCypressClient;
+using namespace NObjectClient;
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStartTransactionCommand::TStartTransactionCommand()
+{
+ RegisterParameter("type", Type)
+ .Default(NTransactionClient::ETransactionType::Master);
+ RegisterParameter("attributes", Attributes)
+ .Default(nullptr);
+ RegisterParameter("sticky", Options.Sticky)
+ .Optional();
+ RegisterParameter("timeout", Options.Timeout)
+ .Optional();
+ RegisterParameter("transaction_id_override", Options.Id)
+ .Optional();
+ RegisterParameter("start_timestamp_override", Options.StartTimestamp)
+ .Optional();
+ RegisterParameter("transaction_id", Options.ParentId)
+ .Optional();
+ RegisterParameter("ping_ancestor_transactions", Options.PingAncestors)
+ .Optional();
+ RegisterParameter("prerequisite_transaction_ids", Options.PrerequisiteTransactionIds)
+ .Optional();
+ RegisterParameter("deadline", Options.Deadline)
+ .Optional();
+ RegisterParameter("atomicity", Options.Atomicity)
+ .Optional();
+ RegisterParameter("durability", Options.Durability)
+ .Optional();
+ RegisterParameter("suppress_start_timestamp_generation", Options.SuppressStartTimestampGeneration)
+ .Optional();
+ RegisterParameter("coordinator_master_cell_tag", Options.CoordinatorMasterCellTag)
+ .Optional();
+ RegisterParameter("replicate_to_master_cell_tags", Options.ReplicateToMasterCellTags)
+ .Optional();
+ RegisterParameter("start_cypress_transaction", Options.StartCypressTransaction)
+ .Optional();
+}
+
+void TStartTransactionCommand::DoExecute(ICommandContextPtr context)
+{
+ Options.Ping = true;
+ Options.AutoAbort = false;
+
+ if (Attributes) {
+ Options.Attributes = ConvertToAttributes(Attributes);
+ }
+
+ if (Type != ETransactionType::Master) {
+ Options.Sticky = true;
+ }
+
+ auto transaction = WaitFor(context->GetClient()->StartTransaction(Type, Options))
+ .ValueOrThrow();
+
+ if (Options.Sticky) {
+ context->GetDriver()->GetStickyTransactionPool()->RegisterTransaction(transaction);
+ } else {
+ transaction->Detach();
+ }
+
+ ProduceSingleOutputValue(context, "transaction_id", transaction->GetId());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TPingTransactionCommand::DoExecute(ICommandContextPtr context)
+{
+ // Specially for evvers@ :)
+ if (!Options.TransactionId) {
+ return;
+ }
+
+ auto transaction = AttachTransaction(context, true);
+ WaitFor(transaction->Ping())
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TCommitTransactionCommand::DoExecute(ICommandContextPtr context)
+{
+ auto transaction = AttachTransaction(context, true);
+ WaitFor(transaction->Commit(Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAbortTransactionCommand::TAbortTransactionCommand()
+{
+ RegisterParameter("force", Options.Force)
+ .Optional();
+}
+
+void TAbortTransactionCommand::DoExecute(ICommandContextPtr context)
+{
+ auto transaction = AttachTransaction(context, true);
+ WaitFor(transaction->Abort(Options))
+ .ThrowOnError();
+
+ ProduceEmptyOutput(context);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TGenerateTimestampCommand::DoExecute(ICommandContextPtr context)
+{
+ auto timestampProvider = context->GetClient()->GetTimestampProvider();
+ auto timestamp = WaitFor(timestampProvider->GenerateTimestamps())
+ .ValueOrThrow();
+
+ ProduceSingleOutputValue(context, "timestamp", timestamp);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/driver/transaction_commands.h b/yt/yt/client/driver/transaction_commands.h
new file mode 100644
index 0000000000..62c61a3fc9
--- /dev/null
+++ b/yt/yt/client/driver/transaction_commands.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "command.h"
+
+namespace NYT::NDriver {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TStartTransactionCommand
+ : public TTypedCommand<NApi::TTransactionStartOptions>
+{
+public:
+ TStartTransactionCommand();
+
+private:
+ NTransactionClient::ETransactionType Type;
+ NYTree::INodePtr Attributes;
+
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPingTransactionCommand
+ : public TTypedCommand<NApi::TTransactionalOptions>
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCommitTransactionCommand
+ : public TTypedCommand<NApi::TTransactionCommitOptions>
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAbortTransactionCommand
+ : public TTypedCommand<NApi::TTransactionAbortOptions>
+{
+public:
+ TAbortTransactionCommand();
+
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TGenerateTimestampOptions
+{ };
+
+class TGenerateTimestampCommand
+ : public TTypedCommand<TGenerateTimestampOptions>
+{
+private:
+ void DoExecute(ICommandContextPtr context) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDriver
+
diff --git a/yt/yt/client/driver/ya.make b/yt/yt/client/driver/ya.make
new file mode 100644
index 0000000000..d08c35855e
--- /dev/null
+++ b/yt/yt/client/driver/ya.make
@@ -0,0 +1,31 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ admin_commands.cpp
+ authentication_commands.cpp
+ chaos_commands.cpp
+ command.cpp
+ config.cpp
+ cypress_commands.cpp
+ driver.cpp
+ etc_commands.cpp
+ file_commands.cpp
+ helpers.cpp
+ journal_commands.cpp
+ proxy_discovery_cache.cpp
+ query_commands.cpp
+ queue_commands.cpp
+ scheduler_commands.cpp
+ table_commands.cpp
+ transaction_commands.cpp
+ internal_commands.cpp
+)
+
+PEERDIR(
+ yt/yt/client
+ yt/yt/client/formats
+)
+
+END()
diff --git a/yt/yt/client/election/public.cpp b/yt/yt/client/election/public.cpp
new file mode 100644
index 0000000000..0b1201670b
--- /dev/null
+++ b/yt/yt/client/election/public.cpp
@@ -0,0 +1,12 @@
+#include "public.h"
+
+namespace NYT::NElection {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TCellId NullCellId;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NElection
+
diff --git a/yt/yt/client/election/public.h b/yt/yt/client/election/public.h
new file mode 100644
index 0000000000..b48fc712db
--- /dev/null
+++ b/yt/yt/client/election/public.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <yt/yt/core/misc/error_code.h>
+
+#include <library/cpp/yt/misc/enum.h>
+#include <library/cpp/yt/misc/guid.h>
+
+namespace NYT::NElection {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TEpochId = TGuid;
+using TPeerPriority = std::pair<i64, i64>;
+
+using TPeerId = int;
+constexpr TPeerId InvalidPeerId = -1;
+
+using TCellId = TGuid;
+extern const TCellId NullCellId;
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((InvalidState) (800))
+ ((InvalidLeader) (801))
+ ((InvalidEpoch) (802))
+);
+
+DEFINE_ENUM(EPeerState,
+ (Stopped)
+ (Voting)
+ (Leading)
+ (Following)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NElection
diff --git a/yt/yt/client/federated/client.cpp b/yt/yt/client/federated/client.cpp
new file mode 100644
index 0000000000..3efebfc430
--- /dev/null
+++ b/yt/yt/client/federated/client.cpp
@@ -0,0 +1,732 @@
+#include "client.h"
+
+#include "config.h"
+#include "private.h"
+
+#include <yt/yt/client/api/client.h>
+#include <yt/yt/client/api/transaction.h>
+
+#include <yt/yt/client/misc/method_helpers.h>
+
+#include <yt/yt/client/ypath/public.h>
+
+#include <yt/yt/client/object_client/helpers.h>
+
+#include <yt/yt/core/concurrency/periodic_executor.h>
+
+#include <yt/yt/core/net/address.h>
+#include <yt/yt/core/net/local_address.h>
+
+#include <yt/yt/core/rpc/dispatcher.h>
+#include <yt/yt/core/rpc/helpers.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+using namespace NYT::NApi;
+
+const auto& Logger = FederatedClientLogger;
+
+DECLARE_REFCOUNTED_CLASS(TClient)
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<TString> GetDataCenterByClient(const IClientPtr& client)
+{
+ TListNodeOptions options;
+ options.MaxSize = 1;
+
+ auto items = NConcurrency::WaitFor(client->ListNode(RpcProxiesPath, options))
+ .ValueOrThrow();
+ auto itemsList = NYTree::ConvertTo<NYTree::IListNodePtr>(items);
+ if (!itemsList->GetChildCount()) {
+ return std::nullopt;
+ }
+ auto host = itemsList->GetChildren()[0];
+ return NNet::InferYPClusterFromHostName(host->GetValue<TString>());
+}
+
+class TTransaction
+ : public ITransaction
+{
+public:
+ TTransaction(TClientPtr client, int clientIndex, ITransactionPtr underlying);
+
+ TFuture<ITransactionPtr> StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const TTransactionStartOptions& options = {}) override;
+
+ TFuture<IUnversionedRowsetPtr> LookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TLookupRowsOptions& options = {}) override;
+
+ TFuture<TSelectRowsResult> SelectRows(
+ const TString& query,
+ const TSelectRowsOptions& options = {}) override;
+
+ void ModifyRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<TRowModification> modifications,
+ const TModifyRowsOptions& options) override;
+
+ TFuture<TTransactionFlushResult> Flush() override;
+
+ TFuture<void> Ping(const NApi::TTransactionPingOptions& options = {}) override;
+
+ TFuture<TTransactionCommitResult> Commit(const TTransactionCommitOptions& options = TTransactionCommitOptions()) override;
+
+ TFuture<void> Abort(const TTransactionAbortOptions& options = TTransactionAbortOptions()) override;
+
+ TFuture<IVersionedRowsetPtr> VersionedLookupRows(
+ const NYPath::TYPath&, NTableClient::TNameTablePtr,
+ const TSharedRange<NTableClient::TUnversionedRow>&,
+ const TVersionedLookupRowsOptions&) override;
+
+ TFuture<std::vector<IUnversionedRowsetPtr>> MultiLookup(
+ const std::vector<TMultiLookupSubrequest>&,
+ const TMultiLookupOptions&) override;
+
+ TFuture<NYson::TYsonString> ExplainQuery(const TString&, const TExplainQueryOptions&) override;
+
+ TFuture<NYson::TYsonString> GetNode(const NYPath::TYPath&, const TGetNodeOptions&) override;
+
+ TFuture<NYson::TYsonString> ListNode(const NYPath::TYPath&, const TListNodeOptions&) override;
+
+ TFuture<bool> NodeExists(const NYPath::TYPath&, const TNodeExistsOptions&) override;
+
+ TFuture<TPullRowsResult> PullRows(const NYPath::TYPath&, const TPullRowsOptions&) override;
+
+ IClientPtr GetClient() const override
+ {
+ return Underlying_->GetClient();
+ }
+
+ NTransactionClient::ETransactionType GetType() const override
+ {
+ return Underlying_->GetType();
+ }
+
+ NTransactionClient::TTransactionId GetId() const override
+ {
+ return Underlying_->GetId();
+ }
+
+ NTransactionClient::TTimestamp GetStartTimestamp() const override
+ {
+ return Underlying_->GetStartTimestamp();
+ }
+
+ virtual NTransactionClient::EAtomicity GetAtomicity() const override
+ {
+ return Underlying_->GetAtomicity();
+ }
+
+ virtual NTransactionClient::EDurability GetDurability() const override
+ {
+ return Underlying_->GetDurability();
+ }
+
+ virtual TDuration GetTimeout() const override
+ {
+ return Underlying_->GetTimeout();
+ }
+
+ void Detach() override
+ {
+ return Underlying_->Detach();
+ }
+
+ void RegisterAlienTransaction(const ITransactionPtr& transaction) override
+ {
+ return Underlying_->RegisterAlienTransaction(transaction);
+ }
+
+ IConnectionPtr GetConnection() override
+ {
+ return Underlying_->GetConnection();
+ }
+
+ void SubscribeCommitted(const TCommittedHandler& handler) override
+ {
+ Underlying_->SubscribeCommitted(handler);
+ }
+
+ void UnsubscribeCommitted(const TCommittedHandler& handler) override
+ {
+ Underlying_->UnsubscribeCommitted(handler);
+ }
+
+ void SubscribeAborted(const TAbortedHandler& handler) override
+ {
+ Underlying_->SubscribeAborted(handler);
+ }
+
+ void UnsubscribeAborted(const TAbortedHandler& handler) override
+ {
+ Underlying_->UnsubscribeAborted(handler);
+ }
+
+ UNIMPLEMENTED_METHOD(TFuture<void>, SetNode, (const NYPath::TYPath&, const NYson::TYsonString&, const TSetNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, MultisetAttributesNode, (const NYPath::TYPath&, const NYTree::IMapNodePtr&, const TMultisetAttributesNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, RemoveNode, (const NYPath::TYPath&, const TRemoveNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, CreateNode, (const NYPath::TYPath&, NObjectClient::EObjectType, const TCreateNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TLockNodeResult>, LockNode, (const NYPath::TYPath&, NCypressClient::ELockMode, const TLockNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, UnlockNode, (const NYPath::TYPath&, const TUnlockNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, CopyNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TCopyNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, MoveNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TMoveNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, LinkNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TLinkNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ConcatenateNodes, (const std::vector<NYPath::TRichYPath>&, const NYPath::TRichYPath&, const TConcatenateNodesOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ExternalizeNode, (const NYPath::TYPath&, NObjectClient::TCellTag, const TExternalizeNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, InternalizeNode, (const NYPath::TYPath&, const TInternalizeNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NObjectClient::TObjectId>, CreateObject, (NObjectClient::EObjectType, const TCreateObjectOptions&));
+
+ UNIMPLEMENTED_METHOD(TFuture<ITableReaderPtr>, CreateTableReader, (const NYPath::TRichYPath&, const TTableReaderOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<IFileReaderPtr>, CreateFileReader, (const NYPath::TYPath&, const TFileReaderOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<ITableWriterPtr>, CreateTableWriter, (const NYPath::TRichYPath&, const TTableWriterOptions&));
+ UNIMPLEMENTED_METHOD(IFileWriterPtr, CreateFileWriter, (const NYPath::TRichYPath&, const TFileWriterOptions&));
+ UNIMPLEMENTED_METHOD(IJournalReaderPtr, CreateJournalReader, (const NYPath::TYPath&, const TJournalReaderOptions&));
+ UNIMPLEMENTED_METHOD(IJournalWriterPtr, CreateJournalWriter, (const NYPath::TYPath&, const TJournalWriterOptions&));
+
+private:
+ const TClientPtr Client_;
+ const int ClientIndex_;
+ const ITransactionPtr Underlying_;
+
+ void OnResult(const TErrorOr<void>& error);
+};
+
+DECLARE_REFCOUNTED_TYPE(TTransaction)
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(TClientDescription)
+
+struct TClientDescription final
+{
+ TClientDescription(IClientPtr client, int priority)
+ : Client(std::move(client))
+ , Priority(priority)
+ { }
+
+ IClientPtr Client;
+ int Priority;
+ std::atomic<bool> HasErrors{false};
+};
+
+DEFINE_REFCOUNTED_TYPE(TClientDescription)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TClient
+ : public IClient
+{
+public:
+ TClient(const std::vector<IClientPtr>& underlyingClients, TFederationConfigPtr config);
+
+ TFuture<IUnversionedRowsetPtr> LookupRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TLookupRowsOptions& options = {}) override;
+ TFuture<TSelectRowsResult> SelectRows(
+ const TString& query,
+ const TSelectRowsOptions& options = {}) override;
+ TFuture<std::vector<IUnversionedRowsetPtr>> MultiLookup(
+ const std::vector<TMultiLookupSubrequest>&,
+ const TMultiLookupOptions&) override;
+ TFuture<IVersionedRowsetPtr> VersionedLookupRows(
+ const NYPath::TYPath&, NTableClient::TNameTablePtr,
+ const TSharedRange<NTableClient::TUnversionedRow>&,
+ const TVersionedLookupRowsOptions&) override;
+ TFuture<TPullRowsResult> PullRows(const NYPath::TYPath&, const TPullRowsOptions&) override;
+
+ TFuture<NQueueClient::IQueueRowsetPtr> PullQueue(
+ const NYPath::TRichYPath&,
+ i64,
+ int,
+ const NQueueClient::TQueueRowBatchReadOptions&,
+ const TPullQueueOptions&) override;
+
+ TFuture<NQueueClient::IQueueRowsetPtr> PullConsumer(
+ const NYPath::TRichYPath&,
+ const NYPath::TRichYPath&,
+ i64,
+ int,
+ const NQueueClient::TQueueRowBatchReadOptions&,
+ const TPullConsumerOptions&) override;
+
+ TFuture<ITransactionPtr> StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const NApi::TTransactionStartOptions& options) override;
+
+ TFuture<NYson::TYsonString> ExplainQuery(const TString&, const TExplainQueryOptions&) override;
+
+ TFuture<NYson::TYsonString> GetNode(const NYPath::TYPath&, const TGetNodeOptions&) override;
+ TFuture<NYson::TYsonString> ListNode(const NYPath::TYPath&, const TListNodeOptions&) override;
+ TFuture<bool> NodeExists(const NYPath::TYPath&, const TNodeExistsOptions&) override;
+
+ const NTabletClient::ITableMountCachePtr& GetTableMountCache() override;
+ TFuture<std::vector<TTabletInfo>> GetTabletInfos(const NYPath::TYPath&, const std::vector<int>&, const TGetTabletInfosOptions&) override;
+
+ const NTransactionClient::ITimestampProviderPtr& GetTimestampProvider() override;
+
+ ITransactionPtr AttachTransaction(NTransactionClient::TTransactionId, const TTransactionAttachOptions&) override;
+
+ IConnectionPtr GetConnection() override
+ {
+ auto [client, _] = GetActiveClient();
+ return client->GetConnection();
+ }
+
+ std::optional<TStringBuf> GetClusterName(bool fetchIfNull) override
+ {
+ auto [client, _] = GetActiveClient();
+ return client->GetClusterName(fetchIfNull);
+ }
+
+ void Terminate() override
+ { }
+
+ // IClientBase Unsupported methods.
+ UNIMPLEMENTED_METHOD(TFuture<void>, SetNode, (const NYPath::TYPath&, const NYson::TYsonString&, const TSetNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, MultisetAttributesNode, (const NYPath::TYPath&, const NYTree::IMapNodePtr&, const TMultisetAttributesNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, RemoveNode, (const NYPath::TYPath&, const TRemoveNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, CreateNode, (const NYPath::TYPath&, NObjectClient::EObjectType, const TCreateNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TLockNodeResult>, LockNode, (const NYPath::TYPath&, NCypressClient::ELockMode, const TLockNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, UnlockNode, (const NYPath::TYPath&, const TUnlockNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, CopyNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TCopyNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, MoveNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TMoveNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NCypressClient::TNodeId>, LinkNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TLinkNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ConcatenateNodes, (const std::vector<NYPath::TRichYPath>&, const NYPath::TRichYPath&, const TConcatenateNodesOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ExternalizeNode, (const NYPath::TYPath&, NObjectClient::TCellTag, const TExternalizeNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, InternalizeNode, (const NYPath::TYPath&, const TInternalizeNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NObjectClient::TObjectId>, CreateObject, (NObjectClient::EObjectType, const TCreateObjectOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TQueryResult>, GetQueryResult, (NQueryTrackerClient::TQueryId, i64, const TGetQueryResultOptions&));
+
+ // IClient unsupported methods.
+ UNIMPLEMENTED_METHOD(TFuture<void>, RegisterQueueConsumer, (const NYPath::TRichYPath&, const NYPath::TRichYPath&, bool, const TRegisterQueueConsumerOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, UnregisterQueueConsumer, (const NYPath::TRichYPath&, const NYPath::TRichYPath&, const TUnregisterQueueConsumerOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<std::vector<TListQueueConsumerRegistrationsResult>>, ListQueueConsumerRegistrations, (const std::optional<NYPath::TRichYPath>&, const std::optional<NYPath::TRichYPath>&, const TListQueueConsumerRegistrationsOptions&));
+ UNIMPLEMENTED_METHOD(const NChaosClient::IReplicationCardCachePtr&, GetReplicationCardCache, ());
+ UNIMPLEMENTED_METHOD(TFuture<void>, MountTable, (const NYPath::TYPath&, const TMountTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, UnmountTable, (const NYPath::TYPath&, const TUnmountTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, RemountTable, (const NYPath::TYPath&, const TRemountTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, FreezeTable, (const NYPath::TYPath&, const TFreezeTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, UnfreezeTable, (const NYPath::TYPath&, const TUnfreezeTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ReshardTable, (const NYPath::TYPath&, const std::vector<NTableClient::TUnversionedOwningRow>&, const TReshardTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ReshardTable, (const NYPath::TYPath&, int, const TReshardTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<std::vector<NTabletClient::TTabletActionId>>, ReshardTableAutomatic, (const NYPath::TYPath&, const TReshardTableAutomaticOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, TrimTable, (const NYPath::TYPath&, int, i64, const TTrimTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AlterTable, (const NYPath::TYPath&, const TAlterTableOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AlterTableReplica, (NTabletClient::TTableReplicaId, const TAlterTableReplicaOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AlterReplicationCard, (NChaosClient::TReplicationCardId, const TAlterReplicationCardOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<std::vector<NTabletClient::TTableReplicaId>>, GetInSyncReplicas, (const NYPath::TYPath&, const NTableClient::TNameTablePtr&, const TSharedRange<NTableClient::TUnversionedRow>&, const TGetInSyncReplicasOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<std::vector<NTabletClient::TTableReplicaId>>, GetInSyncReplicas, (const NYPath::TYPath&, const TGetInSyncReplicasOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TGetTabletErrorsResult>, GetTabletErrors, (const NYPath::TYPath&, const TGetTabletErrorsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<std::vector<NTabletClient::TTabletActionId>>, BalanceTabletCells, (const TString&, const std::vector<NYPath::TYPath>&, const TBalanceTabletCellsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TSkynetSharePartsLocationsPtr>, LocateSkynetShare, (const NYPath::TRichYPath&, const TLocateSkynetShareOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<std::vector<NTableClient::TColumnarStatistics>>, GetColumnarStatistics, (const std::vector<NYPath::TRichYPath>&, const TGetColumnarStatisticsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TMultiTablePartitions>, PartitionTables, (const std::vector<NYPath::TRichYPath>&, const TPartitionTablesOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NYson::TYsonString>, GetTablePivotKeys, (const NYPath::TYPath&, const TGetTablePivotKeysOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, CreateTableBackup, (const TBackupManifestPtr&, const TCreateTableBackupOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, RestoreTableBackup, (const TBackupManifestPtr&, const TRestoreTableBackupOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, TruncateJournal, (const NYPath::TYPath&, i64, const TTruncateJournalOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TGetFileFromCacheResult>, GetFileFromCache, (const TString&, const TGetFileFromCacheOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TPutFileToCacheResult>, PutFileToCache, (const NYPath::TYPath&, const TString&, const TPutFileToCacheOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AddMember, (const TString&, const TString&, const TAddMemberOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, RemoveMember, (const TString&, const TString&, const TRemoveMemberOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TCheckPermissionResponse>, CheckPermission, (const TString&, const NYPath::TYPath&, NYTree::EPermission, const TCheckPermissionOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TCheckPermissionByAclResult>, CheckPermissionByAcl, (const std::optional<TString>&, NYTree::EPermission, NYTree::INodePtr, const TCheckPermissionByAclOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, TransferAccountResources, (const TString&, const TString&, NYTree::INodePtr, const TTransferAccountResourcesOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, TransferPoolResources, (const TString&, const TString&, const TString&, NYTree::INodePtr, const TTransferPoolResourcesOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NScheduler::TOperationId>, StartOperation, (NScheduler::EOperationType, const NYson::TYsonString&, const TStartOperationOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AbortOperation, (const NScheduler::TOperationIdOrAlias&, const TAbortOperationOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, SuspendOperation, (const NScheduler::TOperationIdOrAlias&, const TSuspendOperationOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ResumeOperation, (const NScheduler::TOperationIdOrAlias&, const TResumeOperationOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, CompleteOperation, (const NScheduler::TOperationIdOrAlias&, const TCompleteOperationOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, UpdateOperationParameters, (const NScheduler::TOperationIdOrAlias&, const NYson::TYsonString&, const TUpdateOperationParametersOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TOperation>, GetOperation, (const NScheduler::TOperationIdOrAlias&, const TGetOperationOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, DumpJobContext, (NJobTrackerClient::TJobId, const NYPath::TYPath&, const TDumpJobContextOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr>, GetJobInput, (NJobTrackerClient::TJobId, const TGetJobInputOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NYson::TYsonString>, GetJobInputPaths, (NJobTrackerClient::TJobId, const TGetJobInputPathsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NYson::TYsonString>, GetJobSpec, (NJobTrackerClient::TJobId, const TGetJobSpecOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TSharedRef>, GetJobStderr, (const NScheduler::TOperationIdOrAlias&, NJobTrackerClient::TJobId, const TGetJobStderrOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TSharedRef>, GetJobFailContext, (const NScheduler::TOperationIdOrAlias&, NJobTrackerClient::TJobId, const TGetJobFailContextOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TListOperationsResult>, ListOperations, (const TListOperationsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TListJobsResult>, ListJobs, (const NScheduler::TOperationIdOrAlias&, const TListJobsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NYson::TYsonString>, GetJob, (const NScheduler::TOperationIdOrAlias&, NJobTrackerClient::TJobId, const TGetJobOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AbandonJob, (NJobTrackerClient::TJobId, const TAbandonJobOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TPollJobShellResponse>, PollJobShell, (NJobTrackerClient::TJobId, const std::optional<TString>&, const NYson::TYsonString&, const TPollJobShellOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AbortJob, (NJobTrackerClient::TJobId, const TAbortJobOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TClusterMeta>, GetClusterMeta, (const TGetClusterMetaOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, CheckClusterLiveness, (const TCheckClusterLivenessOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<int>, BuildSnapshot, (const TBuildSnapshotOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TCellIdToSnapshotIdMap>, BuildMasterSnapshots, (const TBuildMasterSnapshotsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, SwitchLeader, (NObjectClient::TCellId, const TString&, const TSwitchLeaderOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ResetStateHash, (NObjectClient::TCellId, const TResetStateHashOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, GCCollect, (const TGCCollectOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, KillProcess, (const TString&, const TKillProcessOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TString>, WriteCoreDump, (const TString&, const TWriteCoreDumpOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TGuid>, WriteLogBarrier, (const TString&, const TWriteLogBarrierOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TString>, WriteOperationControllerCoreDump, (NJobTrackerClient::TOperationId, const TWriteOperationControllerCoreDumpOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, HealExecNode, (const TString&, const THealExecNodeOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, SuspendCoordinator, (NObjectClient::TCellId, const TSuspendCoordinatorOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ResumeCoordinator, (NObjectClient::TCellId, const TResumeCoordinatorOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, MigrateReplicationCards, (NObjectClient::TCellId, const TMigrateReplicationCardsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, SuspendChaosCells, (const std::vector<NObjectClient::TCellId>&, const TSuspendChaosCellsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ResumeChaosCells, (const std::vector<NObjectClient::TCellId>&, const TResumeChaosCellsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, SuspendTabletCells, (const std::vector<NObjectClient::TCellId>&, const TSuspendTabletCellsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, ResumeTabletCells, (const std::vector<NObjectClient::TCellId>&, const TResumeTabletCellsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NChaosClient::TReplicationCardPtr>, GetReplicationCard, (NChaosClient::TReplicationCardId, const TGetReplicationCardOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, UpdateChaosTableReplicaProgress, (NChaosClient::TReplicaId, const TUpdateChaosTableReplicaProgressOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TMaintenanceId>, AddMaintenance, (EMaintenanceComponent, const TString&, EMaintenanceType, const TString&, const TAddMaintenanceOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TMaintenanceCounts>, RemoveMaintenance, (EMaintenanceComponent, const TString&, const TMaintenanceFilter&, const TRemoveMaintenanceOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TDisableChunkLocationsResult>, DisableChunkLocations, (const TString&, const std::vector<TGuid>&, const TDisableChunkLocationsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TDestroyChunkLocationsResult>, DestroyChunkLocations, (const TString&, const std::vector<TGuid>&, const TDestroyChunkLocationsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TResurrectChunkLocationsResult>, ResurrectChunkLocations, (const TString&, const std::vector<TGuid>&, const TResurrectChunkLocationsOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TRequestRebootResult>, RequestReboot, (const TString&, const TRequestRebootOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, SetUserPassword, (const TString&, const TString&, const TString&, const TSetUserPasswordOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TIssueTokenResult>, IssueToken, (const TString&, const TString&, const TIssueTokenOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, RevokeToken, (const TString&, const TString&, const TString&, const TRevokeTokenOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TListUserTokensResult>, ListUserTokens, (const TString&, const TString&, const TListUserTokensOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<NQueryTrackerClient::TQueryId>, StartQuery, (NQueryTrackerClient::EQueryEngine, const TString&, const TStartQueryOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AbortQuery, (NQueryTrackerClient::TQueryId, const TAbortQueryOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<IUnversionedRowsetPtr>, ReadQueryResult, (NQueryTrackerClient::TQueryId, i64, const TReadQueryResultOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TQuery>, GetQuery, (NQueryTrackerClient::TQueryId, const TGetQueryOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<TListQueriesResult>, ListQueries, (const TListQueriesOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<void>, AlterQuery, (NQueryTrackerClient::TQueryId, const TAlterQueryOptions&));
+
+ UNIMPLEMENTED_METHOD(TFuture<ITableReaderPtr>, CreateTableReader, (const NYPath::TRichYPath&, const TTableReaderOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<ITableWriterPtr>, CreateTableWriter, (const NYPath::TRichYPath&, const TTableWriterOptions&));
+ UNIMPLEMENTED_METHOD(TFuture<IFileReaderPtr>, CreateFileReader, (const NYPath::TYPath&, const TFileReaderOptions&));
+ UNIMPLEMENTED_METHOD(IFileWriterPtr, CreateFileWriter, (const NYPath::TRichYPath&, const TFileWriterOptions&));
+ UNIMPLEMENTED_METHOD(IJournalReaderPtr, CreateJournalReader, (const NYPath::TYPath&, const TJournalReaderOptions&));
+ UNIMPLEMENTED_METHOD(IJournalWriterPtr, CreateJournalWriter, (const NYPath::TYPath&, const TJournalWriterOptions&));
+
+ friend class TTransaction;
+
+private:
+ struct TActiveClientInfo
+ {
+ IClientPtr Client;
+ int ClientIndex;
+ };
+
+ template <class T>
+ TFuture<T> DoCall(int retryAttemptCount, const TCallback<TFuture<T>(const IClientPtr&, int)>& callee);
+ void HandleError(const TErrorOr<void>& error, int clientIndex);
+
+ void UpdateActiveClient();
+ TActiveClientInfo GetActiveClient();
+
+ void CheckClustersHealth();
+
+private:
+ const TFederationConfigPtr Config_;
+ const NConcurrency::TPeriodicExecutorPtr Executor_;
+
+ std::vector<TClientDescriptionPtr> UnderlyingClients_;
+ IClientPtr ActiveClient_;
+ std::atomic<int> ActiveClientIndex_;
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TReaderWriterSpinLock, Lock_);
+};
+
+DECLARE_REFCOUNTED_TYPE(TTransaction)
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTransaction::TTransaction(TClientPtr client, int clientIndex, ITransactionPtr underlying)
+ : Client_(std::move(client))
+ , ClientIndex_(clientIndex)
+ , Underlying_(std::move(underlying))
+{ }
+
+void TTransaction::OnResult(const TErrorOr<void>& error)
+{
+ if (!error.IsOK()) {
+ Client_->HandleError(error, ClientIndex_);
+ }
+}
+
+#define TRANSACTION_METHOD_IMPL(ResultType, MethodName, Args) \
+TFuture<ResultType> TTransaction::MethodName(Y_METHOD_USED_ARGS_DECLARATION(Args)) \
+{ \
+ auto future = Underlying_->MethodName(Y_PASS_METHOD_USED_ARGS(Args)); \
+ future.Subscribe(BIND(&TTransaction::OnResult, MakeStrong(this))); \
+ return future; \
+} Y_SEMICOLON_GUARD
+
+TRANSACTION_METHOD_IMPL(IUnversionedRowsetPtr, LookupRows, (const NYPath::TYPath&, NTableClient::TNameTablePtr, const TSharedRange<NTableClient::TUnversionedRow>&, const TLookupRowsOptions&));
+TRANSACTION_METHOD_IMPL(TSelectRowsResult, SelectRows, (const TString&, const TSelectRowsOptions&));
+TRANSACTION_METHOD_IMPL(void, Ping, (const NApi::TTransactionPingOptions&));
+TRANSACTION_METHOD_IMPL(TTransactionCommitResult, Commit, (const TTransactionCommitOptions&));
+TRANSACTION_METHOD_IMPL(void, Abort, (const TTransactionAbortOptions&));
+TRANSACTION_METHOD_IMPL(IVersionedRowsetPtr, VersionedLookupRows, (const NYPath::TYPath&, NTableClient::TNameTablePtr, const TSharedRange<NTableClient::TUnversionedRow>&, const TVersionedLookupRowsOptions&));
+TRANSACTION_METHOD_IMPL(std::vector<IUnversionedRowsetPtr>, MultiLookup, (const std::vector<TMultiLookupSubrequest>&, const TMultiLookupOptions&));
+TRANSACTION_METHOD_IMPL(TPullRowsResult, PullRows, (const NYPath::TYPath&, const TPullRowsOptions&));
+TRANSACTION_METHOD_IMPL(NYson::TYsonString, ExplainQuery, (const TString&, const TExplainQueryOptions&));
+TRANSACTION_METHOD_IMPL(NYson::TYsonString, GetNode, (const NYPath::TYPath&, const TGetNodeOptions&));
+TRANSACTION_METHOD_IMPL(NYson::TYsonString, ListNode, (const NYPath::TYPath&, const TListNodeOptions&));
+TRANSACTION_METHOD_IMPL(bool, NodeExists, (const NYPath::TYPath&, const TNodeExistsOptions&));
+
+void TTransaction::ModifyRows(
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<TRowModification> modifications,
+ const TModifyRowsOptions& options)
+{
+ Underlying_->ModifyRows(path, nameTable, modifications, options);
+}
+
+TFuture<TTransactionFlushResult> TTransaction::Flush()
+{
+ auto future = Underlying_->Flush();
+ future.Subscribe(BIND(&TTransaction::OnResult, MakeStrong(this)));
+ return future;
+}
+
+TFuture<ITransactionPtr> TTransaction::StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const TTransactionStartOptions& options)
+{
+ return Underlying_->StartTransaction(type, options).ApplyUnique(BIND(
+ [this, this_ = MakeStrong(this)] (TErrorOr<ITransactionPtr>&& result) -> TErrorOr<ITransactionPtr> {
+ if (!result.IsOK()) {
+ Client_->HandleError(result, ClientIndex_);
+ return result;
+ } else {
+ return {New<TTransaction>(Client_, ClientIndex_, result.Value())};
+ }
+ }
+ ));
+}
+
+DEFINE_REFCOUNTED_TYPE(TTransaction)
+
+////////////////////////////////////////////////////////////////////////////////
+
+TClient::TClient(const std::vector<IClientPtr>& underlyingClients, TFederationConfigPtr config)
+ : Config_(std::move(config))
+ , Executor_(New<NConcurrency::TPeriodicExecutor>(
+ NRpc::TDispatcher::Get()->GetLightInvoker(),
+ BIND(&TClient::CheckClustersHealth, MakeWeak(this)),
+ Config_->ClusterHealthCheckPeriod))
+{
+ YT_VERIFY(!underlyingClients.empty());
+
+ UnderlyingClients_.reserve(underlyingClients.size());
+ const auto& localDatacenter = NNet::GetLocalYPCluster();
+ for (const auto& client : underlyingClients) {
+ int priority = GetDataCenterByClient(client) == localDatacenter ? 1 : 0;
+ UnderlyingClients_.push_back(New<TClientDescription>(client, priority));
+ }
+ std::stable_sort(UnderlyingClients_.begin(), UnderlyingClients_.end(), [](const auto& lhs, const auto& rhs) {
+ return lhs->Priority > rhs->Priority;
+ });
+
+ ActiveClient_ = UnderlyingClients_[0]->Client;
+ ActiveClientIndex_ = 0;
+
+ Executor_->Start();
+}
+
+void TClient::CheckClustersHealth()
+{
+ TCheckClusterLivenessOptions options;
+ options.CheckCypressRoot = true;
+ options.CheckTabletCellBundle = Config_->BundleName;
+
+ int activeClientIndex = ActiveClientIndex_.load();
+ std::optional<int> betterClientIndex;
+
+ std::vector<TFuture<void>> checks;
+ checks.reserve(UnderlyingClients_.size());
+
+ for (const auto& clientDescription : UnderlyingClients_) {
+ checks.emplace_back(clientDescription->Client->CheckClusterLiveness(options));
+ }
+
+ for (int index = 0; index < std::ssize(checks); ++index) {
+ const auto& check = checks[index];
+ bool hasErrors = !NConcurrency::WaitFor(check).IsOK();
+ UnderlyingClients_[index]->HasErrors = hasErrors;
+ if (!betterClientIndex && !hasErrors && index < activeClientIndex) {
+ betterClientIndex = index;
+ }
+ }
+
+ if (betterClientIndex && ActiveClientIndex_ == activeClientIndex) {
+ int newClientIndex = *betterClientIndex;
+ auto guard = NThreading::WriterGuard(Lock_);
+ ActiveClient_ = UnderlyingClients_[newClientIndex]->Client;
+ ActiveClientIndex_ = newClientIndex;
+ return;
+ }
+
+ // If active cluster is not healthy, try changing it.
+ if (UnderlyingClients_[activeClientIndex]->HasErrors) {
+ auto guard = NThreading::WriterGuard(Lock_);
+ // Check that active client wasn't changed.
+ if (ActiveClientIndex_ == activeClientIndex && UnderlyingClients_[activeClientIndex]->HasErrors) {
+ UpdateActiveClient();
+ }
+ }
+}
+
+template <class T>
+TFuture<T> TClient::DoCall(int retryAttemptCount, const TCallback<TFuture<T>(const IClientPtr&, int)>& callee)
+{
+ auto [client, clientIndex] = GetActiveClient();
+ return callee(client, clientIndex).ApplyUnique(BIND(
+ [
+ this,
+ this_ = MakeStrong(this),
+ retryAttemptCount,
+ callee,
+ clientIndex = clientIndex
+ ] (TErrorOr<T>&& result) {
+ if (!result.IsOK()) {
+ HandleError(result, clientIndex);
+ if (retryAttemptCount > 1) {
+ return DoCall<T>(retryAttemptCount - 1, callee);
+ }
+ }
+ return MakeFuture(std::move(result));
+ }));
+}
+
+TFuture<ITransactionPtr> TClient::StartTransaction(
+ NTransactionClient::ETransactionType type,
+ const NApi::TTransactionStartOptions& options)
+{
+ auto callee = BIND([this_ = MakeStrong(this), type, options] (const IClientPtr& client, int clientIndex) {
+ return client->StartTransaction(type, options).ApplyUnique(BIND(
+ [this_, clientIndex] (ITransactionPtr&& transaction) -> ITransactionPtr {
+ return New<TTransaction>(std::move(this_), clientIndex, std::move(transaction));
+ }));
+ });
+
+ return DoCall<ITransactionPtr>(Config_->ClusterRetryAttempts, callee);
+}
+
+#define CLIENT_METHOD_IMPL(ResultType, MethodName, Args) \
+TFuture<ResultType> TClient::MethodName(Y_METHOD_USED_ARGS_DECLARATION(Args)) \
+{ \
+ auto callee = BIND([Y_PASS_METHOD_USED_ARGS(Args)] (const IClientPtr& client, int /*clientIndex*/) { \
+ return client->MethodName(Y_PASS_METHOD_USED_ARGS(Args)); \
+ }); \
+ return DoCall<ResultType>(Config_->ClusterRetryAttempts, callee); \
+} Y_SEMICOLON_GUARD
+
+CLIENT_METHOD_IMPL(IUnversionedRowsetPtr, LookupRows, (const NYPath::TYPath&, NTableClient::TNameTablePtr, const TSharedRange<NTableClient::TLegacyKey>&, const TLookupRowsOptions&));
+CLIENT_METHOD_IMPL(TSelectRowsResult, SelectRows, (const TString&, const TSelectRowsOptions&));
+CLIENT_METHOD_IMPL(std::vector<IUnversionedRowsetPtr>, MultiLookup, (const std::vector<TMultiLookupSubrequest>&, const TMultiLookupOptions&));
+CLIENT_METHOD_IMPL(IVersionedRowsetPtr, VersionedLookupRows, (const NYPath::TYPath&, NTableClient::TNameTablePtr, const TSharedRange<NTableClient::TUnversionedRow>&, const TVersionedLookupRowsOptions&));
+CLIENT_METHOD_IMPL(TPullRowsResult, PullRows, (const NYPath::TYPath&, const TPullRowsOptions&));
+CLIENT_METHOD_IMPL(NQueueClient::IQueueRowsetPtr, PullQueue, (const NYPath::TRichYPath&, i64, int, const NQueueClient::TQueueRowBatchReadOptions&, const TPullQueueOptions&));
+CLIENT_METHOD_IMPL(NQueueClient::IQueueRowsetPtr, PullConsumer, (const NYPath::TRichYPath&, const NYPath::TRichYPath&, i64, int, const NQueueClient::TQueueRowBatchReadOptions&, const TPullConsumerOptions&));
+CLIENT_METHOD_IMPL(NYson::TYsonString, ExplainQuery, (const TString&, const TExplainQueryOptions&));
+CLIENT_METHOD_IMPL(NYson::TYsonString, GetNode, (const NYPath::TYPath&, const TGetNodeOptions&));
+CLIENT_METHOD_IMPL(NYson::TYsonString, ListNode, (const NYPath::TYPath&, const TListNodeOptions&));
+CLIENT_METHOD_IMPL(bool, NodeExists, (const NYPath::TYPath&, const TNodeExistsOptions&));
+CLIENT_METHOD_IMPL(std::vector<TTabletInfo>, GetTabletInfos, (const NYPath::TYPath&, const std::vector<int>&, const TGetTabletInfosOptions&));
+
+const NTabletClient::ITableMountCachePtr& TClient::GetTableMountCache()
+{
+ auto [client, _] = GetActiveClient();
+ return client->GetTableMountCache();
+}
+
+const NTransactionClient::ITimestampProviderPtr& TClient::GetTimestampProvider()
+{
+ auto [client, _] = GetActiveClient();
+ return client->GetTimestampProvider();
+}
+
+ITransactionPtr TClient::AttachTransaction(
+ NTransactionClient::TTransactionId transactionId,
+ const TTransactionAttachOptions& options)
+{
+ auto transactionClusterTag = NObjectClient::CellTagFromId(transactionId);
+ for (const auto& clientDescription : UnderlyingClients_) {
+ const auto& client = clientDescription->Client;
+ auto clientClusterTag = client->GetConnection()->GetClusterTag();
+ if (clientClusterTag == transactionClusterTag) {
+ return client->AttachTransaction(transactionId, options);
+ }
+ }
+ THROW_ERROR_EXCEPTION("No client is known for transaction %v", transactionId);
+}
+
+void TClient::HandleError(const TErrorOr<void>& error, int clientIndex)
+{
+ if (!NRpc::IsChannelFailureError(error)) {
+ return;
+ }
+
+ UnderlyingClients_[clientIndex]->HasErrors = true;
+ if (ActiveClientIndex_ != clientIndex) {
+ return;
+ }
+
+ auto guard = WriterGuard(Lock_);
+ if (ActiveClientIndex_ != clientIndex) {
+ return;
+ }
+
+ UpdateActiveClient();
+}
+
+void TClient::UpdateActiveClient()
+{
+ VERIFY_WRITER_SPINLOCK_AFFINITY(Lock_);
+
+ int activeClientIndex = ActiveClientIndex_.load();
+
+ for (int index = 0; index < std::ssize(UnderlyingClients_); ++index) {
+ const auto& clientDescription = UnderlyingClients_[index];
+ if (!clientDescription->HasErrors) {
+ if (activeClientIndex != index) {
+ YT_LOG_DEBUG("Active client was changed (PreviousClientIndex: %v, NewClientIndex: %v)",
+ activeClientIndex,
+ index);
+ }
+
+ ActiveClient_ = clientDescription->Client;
+ ActiveClientIndex_ = index;
+ break;
+ }
+ }
+}
+
+TClient::TActiveClientInfo TClient::GetActiveClient()
+{
+ auto guard = ReaderGuard(Lock_);
+ YT_LOG_TRACE("Request will be send to the active client (ClientIndex: %v)",
+ ActiveClientIndex_.load());
+ return {ActiveClient_, ActiveClientIndex_.load()};
+}
+
+DEFINE_REFCOUNTED_TYPE(TClient)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+NApi::IClientPtr CreateClient(const std::vector<NApi::IClientPtr>& clients, TFederationConfigPtr config)
+{
+ return New<TClient>(clients, std::move(config));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/client.h b/yt/yt/client/federated/client.h
new file mode 100644
index 0000000000..4502c43068
--- /dev/null
+++ b/yt/yt/client/federated/client.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <vector>
+
+//! Federated client is a wrapper for several YT-clients with ability
+//! to retry asynchronously the same request with different underlying-clients.
+//! Each YT-client typically corresponds to a different YT-cluster.
+//! In case of errors (for example, cluster unavailability) federated client tries
+//! to retry request via another client (cluster).
+//!
+//! Client in the same datacenter is more prior than other.
+//!
+//! Federated client implements IClient interface, but does not support
+//! the most of mutable methods (except modifications inside transactions).
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Creates federated client with given underlying clients.
+NApi::IClientPtr CreateClient(const std::vector<NApi::IClientPtr>& clients, TFederationConfigPtr config);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/config.cpp b/yt/yt/client/federated/config.cpp
new file mode 100644
index 0000000000..d16649672c
--- /dev/null
+++ b/yt/yt/client/federated/config.cpp
@@ -0,0 +1,35 @@
+#include "config.h"
+
+#include <yt/yt/client/api/rpc_proxy/config.h>
+
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TFederationConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("bundle_name", &TThis::BundleName)
+ .Default();
+
+ registrar.Parameter("cluster_health_check_period", &TThis::ClusterHealthCheckPeriod)
+ .GreaterThan(TDuration::Zero())
+ .Default(TDuration::Seconds(60));
+
+ registrar.Parameter("cluster_retry_attempts", &TThis::ClusterRetryAttempts)
+ .GreaterThanOrEqual(0)
+ .Default(3);
+}
+
+void TConnectionConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("rpc_proxy_connections", &TThis::RpcProxyConnections);
+
+ registrar.Postprocessor([] (TThis* config) {
+ THROW_ERROR_EXCEPTION_IF(config->RpcProxyConnections.empty(),
+ "At least one `rpc_proxy_connections` must be specified");
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/config.h b/yt/yt/client/federated/config.h
new file mode 100644
index 0000000000..c3a67aef58
--- /dev/null
+++ b/yt/yt/client/federated/config.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/rpc_proxy/public.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <vector>
+
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFederationConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ //! Bundle name which liveness should be checked on the background.
+ std::optional<TString> BundleName;
+
+ //! How often cluster liveness should be checked on the background.
+ TDuration ClusterHealthCheckPeriod;
+
+ //! Maximum number of retry attempts to make.
+ int ClusterRetryAttempts;
+
+ REGISTER_YSON_STRUCT(TFederationConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TFederationConfig)
+
+class TConnectionConfig
+ : public TFederationConfig
+{
+public:
+ //! The RPC connection config for participants clusters.
+ std::vector<NApi::NRpcProxy::TConnectionConfigPtr> RpcProxyConnections;
+
+ REGISTER_YSON_STRUCT(TConnectionConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TConnectionConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/connection.cpp b/yt/yt/client/federated/connection.cpp
new file mode 100644
index 0000000000..ec0d6bfdfd
--- /dev/null
+++ b/yt/yt/client/federated/connection.cpp
@@ -0,0 +1,137 @@
+#include "connection.h"
+
+#include "config.h"
+#include "client.h"
+
+#include <yt/yt/client/api/rpc_proxy/config.h>
+
+#include <yt/yt/client/misc/method_helpers.h>
+
+#include <yt/yt/core/concurrency/action_queue.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <library/cpp/yt/string/guid.h>
+#include <library/cpp/yt/string/string_builder.h>
+
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+TString MakeConnectionLoggingTag(const std::vector<NApi::IConnectionPtr>& connections, TGuid connectionId)
+{
+ TStringBuilder builder;
+ builder.AppendString("Clusters: (");
+ TDelimitedStringBuilderWrapper delimitedBuilder(&builder, "; ");
+ for (const auto& connection : connections) {
+ delimitedBuilder->AppendString(connection->GetLoggingTag());
+ }
+ builder.AppendString("), ");
+ builder.AppendFormat("ConnectionId: %v", connectionId);
+ return builder.Flush();
+}
+
+class TConnection
+ : public NApi::IConnection
+{
+public:
+ TConnection(
+ std::vector<NApi::IConnectionPtr> connections,
+ NConcurrency::TActionQueuePtr actionQueue,
+ TFederationConfigPtr config)
+ : Config_(std::move(config))
+ , Connections_(std::move(connections))
+ , ActionQueue_(std::move(actionQueue))
+ , ConnectionId_(TGuid::Create())
+ , LoggingTag_(MakeConnectionLoggingTag(Connections_, ConnectionId_))
+ {
+ YT_VERIFY(!Connections_.empty());
+ }
+
+ const TString& GetLoggingTag() const override
+ {
+ return LoggingTag_;
+ }
+
+ IInvokerPtr GetInvoker() override
+ {
+ return Connections_[0]->GetInvoker();
+ }
+
+ NApi::IClientPtr CreateClient(const NApi::TClientOptions& options = {}) override
+ {
+ std::vector<NApi::IClientPtr> clients;
+ clients.reserve(Connections_.size());
+ for (auto& connection : Connections_) {
+ clients.push_back(connection->CreateClient(options));
+ }
+ return NFederated::CreateClient(clients, Config_);
+ }
+
+ void ClearMetadataCaches() override
+ {
+ // TODO(bulatman) What about exceptions?
+ for (auto& connection : Connections_) {
+ connection->ClearMetadataCaches();
+ }
+ }
+
+ void Terminate() override
+ {
+ // TODO(bulatman) What about exceptions?
+ for (auto& connection : Connections_) {
+ connection->Terminate();
+ }
+ }
+
+ //! Returns a YSON-serialized connection config.
+ NYson::TYsonString GetConfigYson() const override
+ {
+ return NYson::ConvertToYsonString(Config_);
+ }
+
+ UNIMPLEMENTED_CONST_METHOD(NApi::TClusterTag, GetClusterTag, ());
+ UNIMPLEMENTED_CONST_METHOD(const TString&, GetClusterId, ());
+ UNIMPLEMENTED_CONST_METHOD(const std::optional<TString>&, GetClusterName, ());
+ UNIMPLEMENTED_CONST_METHOD(bool, IsSameCluster, (const NApi::IConnectionPtr&));
+ UNIMPLEMENTED_METHOD(
+ NHiveClient::ITransactionParticipantPtr,
+ CreateTransactionParticipant,
+ (NHiveClient::TCellId, const NApi::TTransactionParticipantOptions&));
+
+private:
+ const TFederationConfigPtr Config_;
+ const std::vector<NApi::IConnectionPtr> Connections_;
+ const NConcurrency::TActionQueuePtr ActionQueue_;
+ const TGuid ConnectionId_;
+ const TString LoggingTag_;
+};
+
+} // namespace
+
+NApi::IConnectionPtr CreateConnection(std::vector<NApi::IConnectionPtr> connections, TFederationConfigPtr config)
+{
+ return New<TConnection>(std::move(connections), nullptr, std::move(config));
+}
+
+NApi::IConnectionPtr CreateConnection(TConnectionConfigPtr config, NApi::NRpcProxy::TConnectionOptions options)
+{
+ NConcurrency::TActionQueuePtr actionQueue;
+ if (!options.ConnectionInvoker) {
+ actionQueue = New<NConcurrency::TActionQueue>("FederatedConn");
+ options.ConnectionInvoker = actionQueue->GetInvoker();
+ }
+ std::vector<NApi::IConnectionPtr> connections;
+ connections.reserve(config->RpcProxyConnections.size());
+ for (const auto& rpcProxyConfig : config->RpcProxyConnections) {
+ connections.push_back(NApi::NRpcProxy::CreateConnection(rpcProxyConfig, options));
+ }
+
+ return New<TConnection>(std::move(connections), std::move(actionQueue), std::move(config));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/connection.h b/yt/yt/client/federated/connection.h
new file mode 100644
index 0000000000..ce7b51f5ee
--- /dev/null
+++ b/yt/yt/client/federated/connection.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/rpc_proxy/connection.h>
+
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Creates federated connection with given underlying |connections|.
+//! Federated connection is a wrapper for several connections with ability
+//! to create federated client with clients created by |connections| with the same |TAuthenticationOptions|.
+NApi::IConnectionPtr CreateConnection(
+ std::vector<NApi::IConnectionPtr> connections,
+ TFederationConfigPtr config);
+
+//! Creates federated connection with given |config| and |options|.
+NApi::IConnectionPtr CreateConnection(
+ TConnectionConfigPtr config,
+ NApi::NRpcProxy::TConnectionOptions options);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/private.h b/yt/yt/client/federated/private.h
new file mode 100644
index 0000000000..6762c79ff2
--- /dev/null
+++ b/yt/yt/client/federated/private.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger FederatedClientLogger("FederatedClient");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/public.h b/yt/yt/client/federated/public.h
new file mode 100644
index 0000000000..37b85a1ae4
--- /dev/null
+++ b/yt/yt/client/federated/public.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NClient::NFederated {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TFederationConfig)
+DECLARE_REFCOUNTED_CLASS(TConnectionConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/unittests/client_ut.cpp b/yt/yt/client/federated/unittests/client_ut.cpp
new file mode 100644
index 0000000000..10e14a5b71
--- /dev/null
+++ b/yt/yt/client/federated/unittests/client_ut.cpp
@@ -0,0 +1,480 @@
+#include <yt/yt/client/federated/client.h>
+#include <yt/yt/client/federated/config.h>
+
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/unittests/mock/client.h>
+#include <yt/yt/client/unittests/mock/connection.h>
+#include <yt/yt/client/unittests/mock/transaction.h>
+
+#include <yt/yt/core/net/local_address.h>
+
+#include <util/datetime/base.h>
+
+
+namespace NYT::NClient::NFederated {
+namespace {
+
+using namespace NYT::NApi;
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+using TStrictMockClient = StrictMock<NApi::TMockClient>;
+using TStrictMockConnection = StrictMock<NApi::TMockConnection>;
+using TStrictMockTransaction = StrictMock<NApi::TMockTransaction>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTestDataStorage
+{
+ TTestDataStorage()
+ {
+ LookupResult1 = [&]() {
+ auto rowBuffer = NYT::New<NTableClient::TRowBuffer>();
+ std::vector<NTableClient::TUnversionedRow> rows;
+ for (ui64 key : {10, 11}) {
+ auto row = rowBuffer->AllocateUnversioned(2);
+ row[0] = rowBuffer->CaptureValue(NTableClient::MakeUnversionedUint64Value(key, TableSchema->GetColumnIndex(KeyColumn)));
+ row[1] = rowBuffer->CaptureValue(NTableClient::MakeUnversionedUint64Value(key + 10, TableSchema->GetColumnIndex(ValueColumn)));
+ rows.push_back(NTableClient::TUnversionedRow{row});
+ }
+ return NApi::CreateRowset(TableSchema, MakeSharedRange(rows, std::move(rowBuffer)));
+ } ();
+
+ LookupResult2 = [&]() {
+ auto rowBuffer = NYT::New<NTableClient::TRowBuffer>();
+ std::vector<NTableClient::TUnversionedRow> rows;
+ for (ui64 key : {12, 13}) {
+ auto row = rowBuffer->AllocateUnversioned(2);
+ row[0] = rowBuffer->CaptureValue(NTableClient::MakeUnversionedUint64Value(key, TableSchema->GetColumnIndex(KeyColumn)));
+ row[1] = rowBuffer->CaptureValue(NTableClient::MakeUnversionedUint64Value(key + 10, TableSchema->GetColumnIndex(ValueColumn)));
+ rows.push_back(NTableClient::TUnversionedRow{row});
+ }
+ return NApi::CreateRowset(TableSchema, MakeSharedRange(rows, std::move(rowBuffer)));
+ } ();
+
+ NameTable = NYT::New<NTableClient::TNameTable>();
+
+ i32 keyField = NameTable->GetIdOrRegisterName(KeyColumn);
+ auto keys = [&]() {
+ auto rowBuffer = NYT::New<NTableClient::TRowBuffer>();
+ std::vector<NTableClient::TUnversionedRow> keysVector;
+ for (ui64 key : {10, 11}) {
+ NTableClient::TUnversionedRowBuilder builder;
+ builder.AddValue(NTableClient::MakeUnversionedUint64Value(key, keyField));
+ keysVector.push_back(rowBuffer->CaptureRow(builder.GetRow()));
+ }
+ return NYT::MakeSharedRange(std::move(keysVector), std::move(rowBuffer));
+ } ();
+ }
+
+ const NYPath::TYPath Path = "/test/table";
+ const TString KeyColumn = "key";
+ const TString ValueColumn = "value";
+
+ const NTableClient::TColumnSchema KeyColumnSchema = NTableClient::TColumnSchema(KeyColumn, NTableClient::EValueType::Uint64);
+ const NTableClient::TColumnSchema ValueColumnSchema = NTableClient::TColumnSchema(ValueColumn, NTableClient::EValueType::Uint64);
+ NTableClient::TTableSchemaPtr TableSchema = New<NTableClient::TTableSchema>(std::vector{KeyColumnSchema, ValueColumnSchema});
+
+ IUnversionedRowsetPtr LookupResult1;
+ IUnversionedRowsetPtr LookupResult2;
+ NTableClient::TNameTablePtr NameTable;
+ TSharedRange<NTableClient::TUnversionedRow> Keys;
+};
+
+TEST(TFederatedClientTest, Basic)
+{
+ TTestDataStorage data;
+
+ auto mockClientSas = New<TStrictMockClient>();
+ auto mockClientVla = New<TStrictMockClient>();
+
+ // To identify best (closest) cluster.
+ NYson::TYsonString listResult1(TStringBuf(R"(["a-rpc-proxy-a.sas.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientSas, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult1)));
+
+ NYson::TYsonString listResult2(TStringBuf(R"(["b-rpc-proxy-b.vla.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientVla, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult2)));
+
+ auto finally = Finally([oldLocalHostName = NNet::GetLocalHostName()] {
+ NNet::WriteLocalHostName(oldLocalHostName);
+ });
+ NNet::WriteLocalHostName("a-rpc-proxy.vla.yp-c.yandex.net");
+
+ EXPECT_CALL(*mockClientVla, CheckClusterLiveness(_))
+ .WillOnce(Return(VoidFuture))
+ .WillRepeatedly(Return(MakeFuture(TError("Failure"))));
+
+ EXPECT_CALL(*mockClientSas, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(VoidFuture));
+
+ // Creation of federated client.
+ std::vector<IClientPtr> clients = {mockClientSas, mockClientVla};
+ auto config = New<TFederationConfig>();
+ config->ClusterHealthCheckPeriod = TDuration::Seconds(5);
+ config->ClusterRetryAttempts = 1;
+ auto federatedClient = CreateClient(clients, config);
+
+ // 1. `vla` client should be used as closest cluster.
+ // 2. error from `vla` cluster should be received.
+ // 3. `sas` client should be used as other cluster.
+
+ EXPECT_CALL(*mockClientVla, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult1)))
+ .WillOnce(Return(MakeFuture<IUnversionedRowsetPtr>(TError(NRpc::EErrorCode::Unavailable, "Failure"))));
+
+ EXPECT_CALL(*mockClientSas, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult2)));
+
+ // Wait for the first execution of CheckClustersHealth.
+ Sleep(TDuration::Seconds(2));
+
+ // From `vla`.
+ {
+ auto result = federatedClient->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+ ASSERT_EQ("[0#10u, 1#20u]", actualFirstRow);
+ }
+
+ // Error from `vla`.
+ {
+ auto result = federatedClient->LookupRows(data.Path, data.NameTable, data.Keys);
+ ASSERT_ANY_THROW(result.Get().ValueOrThrow());
+ }
+
+ // From `sas`.
+ {
+ auto result = federatedClient->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+
+ ASSERT_EQ("[0#12u, 1#22u]", actualFirstRow);
+ }
+}
+
+TEST(TFederatedClientTest, CheckHealth)
+{
+ TTestDataStorage data;
+
+ auto mockClientSas = New<TStrictMockClient>();
+ auto mockClientVla = New<TStrictMockClient>();
+
+ // To identify best (closest) cluster.
+ NYson::TYsonString listResult1(TStringBuf(R"(["a-rpc-proxy-a.sas.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientSas, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult1)));
+
+ NYson::TYsonString listResult2(TStringBuf(R"(["b-rpc-proxy-b.vla.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientVla, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult2)));
+
+ auto finally = Finally([oldLocalHostName = NNet::GetLocalHostName()] {
+ NNet::WriteLocalHostName(oldLocalHostName);
+ });
+ NNet::WriteLocalHostName("a-rpc-proxy.vla.yp-c.yandex.net");
+
+ std::vector<IClientPtr> clients = {mockClientSas, mockClientVla};
+ auto config = New<TFederationConfig>();
+ config->ClusterHealthCheckPeriod = TDuration::Seconds(5);
+ config->ClusterRetryAttempts = 1;
+ config->BundleName = "my_bundle";
+
+ TCheckClusterLivenessOptions checkLivenessOptions;
+ checkLivenessOptions.CheckCypressRoot = true;
+ checkLivenessOptions.CheckTabletCellBundle = config->BundleName;
+ EXPECT_CALL(*mockClientVla, CheckClusterLiveness(checkLivenessOptions))
+ .WillOnce(Return(VoidFuture))
+ .WillOnce(Return(MakeFuture(TError("Failure"))))
+ .WillOnce(Return(VoidFuture));
+
+ EXPECT_CALL(*mockClientSas, CheckClusterLiveness(checkLivenessOptions))
+ .WillRepeatedly(Return(VoidFuture));
+
+ auto federatedClient = CreateClient(clients, config);
+
+ EXPECT_CALL(*mockClientVla, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult1)))
+ .WillOnce(Return(MakeFuture(data.LookupResult1)));
+
+ EXPECT_CALL(*mockClientSas, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult2)));
+
+ // From `vla`.
+ {
+ auto result = federatedClient->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+ ASSERT_EQ("[0#10u, 1#20u]", actualFirstRow);
+ }
+
+ // Wait for the check of cluster liveness.
+ Sleep(TDuration::Seconds(6));
+
+ // From `sas` because `vla` was marked as unhealthy after CheckClustersHealth.
+ {
+ auto result = federatedClient->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+
+ ASSERT_EQ("[0#12u, 1#22u]", actualFirstRow);
+ }
+
+ // Wait for the next check of cluster liveness, `vla` cluster will become current again.
+ Sleep(TDuration::Seconds(5));
+
+ // From `vla` because it became ok again.
+ {
+ auto result = federatedClient->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+ ASSERT_EQ("[0#10u, 1#20u]", actualFirstRow);
+ }
+}
+
+TEST(TFederatedClientTest, Transactions)
+{
+ TTestDataStorage data;
+
+ auto mockClientSas = New<TStrictMockClient>();
+ auto mockClientVla = New<TStrictMockClient>();
+
+ // To identify best (closest) cluster.
+ NYson::TYsonString listResult1(TStringBuf(R"(["a-rpc-proxy-a.sas.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientSas, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult1)));
+
+ NYson::TYsonString listResult2(TStringBuf(R"(["b-rpc-proxy-b.vla.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientVla, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult2)));
+
+ auto finally = Finally([oldLocalHostName = NNet::GetLocalHostName()] {
+ NNet::WriteLocalHostName(oldLocalHostName);
+ });
+ NNet::WriteLocalHostName("a-rpc-proxy.vla.yp-c.yandex.net");
+
+ EXPECT_CALL(*mockClientVla, CheckClusterLiveness(_))
+ .WillOnce(Return(VoidFuture))
+ .WillRepeatedly(Return(MakeFuture(TError("Failure"))));
+
+ EXPECT_CALL(*mockClientSas, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(VoidFuture));
+
+ // Creation of federated client.
+ std::vector<IClientPtr> clients = {mockClientSas, mockClientVla};
+ auto config = New<TFederationConfig>();
+ config->ClusterHealthCheckPeriod = TDuration::Seconds(5);
+ config->ClusterRetryAttempts = 1;
+ auto federatedClient = CreateClient(clients, config);
+
+ auto mockTransactionVla = New<TStrictMockTransaction>();
+
+ EXPECT_CALL(*mockClientVla, StartTransaction(_, _))
+ .WillOnce(Return(MakeFuture(NApi::ITransactionPtr{mockTransactionVla})));
+
+ EXPECT_CALL(*mockTransactionVla, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult1)))
+ .WillOnce(Return(MakeFuture<IUnversionedRowsetPtr>(TError(NRpc::EErrorCode::Unavailable, "Failure"))));
+
+ // Wait for the first check of clusters healths.
+ Sleep(TDuration::Seconds(2));
+
+ auto transaction = federatedClient->StartTransaction(NTransactionClient::ETransactionType::Tablet).Get().Value();
+
+ // From `vla`.
+ {
+ auto result = transaction->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+ ASSERT_EQ("[0#10u, 1#20u]", actualFirstRow);
+ }
+
+ // Error from `vla`.
+ {
+ auto result = transaction->LookupRows(data.Path, data.NameTable, data.Keys);
+ ASSERT_ANY_THROW(result.Get().ValueOrThrow());
+ }
+
+ auto mockTransactionSas = New<TStrictMockTransaction>();
+ EXPECT_CALL(*mockClientSas, StartTransaction(_, _))
+ .WillOnce(Return(MakeFuture(NApi::ITransactionPtr{mockTransactionSas})));
+
+ EXPECT_CALL(*mockTransactionSas, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult2)));
+
+ // Creating next transaction in `sas`.
+ {
+ transaction = federatedClient->StartTransaction(NTransactionClient::ETransactionType::Tablet).Get().Value();
+
+ auto result = transaction->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+
+ ASSERT_EQ("[0#12u, 1#22u]", actualFirstRow);
+ }
+}
+
+TEST(TFederatedClientTest, RetryWithoutTransaction)
+{
+ TTestDataStorage data;
+
+ auto mockClientSas = New<TStrictMockClient>();
+ auto mockClientVla = New<TStrictMockClient>();
+
+ // To identify best (closest) cluster.
+ NYson::TYsonString listResult1(TStringBuf(R"(["a-rpc-proxy-a.sas.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientSas, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult1)));
+
+ NYson::TYsonString listResult2(TStringBuf(R"(["b-rpc-proxy-b.vla.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientVla, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult2)));
+
+ auto finally = Finally([oldLocalHostName = NNet::GetLocalHostName()] {
+ NNet::WriteLocalHostName(oldLocalHostName);
+ });
+ NNet::WriteLocalHostName("a-rpc-proxy.vla.yp-c.yandex.net");
+
+ EXPECT_CALL(*mockClientVla, CheckClusterLiveness(_))
+ .WillOnce(Return(VoidFuture))
+ .WillOnce(Return(VoidFuture))
+ .WillRepeatedly(Return(MakeFuture(TError("Failure"))));
+
+ EXPECT_CALL(*mockClientSas, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(VoidFuture));
+
+ // Creation of federated client.
+ std::vector<IClientPtr> clients = {mockClientSas, mockClientVla};
+ auto config = New<TFederationConfig>();
+ config->ClusterHealthCheckPeriod = TDuration::Seconds(5);
+ auto federatedClient = CreateClient(clients, config);
+
+ // 1. `vla` client should be used as closest cluster.
+ // 2. error from `vla` cluster should be received.
+ // 3. `sas` client should be used as other cluster.
+
+ EXPECT_CALL(*mockClientVla, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture<IUnversionedRowsetPtr>(TError(NRpc::EErrorCode::Unavailable, "Failure"))));
+
+ EXPECT_CALL(*mockClientSas, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult2)));
+
+ // Wait for the first execution of CheckClustersHealth.
+ Sleep(TDuration::Seconds(2));
+
+ // Go to `vla`, getting error, retry via `sas` and getting response from `sas`.
+ {
+ auto result = federatedClient->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+
+ ASSERT_EQ("[0#12u, 1#22u]", actualFirstRow);
+ }
+
+ // Wait for the next check, `vla` is current cluster again.
+ Sleep(TDuration::Seconds(5));
+
+ auto mockTransactionVla = New<TStrictMockTransaction>();
+ EXPECT_CALL(*mockClientVla, StartTransaction(_, _))
+ .WillOnce(Return(MakeFuture<NApi::ITransactionPtr>(TError(NRpc::EErrorCode::Unavailable, "Failure"))));
+
+ auto mockTransactionSas = New<TStrictMockTransaction>();
+ EXPECT_CALL(*mockClientSas, StartTransaction(_, _))
+ .WillOnce(Return(MakeFuture(NApi::ITransactionPtr{mockTransactionSas})));
+ EXPECT_CALL(*mockTransactionSas, LookupRows(data.Path, _, _, _))
+ .WillOnce(Return(MakeFuture(data.LookupResult2)));
+
+
+ // Try to start transaction in `vla`, getting error, retry via `sas`, creating transaction and getting response from `sas`.
+ {
+ auto transaction = federatedClient->StartTransaction(NTransactionClient::ETransactionType::Tablet).Get().Value();
+
+ auto result = transaction->LookupRows(data.Path, data.NameTable, data.Keys);
+ auto rows = result.Get().Value()->GetRows();
+
+ ASSERT_EQ(2u, rows.Size());
+ auto actualFirstRow = ToString(rows[0]);
+
+ ASSERT_EQ("[0#12u, 1#22u]", actualFirstRow);
+ }
+}
+
+TEST(TFederatedClientTest, AttachTransaction)
+{
+ TTestDataStorage data;
+
+ auto mockClientSas = New<TStrictMockClient>();
+ auto mockClientVla = New<TStrictMockClient>();
+
+ // To identify best (closest) cluster.
+ NYson::TYsonString listResult1(TStringBuf(R"(["a-rpc-proxy-a.sas.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientSas, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult1)));
+
+ NYson::TYsonString listResult2(TStringBuf(R"(["b-rpc-proxy-b.vla.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientVla, ListNode("//sys/rpc_proxies", _))
+ .WillOnce(Return(MakeFuture(listResult2)));
+
+ auto finally = Finally([oldLocalHostName = NNet::GetLocalHostName()] {
+ NNet::WriteLocalHostName(oldLocalHostName);
+ });
+ NNet::WriteLocalHostName("b-rpc-proxy.vla.yp-c.yandex.net");
+
+ EXPECT_CALL(*mockClientVla, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(MakeFuture(TError("Failure"))));
+
+ EXPECT_CALL(*mockClientSas, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(VoidFuture));
+
+ auto mockConnectionSas = New<TStrictMockConnection>();
+ EXPECT_CALL(*mockConnectionSas, GetClusterTag())
+ .WillRepeatedly(Return(NObjectClient::TCellTag(123)));
+ EXPECT_CALL(*mockClientSas, GetConnection())
+ .WillOnce(Return(mockConnectionSas));
+
+ auto mockConnectionVla = New<TStrictMockConnection>();
+ EXPECT_CALL(*mockConnectionVla, GetClusterTag())
+ .WillRepeatedly(Return(NObjectClient::TCellTag(456)));
+ EXPECT_CALL(*mockClientVla, GetConnection())
+ .WillOnce(Return(mockConnectionVla));
+
+ // Creation of federated client.
+ std::vector<IClientPtr> clients = {mockClientSas, mockClientVla};
+ auto config = New<TFederationConfig>();
+ config->ClusterHealthCheckPeriod = TDuration::Seconds(5);
+ auto federatedClient = CreateClient(clients, config);
+
+ auto mockTransactionSas = New<TStrictMockTransaction>();
+ auto transactionId = TGuid(0, 123 << 16, 0, 0);
+ EXPECT_CALL(*mockTransactionSas, GetId())
+ .WillRepeatedly(Return(transactionId));
+
+ EXPECT_CALL(*mockClientSas, AttachTransaction(transactionId, _))
+ .WillOnce(Return(mockTransactionSas));
+
+ auto transaction = federatedClient->AttachTransaction(transactionId);
+ ASSERT_EQ(transaction->GetId(), transactionId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/unittests/connection_ut.cpp b/yt/yt/client/federated/unittests/connection_ut.cpp
new file mode 100644
index 0000000000..7f7473d019
--- /dev/null
+++ b/yt/yt/client/federated/unittests/connection_ut.cpp
@@ -0,0 +1,75 @@
+#include <yt/yt/client/federated/connection.h>
+#include <yt/yt/client/federated/config.h>
+
+#include <yt/yt/client/unittests/mock/client.h>
+#include <yt/yt/client/unittests/mock/connection.h>
+
+#include <yt/yt/core/net/local_address.h>
+
+namespace NYT::NClient::NFederated {
+namespace {
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::ReturnRefOfCopy;
+using ::testing::StrictMock;
+
+using TStrictMockClient = StrictMock<NApi::TMockClient>;
+using TStrictMockConnection = StrictMock<NApi::TMockConnection>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TFederatedConnectionTest, CreateClient)
+{
+ auto config = New<TFederationConfig>();
+ config->BundleName = "my_bundle";
+
+ auto mockConnectionSas = New<TStrictMockConnection>();
+ auto mockConnectionVla = New<TStrictMockConnection>();
+ auto mockClientSas = New<TStrictMockClient>();
+ auto mockClientVla = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClientVla, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(VoidFuture));
+
+ // To identify best (closest) cluster.
+ NYson::TYsonString nodesYsonSas(TStringBuf(R"(["a-rpc-proxy-a.sas.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientSas, ListNode("//sys/rpc_proxies", _))
+ .WillRepeatedly(Return(MakeFuture(nodesYsonSas)));
+
+ NYson::TYsonString nodesYsonVla(TStringBuf(R"(["a-rpc-proxy-a.vla.yp-c.yandex.net:9013"])"));
+ EXPECT_CALL(*mockClientVla, ListNode("//sys/rpc_proxies", _))
+ .WillRepeatedly(Return(MakeFuture(nodesYsonVla)));
+
+ EXPECT_CALL(*mockClientSas, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(VoidFuture));
+ EXPECT_CALL(*mockClientVla, CheckClusterLiveness(_))
+ .WillRepeatedly(Return(VoidFuture));
+
+ NApi::TClientOptions clientOptions;
+ EXPECT_CALL(*mockConnectionSas, CreateClient(::testing::Ref(clientOptions)))
+ .WillOnce(Return(mockClientSas));
+ EXPECT_CALL(*mockConnectionVla, CreateClient(::testing::Ref(clientOptions)))
+ .WillOnce(Return(mockClientVla));
+
+ EXPECT_CALL(*mockConnectionSas, GetLoggingTag())
+ .WillOnce(ReturnRefOfCopy(TString("sas")));
+ EXPECT_CALL(*mockConnectionVla, GetLoggingTag())
+ .WillOnce(ReturnRefOfCopy(TString("vla")));
+
+ auto finally = Finally([oldLocalHostName = NNet::GetLocalHostName()] {
+ NNet::WriteLocalHostName(oldLocalHostName);
+ });
+ NNet::WriteLocalHostName("a-rpc-proxy.sas.yp-c.yandex.net");
+
+ auto connection = CreateConnection({mockConnectionSas, mockConnectionVla}, config);
+ EXPECT_THAT(connection->GetLoggingTag(), testing::HasSubstr("Clusters: (sas; vla)"));
+ auto client = connection->CreateClient(clientOptions);
+ auto nodes = client->ListNode("//sys/rpc_proxies").Get().ValueOrThrow();
+ EXPECT_EQ(nodesYsonSas, nodes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NClient::NFederated
diff --git a/yt/yt/client/federated/unittests/ya.make b/yt/yt/client/federated/unittests/ya.make
new file mode 100644
index 0000000000..5cb97978d0
--- /dev/null
+++ b/yt/yt/client/federated/unittests/ya.make
@@ -0,0 +1,21 @@
+GTEST(unittester-federated-client)
+
+SRCS(
+ client_ut.cpp
+ connection_ut.cpp
+)
+
+PEERDIR(
+ library/cpp/iterator
+ library/cpp/testing/common
+ library/cpp/testing/hook
+
+ yt/yt/library/profiling/solomon
+
+ yt/yt/core
+
+ yt/yt/client/federated
+ yt/yt/client/unittests/mock
+)
+
+END()
diff --git a/yt/yt/client/federated/ya.make b/yt/yt/client/federated/ya.make
new file mode 100644
index 0000000000..efea22071c
--- /dev/null
+++ b/yt/yt/client/federated/ya.make
@@ -0,0 +1,17 @@
+LIBRARY()
+
+SRCS(
+ client.cpp
+ config.cpp
+ connection.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/string
+ yt/yt/core
+ yt/yt/client
+)
+
+END()
+
+RECURSE_FOR_TESTS(unittests)
diff --git a/yt/yt/client/file_client/config.h b/yt/yt/client/file_client/config.h
new file mode 100644
index 0000000000..03821e4966
--- /dev/null
+++ b/yt/yt/client/file_client/config.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/chunk_client/config.h>
+
+#include <yt/yt/core/ytree/yson_serializable.h>
+
+namespace NYT::NFileClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFileChunkWriterConfig
+ : public virtual NChunkClient::TEncodingWriterConfig
+{
+public:
+ i64 BlockSize;
+
+ REGISTER_YSON_STRUCT(TFileChunkWriterConfig);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.Parameter("block_size", &TThis::BlockSize)
+ .Default(16_MB)
+ .GreaterThan(0);
+ }
+};
+
+DEFINE_REFCOUNTED_TYPE(TFileChunkWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFileClient
diff --git a/yt/yt/client/file_client/public.h b/yt/yt/client/file_client/public.h
new file mode 100644
index 0000000000..f999e52908
--- /dev/null
+++ b/yt/yt/client/file_client/public.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NFileClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TFileChunkWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFileClient
diff --git a/yt/yt/client/formats/config.cpp b/yt/yt/client/formats/config.cpp
new file mode 100644
index 0000000000..f6c6a8c8a6
--- /dev/null
+++ b/yt/yt/client/formats/config.cpp
@@ -0,0 +1,355 @@
+#include "config.h"
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TControlAttributesConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_key_switch", &TThis::EnableKeySwitch)
+ .Default(false);
+
+ registrar.Parameter("enable_end_of_stream", &TThis::EnableEndOfStream)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TYsonFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("format", &TThis::Format)
+ .Default(NYson::EYsonFormat::Binary);
+ registrar.Parameter("complex_type_mode", &TThis::ComplexTypeMode)
+ .Default(EComplexTypeMode::Named);
+ registrar.Parameter("string_keyed_dict_mode", &TThis::StringKeyedDictMode)
+ .Default(EDictMode::Positional);
+ registrar.Parameter("decimal_mode", &TThis::DecimalMode)
+ .Default(EDecimalMode::Binary);
+ registrar.Parameter("time_mode", &TThis::TimeMode)
+ .Default(ETimeMode::Binary);
+ registrar.Parameter("uuid_mode", &TThis::UuidMode)
+ .Default(EUuidMode::Binary);
+ registrar.Parameter("skip_null_values", &TThis::SkipNullValues)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TYamrFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.BaseClassParameter("has_subkey", &TThis::HasSubkey)
+ .Default(false);
+ registrar.Parameter("key", &TThis::Key)
+ .Default("key");
+ registrar.Parameter("subkey", &TThis::Subkey)
+ .Default("subkey");
+ registrar.Parameter("value", &TThis::Value)
+ .Default("value");
+ registrar.BaseClassParameter("lenval", &TThis::Lenval)
+ .Default(false);
+ registrar.BaseClassParameter("fs", &TThis::FieldSeparator)
+ .Default('\t');
+ registrar.BaseClassParameter("rs", &TThis::RecordSeparator)
+ .Default('\n');
+ registrar.BaseClassParameter("enable_table_index", &TThis::EnableTableIndex)
+ .Default(false);
+ registrar.BaseClassParameter("enable_escaping", &TThis::EnableEscaping)
+ .Default(false);
+ registrar.BaseClassParameter("escaping_symbol", &TThis::EscapingSymbol)
+ .Default('\\');
+ registrar.BaseClassParameter("enable_eom", &TThis::EnableEom)
+ .Default(false);
+
+ registrar.Preprocessor([] (TThis* config) {
+ if (config->EnableEom && !config->Lenval) {
+ THROW_ERROR_EXCEPTION("EOM marker is not supported in YAMR text mode");
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TDsvFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.BaseClassParameter("record_separator", &TThis::RecordSeparator)
+ .Default('\n');
+ registrar.BaseClassParameter("key_value_separator", &TThis::KeyValueSeparator)
+ .Default('=');
+ registrar.BaseClassParameter("field_separator", &TThis::FieldSeparator)
+ .Default('\t');
+ registrar.BaseClassParameter("line_prefix", &TThis::LinePrefix)
+ .Default();
+ registrar.BaseClassParameter("enable_escaping", &TThis::EnableEscaping)
+ .Default(true);
+ registrar.BaseClassParameter("escaping_symbol", &TThis::EscapingSymbol)
+ .Default('\\');
+ registrar.BaseClassParameter("enable_table_index", &TThis::EnableTableIndex)
+ .Default(false);
+ registrar.Parameter("table_index_column", &TThis::TableIndexColumn)
+ .Default("@table_index")
+ .NonEmpty();
+ registrar.Parameter("skip_unsupported_types", &TThis::SkipUnsupportedTypes)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TYamredDsvFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.BaseClassParameter("record_separator", &TThis::RecordSeparator)
+ .Default('\n');
+ registrar.BaseClassParameter("key_value_separator", &TThis::KeyValueSeparator)
+ .Default('=');
+ registrar.BaseClassParameter("field_separator", &TThis::FieldSeparator)
+ .Default('\t');
+ registrar.BaseClassParameter("line_prefix", &TThis::LinePrefix)
+ .Default();
+ registrar.BaseClassParameter("enable_escaping", &TThis::EnableEscaping)
+ .Default(true);
+ registrar.BaseClassParameter("escaping_symbol", &TThis::EscapingSymbol)
+ .Default('\\');
+ registrar.BaseClassParameter("enable_table_index", &TThis::EnableTableIndex)
+ .Default(false);
+ registrar.BaseClassParameter("has_subkey", &TThis::HasSubkey)
+ .Default(false);
+ registrar.BaseClassParameter("lenval", &TThis::Lenval)
+ .Default(false);
+ registrar.Parameter("key_column_names", &TThis::KeyColumnNames);
+ registrar.Parameter("subkey_column_names", &TThis::SubkeyColumnNames)
+ .Default();
+ registrar.Parameter("yamr_keys_separator", &TThis::YamrKeysSeparator)
+ .Default(' ');
+ registrar.BaseClassParameter("enable_eom", &TThis::EnableEom)
+ .Default(false);
+ registrar.Parameter("skip_unsupported_types_in_value", &TThis::SkipUnsupportedTypesInValue)
+ .Default(false);
+
+ registrar.Preprocessor([] (TThis* config) {
+ if (config->EnableEom && !config->Lenval) {
+ THROW_ERROR_EXCEPTION("EOM marker is not supported in YAMR text mode");
+ }
+ });
+
+ registrar.Postprocessor([] (TThis* config) {
+ THashSet<TString> names;
+
+ for (const auto& name : config->KeyColumnNames) {
+ if (!names.insert(name).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column %Qv found in \"key_column_names\"",
+ name);
+ }
+ }
+
+ for (const auto& name : config->SubkeyColumnNames) {
+ if (!names.insert(name).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column %Qv found in \"subkey_column_names\"",
+ name);
+ }
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const std::vector<TString>& TSchemafulDsvFormatConfig::GetColumnsOrThrow() const
+{
+ if (!Columns) {
+ THROW_ERROR_EXCEPTION("Missing \"columns\" attribute in schemaful DSV format");
+ }
+ return *Columns;
+}
+
+void TSchemafulDsvFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.BaseClassParameter("record_separator", &TThis::RecordSeparator)
+ .Default('\n');
+ registrar.BaseClassParameter("field_separator", &TThis::FieldSeparator)
+ .Default('\t');
+
+ registrar.BaseClassParameter("enable_table_index", &TThis::EnableTableIndex)
+ .Default(false);
+
+ registrar.BaseClassParameter("enable_escaping", &TThis::EnableEscaping)
+ .Default(true);
+ registrar.BaseClassParameter("escaping_symbol", &TThis::EscapingSymbol)
+ .Default('\\');
+
+ registrar.Parameter("columns", &TThis::Columns)
+ .Default();
+
+ registrar.Parameter("missing_value_mode", &TThis::MissingValueMode)
+ .Default(EMissingSchemafulDsvValueMode::Fail);
+
+ registrar.Parameter("missing_value_sentinel", &TThis::MissingValueSentinel)
+ .Default("");
+
+ registrar.Parameter("enable_column_names_header", &TThis::EnableColumnNamesHeader)
+ .Default();
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->Columns) {
+ THashSet<TString> names;
+ for (const auto& name : *config->Columns) {
+ if (!names.insert(name).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column name %Qv in schemaful DSV configuration",
+ name);
+ }
+ }
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TProtobufTypeConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("proto_type", &TThis::ProtoType);
+ registrar.Parameter("fields", &TThis::Fields)
+ .Default();
+ registrar.Parameter("enumeration_name", &TThis::EnumerationName)
+ .Default();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TProtobufColumnConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("name", &TThis::Name)
+ .NonEmpty();
+ registrar.Parameter("field_number", &TThis::FieldNumber)
+ .Optional();
+ registrar.Parameter("repeated", &TThis::Repeated)
+ .Default(false);
+ registrar.Parameter("packed", &TThis::Packed)
+ .Default(false);
+
+ registrar.Parameter("type", &TThis::Type)
+ .Default();
+
+ registrar.Parameter("proto_type", &TThis::ProtoType)
+ .Default();
+ registrar.Parameter("fields", &TThis::Fields)
+ .Default();
+ registrar.Parameter("enumeration_name", &TThis::EnumerationName)
+ .Default();
+
+ registrar.Parameter("enum_writing_mode", &TThis::EnumWritingMode)
+ .Default(EProtobufEnumWritingMode::CheckValues);
+
+ registrar.Postprocessor([] (TThis* config) {
+ config->CustomPostprocess();
+ });
+}
+
+void TProtobufColumnConfig::CustomPostprocess()
+{
+ if (Packed && !Repeated) {
+ THROW_ERROR_EXCEPTION("Field %Qv is marked \"packed\" but is not marked \"repeated\"",
+ Name);
+ }
+
+ if (!Type) {
+ Type = New<TProtobufTypeConfig>();
+ if (!ProtoType) {
+ THROW_ERROR_EXCEPTION("One of \"type\" and \"proto_type\" must be specified");
+ }
+ Type->ProtoType = *ProtoType;
+ Type->Fields = std::move(Fields);
+ Type->EnumerationName = EnumerationName;
+ }
+
+ if (!FieldNumber && Type->ProtoType != EProtobufType::Oneof) {
+ THROW_ERROR_EXCEPTION("\"field_number\" is required for type %Qlv",
+ Type->ProtoType);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TProtobufTableConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("columns", &TThis::Columns);
+
+ registrar.Postprocessor([] (TThis* config) {
+ bool hasOtherColumns = false;
+ for (const auto& column: config->Columns) {
+ if (column->ProtoType == EProtobufType::OtherColumns) {
+ if (hasOtherColumns) {
+ THROW_ERROR_EXCEPTION("Multiple \"other_columns\" in protobuf config are not allowed");
+ }
+ hasOtherColumns = true;
+ }
+ }
+ });
+}
+
+void TProtobufFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("file_descriptor_set", &TThis::FileDescriptorSet)
+ .Default();
+ registrar.Parameter("file_indices", &TThis::FileIndices)
+ .Default();
+ registrar.Parameter("message_indices", &TThis::MessageIndices)
+ .Default();
+ registrar.Parameter("nested_messages_mode", &TThis::NestedMessagesMode)
+ .Default(ENestedMessagesMode::Protobuf);
+ registrar.Parameter("enums_as_strings", &TThis::EnumsAsStrings)
+ .Default();
+
+ registrar.Parameter("tables", &TThis::Tables)
+ .Default();
+ registrar.Parameter("enumerations", &TThis::Enumerations)
+ .Default();
+
+ registrar.Parameter("file_descriptor_set_text", &TThis::FileDescriptorSetText)
+ .Default();
+ registrar.Parameter("type_names", &TThis::TypeNames)
+ .Default();
+
+ registrar.Parameter("complex_type_mode", &TThis::ComplexTypeMode)
+ .Default(EComplexTypeMode::Named);
+ registrar.Parameter("decimal_mode", &TThis::DecimalMode)
+ .Default(EDecimalMode::Binary);
+ registrar.Parameter("time_mode", &TThis::TimeMode)
+ .Default(ETimeMode::Binary);
+ registrar.Parameter("uuid_mode", &TThis::UuidMode)
+ .Default(EUuidMode::Binary);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TWebJsonFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_selected_column_count", &TThis::MaxSelectedColumnCount)
+ .Default(50)
+ .GreaterThanOrEqual(0);
+ registrar.Parameter("field_weight_limit", &TThis::FieldWeightLimit)
+ .Default(1_KB)
+ .GreaterThanOrEqual(0);
+ registrar.Parameter("string_weight_limit", &TThis::StringWeightLimit)
+ .Default(200)
+ .GreaterThanOrEqual(0);
+ registrar.Parameter("max_all_column_names_count", &TThis::MaxAllColumnNamesCount)
+ .Default(2000)
+ .GreaterThanOrEqual(0);
+ registrar.Parameter("column_names", &TThis::ColumnNames)
+ .Default();
+ registrar.Parameter("value_format", &TThis::ValueFormat)
+ .Default(EWebJsonValueFormat::Schemaless);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TSkiffFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("skiff_schema_registry", &TThis::SkiffSchemaRegistry)
+ .Default();
+ registrar.Parameter("table_skiff_schemas", &TThis::TableSkiffSchemas);
+
+ registrar.Parameter("override_intermediate_table_schema", &TThis::OverrideIntermediateTableSchema)
+ .Default();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/config.h b/yt/yt/client/formats/config.h
new file mode 100644
index 0000000000..a371050ae6
--- /dev/null
+++ b/yt/yt/client/formats/config.h
@@ -0,0 +1,419 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/config.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TControlAttributesConfig
+ : public NTableClient::TChunkReaderOptions
+{
+public:
+ bool EnableKeySwitch;
+
+ bool EnableEndOfStream;
+
+ REGISTER_YSON_STRUCT(TControlAttributesConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TControlAttributesConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYsonFormatConfig
+ : public NTableClient::TTypeConversionConfig
+{
+public:
+ NYson::EYsonFormat Format;
+ EComplexTypeMode ComplexTypeMode;
+ EDictMode StringKeyedDictMode;
+ EDecimalMode DecimalMode;
+ ETimeMode TimeMode;
+ EUuidMode UuidMode;
+
+ //! Only works for tabular data.
+ bool SkipNullValues;
+
+ REGISTER_YSON_STRUCT(TYsonFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TYsonFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+// Readers for Yamr and Dsv share lots of methods and functionality //
+// and dependency diagram has the following shape: //
+// //
+// TTableFormatConfigBase --------------------------. //
+// / \ \ //
+// / \ \ //
+// TYamrFormatConfigBase TDsvFormatConfigBase \ //
+// / \ / \ \ //
+// / \ / \ \ //
+// TYamrFormatConfig TYamredDsvFormatConfig TDsvFormatConfig TSchemafulDsvFormatConfig //
+// //
+// All fields are declared in Base classes, all parameters are //
+// registered in derived classes. //
+
+class TTableFormatConfigBase
+ : public NTableClient::TTypeConversionConfig
+{
+public:
+ char RecordSeparator;
+ char FieldSeparator;
+
+ // Escaping rules (EscapingSymbol is '\\')
+ // * '\0' ---> "\0"
+ // * '\n' ---> "\n"
+ // * '\t' ---> "\t"
+ // * 'X' ---> "\X" if X not in ['\0', '\n', '\t']
+ bool EnableEscaping;
+ char EscapingSymbol;
+
+ bool EnableTableIndex;
+
+ REGISTER_YSON_STRUCT(TTableFormatConfigBase);
+
+ static void Register(TRegistrar )
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableFormatConfigBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYamrFormatConfigBase
+ : public virtual TTableFormatConfigBase
+{
+public:
+ bool HasSubkey;
+ bool Lenval;
+ bool EnableEom;
+
+ REGISTER_YSON_STRUCT(TYamrFormatConfigBase);
+
+ static void Register(TRegistrar )
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TYamrFormatConfigBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDsvFormatConfigBase
+ : public virtual TTableFormatConfigBase
+{
+public:
+ char KeyValueSeparator;
+
+ // Only supported for tabular data
+ std::optional<TString> LinePrefix;
+
+ REGISTER_YSON_STRUCT(TDsvFormatConfigBase);
+
+ static void Register(TRegistrar )
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TDsvFormatConfigBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYamrFormatConfig
+ : public TYamrFormatConfigBase
+{
+public:
+ TString Key;
+ TString Subkey;
+ TString Value;
+
+ REGISTER_YSON_STRUCT(TYamrFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TYamrFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDsvFormatConfig
+ : public TDsvFormatConfigBase
+{
+public:
+
+ TString TableIndexColumn;
+ bool SkipUnsupportedTypes = false;
+
+ REGISTER_YSON_STRUCT(TDsvFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TDsvFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYamredDsvFormatConfig
+ : public TYamrFormatConfigBase
+ , public TDsvFormatConfigBase
+{
+public:
+ char YamrKeysSeparator;
+
+ std::vector<TString> KeyColumnNames;
+ std::vector<TString> SubkeyColumnNames;
+
+ bool SkipUnsupportedTypesInValue = false;
+
+ REGISTER_YSON_STRUCT(TYamredDsvFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TYamredDsvFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EMissingSchemafulDsvValueMode,
+ (SkipRow)
+ (Fail)
+ (PrintSentinel)
+);
+
+class TSchemafulDsvFormatConfig
+ : public TTableFormatConfigBase
+{
+public:
+ std::optional<std::vector<TString>> Columns;
+
+ EMissingSchemafulDsvValueMode MissingValueMode;
+ TString MissingValueSentinel;
+
+ std::optional<bool> EnableColumnNamesHeader;
+
+ const std::vector<TString>& GetColumnsOrThrow() const;
+
+ REGISTER_YSON_STRUCT(TSchemafulDsvFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TSchemafulDsvFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EProtobufType,
+ (Double)
+ (Float)
+
+ (Int64)
+ (Uint64)
+ (Sint64)
+ (Fixed64)
+ (Sfixed64)
+
+ (Int32)
+ (Uint32)
+ (Sint32)
+ (Fixed32)
+ (Sfixed32)
+
+ (Bool)
+ (String)
+ (Bytes)
+
+ (EnumInt)
+ (EnumString)
+
+ // Same as 'bytes'.
+ (Message)
+
+ // Protobuf type must be message.
+ // It corresponds to struct type.
+ (StructuredMessage)
+
+ // Protobuf type must be message.
+ // It corresponds to a set of table columns.
+ (EmbeddedMessage)
+
+ // Corresponds to variant struct type.
+ (Oneof)
+
+ // Protobuf type must be string.
+ // Maps to any scalar type (not necessarily "any" type) in table row.
+ (Any)
+
+ // Protobuf type must be string containing valid YSON map.
+ // Each entry (|key|, |value|) of this map will correspond
+ // a separate |TUnversionedValue| under name |key|.
+ // NOTE: Not allowed inside complex types.
+ (OtherColumns)
+);
+
+DEFINE_ENUM(EProtobufEnumWritingMode,
+ (CheckValues)
+ (SkipUnknownValues)
+);
+
+class TProtobufTypeConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ EProtobufType ProtoType;
+ std::vector<TProtobufColumnConfigPtr> Fields;
+ std::optional<TString> EnumerationName;
+
+ REGISTER_YSON_STRUCT(TProtobufTypeConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufTypeConfig)
+
+class TProtobufColumnConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ TString Name;
+ std::optional<ui64> FieldNumber;
+ bool Repeated;
+ bool Packed;
+
+ TProtobufTypeConfigPtr Type;
+
+ std::optional<EProtobufType> ProtoType;
+ std::vector<TProtobufColumnConfigPtr> Fields;
+ std::optional<TString> EnumerationName;
+ EProtobufEnumWritingMode EnumWritingMode;
+
+ REGISTER_YSON_STRUCT(TProtobufColumnConfig);
+
+ static void Register(TRegistrar registrar);
+public:
+ void CustomPostprocess();
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufColumnConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufTableConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ std::vector<TProtobufColumnConfigPtr> Columns;
+
+ REGISTER_YSON_STRUCT(TProtobufTableConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufTableConfig)
+
+DEFINE_ENUM(ENestedMessagesMode,
+ (Protobuf)
+ (Yson)
+);
+
+class TProtobufFormatConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ TString FileDescriptorSet; // deprecated
+ std::vector<int> FileIndices; // deprecated
+ std::vector<int> MessageIndices; // deprecated
+ bool EnumsAsStrings; // deprecated
+ ENestedMessagesMode NestedMessagesMode; // deprecated
+
+ std::vector<TProtobufTableConfigPtr> Tables;
+ NYTree::IMapNodePtr Enumerations;
+
+ std::optional<TString> FileDescriptorSetText;
+ std::vector<TString> TypeNames;
+
+ EComplexTypeMode ComplexTypeMode;
+ EDecimalMode DecimalMode;
+ ETimeMode TimeMode;
+ EUuidMode UuidMode;
+
+ REGISTER_YSON_STRUCT(TProtobufFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EWebJsonValueFormat,
+ // Values are stringified and (de-)serialized together with their types in form
+ // |"column_name": {"$type": "double", "$value": "3.141592"}|.
+ // Strings with length exceeding |FieldWeightLimit| are truncated and meta-attribute
+ // "$incomplete" with value |true| is added to the representation, e.g.
+ // |"column_name": {"$type": "string", "$incomplete": true, "$value": "Some very long st"}|
+ (Schemaless)
+
+ // Values are stringified and (de-)serialized in form
+ // |<column_name>: [ <value>, <stringified-type-index>]|, e.g. |"column_name": ["3.141592", "3"]|.
+ // Type indices point to type registry stored under "yql_type_registry" key.
+ // Non-UTF-8 strings are Base64-encoded and enclosed in a map with "b64" and "val" keys:
+ // | "column_name": {"val": "aqw==", "b64": true} |.
+ // Strings and lists can be truncated, in which case they are enclosed in a map with "inc" and "val" keys:
+ // | "column_name": {"val": ["12", "13"], "inc": true} |
+ // Wrapping in an additional map can occur on any depth.
+ // Both "inc" and "b64" keys may appear in such maps.
+ (Yql)
+);
+
+class TWebJsonFormatConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ int MaxSelectedColumnCount;
+ int FieldWeightLimit;
+ int StringWeightLimit;
+ int MaxAllColumnNamesCount;
+ std::optional<std::vector<TString>> ColumnNames;
+ EWebJsonValueFormat ValueFormat;
+
+ // Intentionally do not reveal following options to user.
+ bool SkipSystemColumns = true;
+
+ REGISTER_YSON_STRUCT(TWebJsonFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TWebJsonFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSkiffFormatConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ NYTree::IMapNodePtr SkiffSchemaRegistry;
+ NYTree::IListNodePtr TableSkiffSchemas;
+
+ // This is temporary configuration until we support schema on mapreduce operations fully.
+ std::optional<NTableClient::TTableSchema> OverrideIntermediateTableSchema;
+
+ REGISTER_YSON_STRUCT(TSkiffFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TSkiffFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/dsv_parser.cpp b/yt/yt/client/formats/dsv_parser.cpp
new file mode 100644
index 0000000000..c2ccb2babf
--- /dev/null
+++ b/yt/yt/client/formats/dsv_parser.cpp
@@ -0,0 +1,236 @@
+#include "dsv_parser.h"
+
+#include "format.h"
+#include "escape.h"
+#include "parser.h"
+
+namespace NYT::NFormats {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EDsvParserState,
+ (InsidePrefix)
+ (InsideKey)
+ (InsideValue)
+);
+
+class TDsvParser
+ : public IParser
+{
+public:
+ TDsvParser(
+ IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config,
+ bool wrapWithmap);
+
+ void Read(TStringBuf data) override;
+ void Finish() override;
+
+private:
+ IYsonConsumer* Consumer;
+ TDsvFormatConfigPtr Config;
+ bool WrapWithMap; // This is actually used to represent "embedded" semantics.
+ char LastCharacter; // This is used to verify record separator presence.
+
+ TEscapeTable KeyEscapeTable_;
+ TEscapeTable ValueEscapeTable_;
+
+ bool NewRecordStarted;
+ bool ExpectingEscapedChar;
+
+ int RecordCount;
+ int FieldCount;
+
+ TString CurrentToken;
+
+ const char* Consume(const char* begin, const char* end);
+
+ void StartRecordIfNeeded();
+ void FinishRecord();
+
+ void ValidatePrefix(const TString& prefix) const;
+
+ using EState = EDsvParserState;
+ EState State;
+
+ EState GetStartState() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDsvParser::TDsvParser(
+ IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config,
+ bool wrapWithMap)
+ : Consumer(consumer)
+ , Config(config)
+ , WrapWithMap(wrapWithMap)
+ , LastCharacter(Config->RecordSeparator)
+ , NewRecordStarted(!wrapWithMap)
+ , ExpectingEscapedChar(false)
+ , RecordCount(1)
+ , FieldCount(1)
+ , State(GetStartState())
+{
+ ConfigureEscapeTables(config, false /* addCarriageReturn */, &KeyEscapeTable_, &ValueEscapeTable_);
+}
+
+void TDsvParser::Read(TStringBuf data)
+{
+ auto current = data.begin();
+ while (current != data.end()) {
+ current = Consume(current, data.end());
+ }
+}
+
+void TDsvParser::Finish()
+{
+ if (ExpectingEscapedChar) {
+ THROW_ERROR_EXCEPTION("Incomplete escape sequence in DSV");
+ }
+ if (WrapWithMap && LastCharacter != Config->RecordSeparator) {
+ THROW_ERROR_EXCEPTION("Expected record to be terminated with record separator");
+ }
+ if (!CurrentToken.empty()) {
+ StartRecordIfNeeded();
+ }
+ if (State == EState::InsideValue) {
+ Consumer->OnStringScalar(CurrentToken);
+ }
+ if (State == EState::InsidePrefix && !CurrentToken.empty()) {
+ ValidatePrefix(CurrentToken);
+ }
+ CurrentToken.clear();
+ FinishRecord();
+}
+
+const char* TDsvParser::Consume(const char* begin, const char* end)
+{
+ if (end - begin > 0) {
+ LastCharacter = *(end - 1);
+ }
+ // Process escaping symbols.
+ if (Config->EnableEscaping && !ExpectingEscapedChar && *begin == Config->EscapingSymbol) {
+ ExpectingEscapedChar = true;
+ return begin + 1;
+ }
+ if (ExpectingEscapedChar) {
+ CurrentToken.append(EscapeBackward[static_cast<ui8>(*begin)]);
+ ExpectingEscapedChar = false;
+ return begin + 1;
+ }
+
+ // Read until first stop symbol.
+ auto next = State == EState::InsideKey
+ ? KeyEscapeTable_.FindNext(begin, end)
+ : ValueEscapeTable_.FindNext(begin, end);
+ CurrentToken.append(begin, next);
+ if (next == end || (Config->EnableEscaping && *next == Config->EscapingSymbol)) {
+ return next;
+ }
+
+ if (*next == '\0') {
+ THROW_ERROR_EXCEPTION("Unescaped \\0 symbol in DSV")
+ << TErrorAttribute("record_index", RecordCount)
+ << TErrorAttribute("field_index", FieldCount);
+ }
+
+ // Here, we have finished reading prefix, key or value
+ if (State == EState::InsidePrefix) {
+ StartRecordIfNeeded();
+ ValidatePrefix(CurrentToken);
+ State = EState::InsideKey;
+ } else if (State == EState::InsideKey) {
+ StartRecordIfNeeded();
+ if (*next == Config->KeyValueSeparator) {
+ Consumer->OnKeyedItem(CurrentToken);
+ State = EState::InsideValue;
+ }
+ } else if (State == EState::InsideValue) {
+ Consumer->OnStringScalar(CurrentToken);
+ State = EState::InsideKey;
+ FieldCount += 1;
+ } else {
+ YT_ABORT();
+ }
+
+ CurrentToken.clear();
+ if (*next == Config->RecordSeparator) {
+ FinishRecord();
+ }
+ return next + 1;
+}
+
+TDsvParser::EState TDsvParser::GetStartState() const
+{
+ return Config->LinePrefix ? EState::InsidePrefix : EState::InsideKey;
+}
+
+void TDsvParser::StartRecordIfNeeded()
+{
+ if (!NewRecordStarted) {
+ Consumer->OnListItem();
+ Consumer->OnBeginMap();
+ NewRecordStarted = true;
+ }
+}
+
+void TDsvParser::FinishRecord()
+{
+ if (WrapWithMap && NewRecordStarted) {
+ Consumer->OnEndMap();
+ NewRecordStarted = false;
+ }
+ State = GetStartState();
+
+ RecordCount += 1;
+ FieldCount = 1;
+}
+
+void TDsvParser::ValidatePrefix(const TString& prefix) const
+{
+ if (prefix != *Config->LinePrefix) {
+ // TODO(babenko): provide position
+ THROW_ERROR_EXCEPTION("Malformed line prefix in DSV: expected %Qv, found %Qv",
+ *Config->LinePrefix,
+ prefix)
+ << TErrorAttribute("record_index", RecordCount)
+ << TErrorAttribute("field_index", FieldCount);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseDsv(
+ IInputStream* input,
+ IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config)
+{
+ auto parser = CreateParserForDsv(consumer, config);
+ Parse(input, parser.get());
+}
+
+void ParseDsv(
+ TStringBuf data,
+ IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config)
+{
+ auto parser = CreateParserForDsv(consumer, config);
+ parser->Read(data);
+ parser->Finish();
+}
+
+std::unique_ptr<IParser> CreateParserForDsv(
+ IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config,
+ bool wrapWithMap)
+{
+ return std::unique_ptr<IParser>(new TDsvParser(consumer, config, wrapWithMap));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/dsv_parser.h b/yt/yt/client/formats/dsv_parser.h
new file mode 100644
index 0000000000..5a156d5db5
--- /dev/null
+++ b/yt/yt/client/formats/dsv_parser.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+#include <yt/yt/core/yson/consumer.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*!
+ * \param wrapWithMap If True then the parser wraps values with calls to
+ * #IYsonConsumer::OnBeginMap and #IYsonConsumer::OnEndMap.
+ */
+std::unique_ptr<IParser> CreateParserForDsv(
+ NYson::IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config = New<TDsvFormatConfig>(),
+ bool wrapWithMap = true);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseDsv(
+ IInputStream* input,
+ NYson::IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config = New<TDsvFormatConfig>());
+
+void ParseDsv(
+ TStringBuf data,
+ NYson::IYsonConsumer* consumer,
+ TDsvFormatConfigPtr config = New<TDsvFormatConfig>());
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/dsv_writer.cpp b/yt/yt/client/formats/dsv_writer.cpp
new file mode 100644
index 0000000000..934b82ed26
--- /dev/null
+++ b/yt/yt/client/formats/dsv_writer.cpp
@@ -0,0 +1,278 @@
+#include "dsv_writer.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/format.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYTree;
+using namespace NYson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDsvWriterBase::TDsvWriterBase(
+ TDsvFormatConfigPtr config)
+ : Config_(config)
+{
+ YT_VERIFY(Config_);
+ ConfigureEscapeTables(config, true /* addCarriageReturn */, &KeyEscapeTable_, &ValueEscapeTable_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessWriterForDsv
+ : public TSchemalessFormatWriterBase
+ , public TDsvWriterBase
+{
+public:
+ TSchemalessWriterForDsv(
+ TNameTablePtr nameTable,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ IAsyncOutputStreamPtr output,
+ TDsvFormatConfigPtr config = New<TDsvFormatConfig>())
+ : TSchemalessFormatWriterBase(
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ 0 /* keyColumnCount */)
+ , TDsvWriterBase(config)
+ { }
+
+private:
+ void DoWrite(TRange<TUnversionedRow> rows) override
+ {
+ auto* output = GetOutputStream();
+ for (const auto& row : rows) {
+ bool firstValue = true;
+
+ if (Config_->LinePrefix) {
+ output->Write(*Config_->LinePrefix);
+ firstValue = false;
+ }
+
+ for (const auto* value = row.Begin(); value != row.End(); ++value) {
+ if (value->Type == EValueType::Null) {
+ continue;
+ }
+
+ if (IsRangeIndexColumnId(value->Id) ||
+ IsRowIndexColumnId(value->Id) ||
+ (IsTableIndexColumnId(value->Id) && !Config_->EnableTableIndex))
+ {
+ continue;
+ }
+
+ if (!firstValue) {
+ output->Write(Config_->FieldSeparator);
+ }
+ firstValue = false;
+
+ if (IsTableIndexColumnId(value->Id)) {
+ WriteTableIndexValue(*value);
+ } else {
+ WriteValue(*value);
+ }
+ }
+
+ output->Write(Config_->RecordSeparator);
+ TryFlushBuffer(false);
+ }
+ TryFlushBuffer(true);
+ }
+
+ void WriteValue(const TUnversionedValue& value)
+ {
+ if (Config_->SkipUnsupportedTypes && IsAnyOrComposite(value.Type)) {
+ return;
+ }
+ auto* output = GetOutputStream();
+ EscapeAndWrite(NameTableReader_->GetName(value.Id), output, KeyEscapeTable_);
+ output->Write(Config_->KeyValueSeparator);
+ WriteUnversionedValue(value, output, ValueEscapeTable_);
+ }
+
+ void WriteTableIndexValue(const TUnversionedValue& value)
+ {
+ auto* output = GetOutputStream();
+ EscapeAndWrite(Config_->TableIndexColumn, output, KeyEscapeTable_);
+ output->Write(Config_->KeyValueSeparator);
+ output->Write(::ToString(value.Data.Int64));
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDsvNodeConsumer::TDsvNodeConsumer(
+ IOutputStream* stream,
+ TDsvFormatConfigPtr config)
+ : TDsvWriterBase(config)
+ , Stream_(stream)
+{ }
+
+void TDsvNodeConsumer::OnStringScalar(TStringBuf value)
+{
+ EscapeAndWrite(value, Stream_, ValueEscapeTable_);
+}
+
+void TDsvNodeConsumer::OnInt64Scalar(i64 value)
+{
+ Stream_->Write(::ToString(value));
+}
+
+void TDsvNodeConsumer::OnUint64Scalar(ui64 value)
+{
+ Stream_->Write(::ToString(value));
+}
+
+void TDsvNodeConsumer::OnDoubleScalar(double value)
+{
+ Stream_->Write(::ToString(value));
+}
+
+void TDsvNodeConsumer::OnBooleanScalar(bool value)
+{
+ Stream_->Write(FormatBool(value));
+}
+
+void TDsvNodeConsumer::OnEntity()
+{
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Entities are not supported by DSV");
+}
+
+void TDsvNodeConsumer::OnBeginList()
+{
+ if (AllowBeginList_) {
+ AllowBeginList_ = false;
+ } else {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Embedded lists are not supported by DSV");
+ }
+}
+
+void TDsvNodeConsumer::OnListItem()
+{
+ AllowBeginMap_ = true;
+ if (BeforeFirstListItem_) {
+ BeforeFirstListItem_ = false;
+ } else {
+ // Not first item.
+ Stream_->Write(Config_->RecordSeparator);
+ }
+}
+
+void TDsvNodeConsumer::OnEndList()
+{
+ Stream_->Write(Config_->RecordSeparator);
+}
+
+void TDsvNodeConsumer::OnBeginMap()
+{
+ if (AllowBeginMap_) {
+ AllowBeginList_ = false;
+ AllowBeginMap_ = false;
+ BeforeFirstMapItem_ = true;
+ } else {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Embedded maps are not supported by DSV");
+ }
+}
+
+void TDsvNodeConsumer::OnKeyedItem(TStringBuf key)
+{
+ YT_ASSERT(!AllowBeginMap_);
+ YT_ASSERT(!AllowBeginList_);
+
+ if (BeforeFirstMapItem_) {
+ BeforeFirstMapItem_ = false;
+ } else {
+ Stream_->Write(Config_->FieldSeparator);
+ }
+
+ EscapeAndWrite(key, Stream_, KeyEscapeTable_);
+ Stream_->Write(Config_->KeyValueSeparator);
+}
+
+void TDsvNodeConsumer::OnEndMap()
+{
+ YT_ASSERT(!AllowBeginMap_);
+ YT_ASSERT(!AllowBeginList_);
+}
+
+void TDsvNodeConsumer::OnBeginAttributes()
+{
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Embedded attributes are not supported by DSV");
+}
+
+void TDsvNodeConsumer::OnEndAttributes()
+{
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForDsv(
+ TDsvFormatConfigPtr config,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int /* keyColumnCount */)
+{
+ if (controlAttributesConfig->EnableKeySwitch) {
+ THROW_ERROR_EXCEPTION("Key switches are not supported in DSV format");
+ }
+
+ if (controlAttributesConfig->EnableRangeIndex) {
+ THROW_ERROR_EXCEPTION("Range indices are not supported in DSV format");
+ }
+
+ if (controlAttributesConfig->EnableRowIndex) {
+ THROW_ERROR_EXCEPTION("Row indices are not supported in DSV format");
+ }
+
+ if (controlAttributesConfig->EnableTabletIndex) {
+ THROW_ERROR_EXCEPTION("Tablet indices are not supported in DSV format");
+ }
+
+ if (controlAttributesConfig->EnableEndOfStream) {
+ THROW_ERROR_EXCEPTION("End of stream attribute is not supported in DSV format");
+ }
+
+ return New<TSchemalessWriterForDsv>(
+ nameTable,
+ enableContextSaving,
+ controlAttributesConfig,
+ output,
+ config);
+}
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForDsv(
+ const IAttributeDictionary& attributes,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ try {
+ auto config = ConvertTo<TDsvFormatConfigPtr>(&attributes);
+ return CreateSchemalessWriterForDsv(
+ config,
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ } catch (const std::exception& exc) {
+ THROW_ERROR_EXCEPTION(EErrorCode::InvalidFormat, "Failed to parse config for DSV format") << exc;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/dsv_writer.h b/yt/yt/client/formats/dsv_writer.h
new file mode 100644
index 0000000000..5d1c5de674
--- /dev/null
+++ b/yt/yt/client/formats/dsv_writer.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#include "config.h"
+#include "escape.h"
+#include "helpers.h"
+#include "public.h"
+#include "schemaless_writer_adapter.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDsvWriterBase
+{
+public:
+ explicit TDsvWriterBase(TDsvFormatConfigPtr config);
+
+protected:
+ const TDsvFormatConfigPtr Config_;
+ TEscapeTable KeyEscapeTable_;
+ TEscapeTable ValueEscapeTable_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// YsonNode is written as follows:
+// * Each element of list is ended with RecordSeparator
+// * Items in map are separated with FieldSeparator
+// * Key and Values in map are separated with KeyValueSeparator
+class TDsvNodeConsumer
+ : public TDsvWriterBase
+ , public TFormatsConsumerBase
+{
+public:
+ explicit TDsvNodeConsumer(
+ IOutputStream* stream,
+ TDsvFormatConfigPtr config = New<TDsvFormatConfig>());
+
+ // IYsonConsumer overrides.
+ void OnStringScalar(TStringBuf value) override;
+ void OnInt64Scalar(i64 value) override;
+ void OnUint64Scalar(ui64 value) override;
+ void OnDoubleScalar(double value) override;
+ void OnBooleanScalar(bool value) override;
+ void OnEntity() override;
+ void OnBeginList() override;
+ void OnListItem() override;
+ void OnEndList() override;
+ void OnBeginMap() override;
+ void OnKeyedItem(TStringBuf key) override;
+ void OnEndMap() override;
+ void OnBeginAttributes() override;
+ void OnEndAttributes() override;
+
+private:
+ IOutputStream* const Stream_;
+
+ bool AllowBeginList_ = true;
+ bool AllowBeginMap_ = true;
+
+ bool BeforeFirstMapItem_ = true;
+ bool BeforeFirstListItem_ = true;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForDsv(
+ TDsvFormatConfigPtr config,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int /* keyColumnCount */);
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForDsv(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int /* keyColumnCount */);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/escape.cpp b/yt/yt/client/formats/escape.cpp
new file mode 100644
index 0000000000..50b1bf85e5
--- /dev/null
+++ b/yt/yt/client/formats/escape.cpp
@@ -0,0 +1,249 @@
+#include "escape.h"
+
+#ifdef YT_USE_SSE42
+ #include <util/system/cpu_id.h>
+#endif
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+#ifdef YT_USE_SSE42
+
+const char _m128i_shift_right[31] = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1
+};
+
+// This method performs an "aligned" load of |p| into 128-bit register.
+// If |p| is not aligned then the returned value will contain a (byte-)prefix
+// of memory region pointed by |p| truncated at the first 16-byte boundary.
+// The length of the result is stored into |length|.
+//
+// Note that real motivation for this method is to avoid accidental page faults
+// with direct unaligned reads. I. e., if you have 4 bytes at the end of a page
+// then unaligned read will read 16 - 4 = 12 bytes from the next page causing
+// a page fault; if the next page is unmapped this will incur a segmentation
+// fault and terminate the process.
+YT_ATTRIBUTE_NO_SANITIZE_ADDRESS inline __m128i AlignedPrefixLoad(
+ const void* p,
+ int* length) Y_NO_SANITIZE("memory")
+{
+ int offset = (size_t)p & 15; *length = 16 - offset;
+
+ if (offset) {
+ // Load and shift to the right.
+ // (Kudos to glibc authors for fast implementation).
+ return _mm_shuffle_epi8(
+ _mm_load_si128((__m128i*)((char*)p - offset)),
+ _mm_loadu_si128((__m128i*)(_m128i_shift_right + offset)));
+ } else {
+ // Just load.
+ return _mm_load_si128((__m128i*)p);
+ }
+}
+
+YT_ATTRIBUTE_NO_SANITIZE_ADDRESS inline const char* FindNextSymbol(
+ const char* begin,
+ const char* end,
+ __m128i symbols,
+ int count) Y_NO_SANITIZE("memory")
+{
+ const char* current = begin;
+ int length = end - begin;
+ int result, result2, tmp;
+
+ __m128i value = AlignedPrefixLoad(current, &tmp);
+ tmp = Min(tmp, length);
+
+ do {
+ // In short, PCMPxSTRx instruction takes two 128-bit registers with
+ // packed bytes and performs string comparison on them with user-defined
+ // strategy. As the result PCMPxSTRx produces a lot of stuff, i. e.
+ // match bit-mask, LSB or MSB of that bit-mask, and a few flags.
+ //
+ // See http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
+ //
+ // In our case we are doing the following:
+ // - _SIDD_UBYTE_OPS - matching unsigned bytes,
+ // - _SIDD_CMP_EQUAL_ANY - comparing any byte from %xmm0 with any byte of %xmm1,
+ // - _SIDD_MASKED_POSITIVE_POLARITY - are interested only in proper bytes with positive matches,
+ // - _SIDD_LEAST_SIGNIFICANT - are interested in the index of least significant match position.
+ //
+ // In human terms this means "find position of first occurrence of
+ // any byte from %xmm0 in %xmm1".
+ //
+ // XXX(sandello): These intrinsics compile down to a single
+ // "pcmpestri $0x20,%xmm0,%xmm1" instruction, because |result| is written
+ // to %ecx and |result2| is (simultaneously) written to CFlag.
+ // We are interested in CFlag because it is cheaper to check.
+ result = _mm_cmpestri(
+ symbols,
+ count,
+ value,
+ tmp,
+ _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY |
+ _SIDD_MASKED_POSITIVE_POLARITY | _SIDD_LEAST_SIGNIFICANT);
+ result2 = _mm_cmpestrc(
+ symbols,
+ count,
+ value,
+ tmp,
+ _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY |
+ _SIDD_MASKED_POSITIVE_POLARITY | _SIDD_LEAST_SIGNIFICANT);
+
+ if (result2) {
+ return current + result;
+ } else {
+ current += tmp;
+ length -= tmp;
+ }
+
+ if (length > 0) {
+ // We may load more bytes than needed (but within memory page, due to 16-byte alignment)
+ // but subsequent call to _mm_cmpestri compares only appropriate bytes.
+ value = _mm_load_si128((__m128i*)current);
+ tmp = Min(16, length);
+ } else {
+ break;
+ }
+ } while (true);
+
+ YT_VERIFY(current == end);
+ return current;
+}
+
+#endif
+
+static inline const char* FindNextSymbol(
+ const char* begin,
+ const char* end,
+ const bool* bitmap)
+{
+ // XXX(sandello): Manual loop unrolling saves about 8% CPU.
+ const char* current = begin;
+#define DO_1 if (bitmap[static_cast<ui8>(*current)]) { return current; } ++current;
+#define DO_4 DO_1 DO_1 DO_1 DO_1
+#define DO_16 DO_4 DO_4 DO_4 DO_4
+ while (current + 16 < end) { DO_16; }
+ while (current + 4 < end) { DO_4; }
+ while (current < end) { DO_1; }
+#undef DO_1
+#undef DO_4
+#undef DO_16
+ YT_ASSERT(current == end);
+ return current;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEscapeTable::TEscapeTable()
+{ }
+
+void TEscapeTable::FillStops(const std::vector<char>& stopSymbols)
+{
+ YT_VERIFY(stopSymbols.size() <= 16);
+
+#ifdef YT_USE_SSE42
+ if (NX86::CachedHaveSSE42()) {
+ char storage[16] = {0};
+
+ SymbolCount = stopSymbols.size();
+ for (int i = 0; i < SymbolCount; ++i) {
+ storage[i] = stopSymbols[i]; // :) C-style!
+ }
+
+ Symbols = _mm_setr_epi8(
+ storage[0], storage[1], storage[2], storage[3],
+ storage[4], storage[5], storage[6], storage[7],
+ storage[8], storage[9], storage[10], storage[11],
+ storage[12], storage[13], storage[14], storage[15]);
+ } else
+#endif
+ {
+ for (int i = 0; i < 256; ++i) {
+ Bitmap[i] = false;
+ }
+
+ for (char stopSymbol : stopSymbols) {
+ Bitmap[static_cast<ui8>(stopSymbol)] = true;
+ }
+ }
+}
+
+const char* TEscapeTable::FindNext(const char* begin, const char* end) const
+{
+ if (begin == end) {
+ return end;
+ }
+#ifdef YT_USE_SSE42
+ if (NX86::CachedHaveSSE42()) {
+ return FindNextSymbol(begin, end, Symbols, SymbolCount);
+ } else
+#endif
+ {
+ return FindNextSymbol(begin, end, Bitmap);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void EscapeAndWrite(
+ TStringBuf string,
+ IOutputStream* stream,
+ const TEscapeTable& escapeTable)
+{
+ if (escapeTable.EscapingSymbol) {
+ auto* begin = string.begin();
+ auto* end = string.end();
+ auto* next = begin;
+ for (; begin != end; begin = next) {
+ next = escapeTable.FindNext(begin, end);
+
+ stream->Write(begin, next - begin);
+ if (next != end) {
+ stream->Write(escapeTable.EscapingSymbol);
+ stream->Write(EscapeForward[static_cast<ui8>(*next)]);
+ ++next;
+ }
+ }
+ } else {
+ stream->Write(string);
+ }
+}
+
+TString Escape(
+ TStringBuf string,
+ const TEscapeTable& escapeTable)
+{
+ if (escapeTable.EscapingSymbol) {
+ TString result;
+ // In worst case result length will be twice the original length.
+ result.reserve(2 * string.length());
+ auto* begin = string.begin();
+ auto* end = string.end();
+ auto* next = begin;
+ for (; begin != end; begin = next) {
+ next = escapeTable.FindNext(begin, end);
+ result.append(begin, next);
+ if (next != end) {
+ result.append(escapeTable.EscapingSymbol);
+ result.append(EscapeForward[static_cast<ui8>(*next)]);
+ ++next;
+ }
+ }
+ return result;
+ } else {
+ return TString(string);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/escape.h b/yt/yt/client/formats/escape.h
new file mode 100644
index 0000000000..979ff2689d
--- /dev/null
+++ b/yt/yt/client/formats/escape.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include "public.h"
+
+#include <string>
+#include <vector>
+
+#ifdef YT_USE_SSE42
+# include <nmmintrin.h>
+#endif
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr char EscapeForward[257] =
+ "\x30\x01\x02\x03\x04\x05\x06\x07\x08\x74\x6e\x0b\x0c\x72\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
+ "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
+ "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
+ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
+ "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
+ "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
+ "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
+ "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
+ "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
+ "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
+ "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
+ "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
+ "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
+ "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
+
+constexpr char EscapeBackward[257] =
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
+ "\x00\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
+ "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
+ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
+ "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x0a\x6f"
+ "\x70\x71\x0d\x73\x09\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
+ "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
+ "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
+ "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
+ "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
+ "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
+ "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
+ "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
+ "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEscapeTable
+{
+public:
+ TEscapeTable();
+
+ void FillStops(const std::vector<char>& v);
+
+ const char* FindNext(const char* begin, const char* end) const;
+
+ //! Value of \0 means that escaping is not enabled.
+ char EscapingSymbol = '\0';
+
+private:
+
+#ifdef YT_USE_SSE42
+# ifdef _MSC_VER
+# define DECL_PREFIX __declspec(align(16))
+# define DECL_SUFFIX
+# else
+# define DECL_PREFIX
+# define DECL_SUFFIX __attribute__((aligned(16)))
+# endif
+ DECL_PREFIX __m128i Symbols DECL_SUFFIX;
+ int SymbolCount;
+#endif
+ bool Bitmap[256];
+
+};
+
+void EscapeAndWrite(
+ TStringBuf string,
+ IOutputStream* stream,
+ const TEscapeTable& escapeTable);
+
+TString Escape(
+ TStringBuf string,
+ const TEscapeTable& escapeTable);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/format.cpp b/yt/yt/client/formats/format.cpp
new file mode 100644
index 0000000000..72db758f73
--- /dev/null
+++ b/yt/yt/client/formats/format.cpp
@@ -0,0 +1,659 @@
+#include "format.h"
+#include "parser.h"
+#include "dsv_parser.h"
+#include "dsv_writer.h"
+#include "protobuf_parser.h"
+#include "protobuf_writer.h"
+#include "schemaful_dsv_parser.h"
+#include "schemaful_dsv_writer.h"
+#include "schemaful_writer.h"
+#include "web_json_writer.h"
+#include "schemaless_writer_adapter.h"
+#include "skiff_parser.h"
+#include "skiff_writer.h"
+#include "versioned_writer.h"
+#include "yamred_dsv_parser.h"
+#include "yamred_dsv_writer.h"
+#include "yamr_parser.h"
+#include "yamr_writer.h"
+#include "yson_parser.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/table_consumer.h>
+
+#include <yt/yt/library/skiff_ext/schema_match.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/writer.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/core/yson/forwarding_consumer.h>
+
+#include <yt/yt/core/json/json_parser.h>
+#include <yt/yt/core/json/json_writer.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYTree;
+using namespace NYson;
+using namespace NJson;
+using namespace NTableClient;
+using namespace NSkiffExt;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFormat::TFormat()
+ : Type_(EFormatType::Null)
+ , Attributes_(CreateEphemeralAttributes())
+{ }
+
+TFormat::TFormat(EFormatType type, const IAttributeDictionary* attributes)
+ : Type_(type)
+ , Attributes_(attributes ? attributes->Clone() : CreateEphemeralAttributes())
+{ }
+
+TFormat::TFormat(const TFormat& other)
+ : Type_(other.Type_)
+ , Attributes_(other.Attributes_->Clone())
+{ }
+
+TFormat& TFormat::operator=(const TFormat& other)
+{
+ if (this != &other) {
+ Type_ = other.Type_;
+ Attributes_ = other.Attributes_ ? other.Attributes_->Clone() : nullptr;
+ }
+ return *this;
+}
+
+const IAttributeDictionary& TFormat::Attributes() const
+{
+ return *Attributes_;
+}
+
+void Serialize(const TFormat& value, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginAttributes()
+ .Items(value.Attributes())
+ .EndAttributes()
+ .Value(value.GetType());
+}
+
+void Deserialize(TFormat& value, INodePtr node)
+{
+ if (node->GetType() != ENodeType::String) {
+ THROW_ERROR_EXCEPTION("Format name must be a string");
+ }
+
+ auto typeStr = node->GetValue<TString>();
+ EFormatType type;
+ try {
+ type = ParseEnum<EFormatType>(typeStr);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Invalid format name %Qv",
+ typeStr);
+ }
+
+ value = TFormat(type, &node->Attributes());
+}
+
+void Deserialize(TFormat& value, NYson::TYsonPullParserCursor* cursor)
+{
+ Deserialize(value, ExtractTo<INodePtr>(cursor));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+EYsonType DataTypeToYsonType(EDataType dataType)
+{
+ switch (dataType) {
+ case EDataType::Structured:
+ return EYsonType::Node;
+ case EDataType::Tabular:
+ return EYsonType::ListFragment;
+ default:
+ THROW_ERROR_EXCEPTION("Data type %Qlv is not supported by YSON",
+ dataType);
+ }
+}
+
+std::unique_ptr<IFlushableYsonConsumer> CreateConsumerForYson(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IZeroCopyOutput* output)
+{
+ auto config = ConvertTo<TYsonFormatConfigPtr>(&attributes);
+ return CreateYsonWriter(
+ output,
+ config->Format,
+ DataTypeToYsonType(dataType),
+ config->Format == EYsonFormat::Binary);
+}
+
+std::unique_ptr<IFlushableYsonConsumer> CreateConsumerForJson(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IOutputStream* output)
+{
+ auto config = ConvertTo<TJsonFormatConfigPtr>(&attributes);
+ return CreateJsonConsumer(output, DataTypeToYsonType(dataType), config);
+}
+
+std::unique_ptr<IFlushableYsonConsumer> CreateConsumerForDsv(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IOutputStream* output)
+{
+ auto config = ConvertTo<TDsvFormatConfigPtr>(&attributes);
+ switch (dataType) {
+ case EDataType::Structured:
+ return std::unique_ptr<IFlushableYsonConsumer>(new TDsvNodeConsumer(output, config));
+
+ case EDataType::Tabular:
+ case EDataType::Binary:
+ case EDataType::Null:
+ THROW_ERROR_EXCEPTION("Data type %Qlv is not supported by DSV",
+ dataType);
+
+ default:
+ YT_ABORT();
+ };
+}
+
+class TTableParserAdapter
+ : public IParser
+{
+public:
+ TTableParserAdapter(
+ const TFormat& format,
+ std::vector<IValueConsumer*> valueConsumers,
+ int tableIndex)
+ : TableConsumer_(new TTableConsumer(
+ TYsonConverterConfig{
+ .ComplexTypeMode = format.Attributes().Get("complex_type_mode", EComplexTypeMode::Named),
+ .StringKeyedDictMode = format.Attributes().Get("string_keyed_dict_mode", EDictMode::Positional),
+ .DecimalMode = format.Attributes().Get("decimal_mode", EDecimalMode::Binary),
+ .TimeMode = format.Attributes().Get("time_mode", ETimeMode::Binary),
+ .UuidMode = format.Attributes().Get("uuid_mode", EUuidMode::Binary),
+ },
+ valueConsumers,
+ tableIndex))
+ , Parser_(CreateParserForFormat(
+ format,
+ EDataType::Tabular,
+ TableConsumer_.get()))
+ { }
+
+ void Read(TStringBuf data) override
+ {
+ Parser_->Read(data);
+ }
+
+ void Finish() override
+ {
+ Parser_->Finish();
+ }
+
+private:
+ const std::unique_ptr<IYsonConsumer> TableConsumer_;
+ const std::unique_ptr<IParser> Parser_;
+};
+
+} // namespace
+
+std::unique_ptr<IFlushableYsonConsumer> CreateConsumerForFormat(
+ const TFormat& format,
+ EDataType dataType,
+ IZeroCopyOutput* output)
+{
+ switch (format.GetType()) {
+ case EFormatType::Yson:
+ return CreateConsumerForYson(dataType, format.Attributes(), output);
+ case EFormatType::Json:
+ return CreateConsumerForJson(dataType, format.Attributes(), output);
+ case EFormatType::Dsv:
+ return CreateConsumerForDsv(dataType, format.Attributes(), output);
+ default:
+ THROW_ERROR_EXCEPTION("Unsupported output format %Qlv",
+ format.GetType());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TWriter, class TConsumerAdapter>
+TIntrusivePtr<TWriter> CreateAdaptedWriterForYson(
+ const IAttributeDictionary& attributes,
+ TTableSchemaPtr schema,
+ IAsyncOutputStreamPtr output)
+{
+ auto config = ConvertTo<TYsonFormatConfigPtr>(&attributes);
+ return New<TConsumerAdapter>(std::move(output), std::move(schema), [=] (IZeroCopyOutput* buffer) {
+ if (config->Format == EYsonFormat::Binary) {
+ return std::unique_ptr<IFlushableYsonConsumer>(new TBufferedBinaryYsonWriter(
+ buffer,
+ EYsonType::ListFragment,
+ true));
+ } else {
+ return std::unique_ptr<IFlushableYsonConsumer>(new TYsonWriter(
+ buffer,
+ config->Format,
+ EYsonType::ListFragment));
+ }
+ });
+}
+
+template <class TWriter, class TConsumerAdapter>
+TIntrusivePtr<TWriter> CreateAdaptedWriterForJson(
+ const IAttributeDictionary& attributes,
+ TTableSchemaPtr schema,
+ IAsyncOutputStreamPtr output)
+{
+ auto config = ConvertTo<TJsonFormatConfigPtr>(&attributes);
+ return New<TConsumerAdapter>(std::move(output), std::move(schema), [&] (IOutputStream* buffer) {
+ return CreateJsonConsumer(buffer, EYsonType::ListFragment, config);
+ });
+}
+
+IUnversionedRowsetWriterPtr CreateSchemafulWriterForFormat(
+ const TFormat& format,
+ TTableSchemaPtr schema,
+ IAsyncOutputStreamPtr output)
+{
+ switch (format.GetType()) {
+ case EFormatType::Yson:
+ return CreateAdaptedWriterForYson<IUnversionedRowsetWriter, TSchemafulWriter>(format.Attributes(), std::move(schema), std::move(output));
+ case EFormatType::Json:
+ return CreateAdaptedWriterForJson<IUnversionedRowsetWriter, TSchemafulWriter>(format.Attributes(), std::move(schema), std::move(output));
+ case EFormatType::SchemafulDsv:
+ return CreateSchemafulWriterForSchemafulDsv(format.Attributes(), std::move(schema), std::move(output));
+ case EFormatType::WebJson: {
+ auto webJsonFormatConfig = ConvertTo<TWebJsonFormatConfigPtr>(&format.Attributes());
+ webJsonFormatConfig->SkipSystemColumns = false;
+
+ return CreateWriterForWebJson(
+ std::move(webJsonFormatConfig),
+ TNameTable::FromSchema(*schema),
+ {schema},
+ std::move(output));
+ }
+ default:
+ THROW_ERROR_EXCEPTION("Unsupported output format %Qlv",
+ format.GetType());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IVersionedWriterPtr CreateVersionedWriterForFormat(
+ const TFormat& format,
+ NTableClient::TTableSchemaPtr schema,
+ NConcurrency::IAsyncOutputStreamPtr output)
+{
+ switch (format.GetType()) {
+ case EFormatType::Yson:
+ return CreateAdaptedWriterForYson<IVersionedWriter, TVersionedWriter>(format.Attributes(), std::move(schema), std::move(output));
+ case EFormatType::Json:
+ return CreateAdaptedWriterForJson<IVersionedWriter, TVersionedWriter>(format.Attributes(), std::move(schema), std::move(output));
+ default:
+ THROW_ERROR_EXCEPTION("Unsupported output format %Qlv", format.GetType());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateStaticTableWriterForFormat(
+ const TFormat& format,
+ TNameTablePtr nameTable,
+ const std::vector<TTableSchemaPtr>& tableSchemas,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ switch (format.GetType()) {
+ case EFormatType::Dsv:
+ return CreateSchemalessWriterForDsv(
+ format.Attributes(),
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ case EFormatType::Yamr:
+ return CreateSchemalessWriterForYamr(
+ format.Attributes(),
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ case EFormatType::YamredDsv:
+ return CreateSchemalessWriterForYamredDsv(
+ format.Attributes(),
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ case EFormatType::SchemafulDsv:
+ return CreateSchemalessWriterForSchemafulDsv(
+ format.Attributes(),
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ case EFormatType::Protobuf:
+ return CreateWriterForProtobuf(
+ format.Attributes(),
+ tableSchemas,
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ case EFormatType::WebJson:
+ return CreateWriterForWebJson(
+ format.Attributes(),
+ nameTable,
+ tableSchemas,
+ std::move(output));
+ case EFormatType::Skiff:
+ return CreateWriterForSkiff(
+ format.Attributes(),
+ nameTable,
+ tableSchemas,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ default:
+ auto adapter = New<TSchemalessWriterAdapter>(
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ adapter->Init(tableSchemas, format);
+ return adapter;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYsonProducer CreateProducerForDsv(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IInputStream* input)
+{
+ if (dataType != EDataType::Tabular) {
+ THROW_ERROR_EXCEPTION("DSV is supported only for tabular data");
+ }
+ auto config = ConvertTo<TDsvFormatConfigPtr>(&attributes);
+ return BIND([=] (IYsonConsumer* consumer) {
+ ParseDsv(input, consumer, config);
+ });
+}
+
+TYsonProducer CreateProducerForYamr(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IInputStream* input)
+{
+ if (dataType != EDataType::Tabular) {
+ THROW_ERROR_EXCEPTION("YAMR is supported only for tabular data");
+ }
+ auto config = ConvertTo<TYamrFormatConfigPtr>(&attributes);
+ return BIND([=] (IYsonConsumer* consumer) {
+ ParseYamr(input, consumer, config);
+ });
+}
+
+TYsonProducer CreateProducerForYamredDsv(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IInputStream* input)
+{
+ if (dataType != EDataType::Tabular) {
+ THROW_ERROR_EXCEPTION("Yamred DSV is supported only for tabular data");
+ }
+ auto config = ConvertTo<TYamredDsvFormatConfigPtr>(&attributes);
+ return BIND([=] (IYsonConsumer* consumer) {
+ ParseYamredDsv(input, consumer, config);
+ });
+}
+
+TYsonProducer CreateProducerForSchemafulDsv(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IInputStream* input)
+{
+ if (dataType != EDataType::Tabular) {
+ THROW_ERROR_EXCEPTION("Schemaful DSV is supported only for tabular data");
+ }
+ auto config = ConvertTo<TSchemafulDsvFormatConfigPtr>(&attributes);
+ return BIND([=] (IYsonConsumer* consumer) {
+ ParseSchemafulDsv(input, consumer, config);
+ });
+}
+
+TYsonProducer CreateProducerForJson(
+ EDataType dataType,
+ const IAttributeDictionary& attributes,
+ IInputStream* input)
+{
+ auto ysonType = DataTypeToYsonType(dataType);
+ auto config = ConvertTo<TJsonFormatConfigPtr>(&attributes);
+ return BIND([=] (IYsonConsumer* consumer) {
+ ParseJson(input, consumer, config, ysonType);
+ });
+}
+
+TYsonProducer CreateProducerForYson(EDataType dataType, IInputStream* input)
+{
+ auto ysonType = DataTypeToYsonType(dataType);
+ return ConvertToProducer(TYsonInput(input, ysonType));
+}
+
+TYsonProducer CreateProducerForFormat(const TFormat& format, EDataType dataType, IInputStream* input)
+{
+ switch (format.GetType()) {
+ case EFormatType::Yson:
+ return CreateProducerForYson(dataType, input);
+ case EFormatType::Json:
+ return CreateProducerForJson(dataType, format.Attributes(), input);
+ case EFormatType::Dsv:
+ return CreateProducerForDsv(dataType, format.Attributes(), input);
+ case EFormatType::Yamr:
+ return CreateProducerForYamr(dataType, format.Attributes(), input);
+ case EFormatType::YamredDsv:
+ return CreateProducerForYamredDsv(dataType, format.Attributes(), input);
+ case EFormatType::SchemafulDsv:
+ return CreateProducerForSchemafulDsv(dataType, format.Attributes(), input);
+ default:
+ THROW_ERROR_EXCEPTION("Unsupported input format %Qlv",
+ format.GetType());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template<class TBase>
+struct TParserAdapter
+ : public TBase
+ , public IParser
+{
+public:
+ template<class... TArgs>
+ TParserAdapter(TArgs&&... args)
+ : TBase(std::forward<TArgs>(args)...)
+ { }
+
+ void Read(TStringBuf data) override
+ {
+ TBase::Read(data);
+ }
+
+ void Finish() override
+ {
+ TBase::Finish();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForFormat(const TFormat& format, EDataType dataType, IYsonConsumer* consumer)
+{
+ switch (format.GetType()) {
+ case EFormatType::Yson:
+ return CreateParserForYson(consumer, DataTypeToYsonType(dataType));
+ case EFormatType::Json: {
+ auto config = ConvertTo<TJsonFormatConfigPtr>(&format.Attributes());
+ return std::unique_ptr<IParser>(new TParserAdapter<TJsonParser>(consumer, config, DataTypeToYsonType(dataType)));
+ }
+ case EFormatType::Dsv: {
+ auto config = ConvertTo<TDsvFormatConfigPtr>(&format.Attributes());
+ return CreateParserForDsv(consumer, config);
+ }
+ case EFormatType::Yamr: {
+ auto config = ConvertTo<TYamrFormatConfigPtr>(&format.Attributes());
+ return CreateParserForYamr(consumer, config);
+ }
+ case EFormatType::YamredDsv: {
+ auto config = ConvertTo<TYamredDsvFormatConfigPtr>(&format.Attributes());
+ return CreateParserForYamredDsv(consumer, config);
+ }
+ case EFormatType::SchemafulDsv: {
+ auto config = ConvertTo<TSchemafulDsvFormatConfigPtr>(&format.Attributes());
+ return CreateParserForSchemafulDsv(consumer, config);
+ }
+ default:
+ THROW_ERROR_EXCEPTION("Unsupported input format %Qlv",
+ format.GetType());
+ }
+}
+
+std::vector<std::unique_ptr<IParser>> CreateParsersForFormat(
+ const TFormat& format,
+ const std::vector<IValueConsumer*>& valueConsumers)
+{
+ std::vector<std::unique_ptr<IParser>> parsers;
+
+ auto parserCount = std::ssize(valueConsumers);
+ parsers.reserve(parserCount);
+
+ switch (format.GetType()) {
+ case EFormatType::Protobuf: {
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(&format.Attributes());
+ // TODO(max42): implementation of CreateParserForProtobuf clones config
+ // on each call, so this loop works in quadratic time. Fix that.
+ for (int tableIndex = 0; tableIndex < parserCount; ++tableIndex) {
+ parsers.emplace_back(CreateParserForProtobuf(valueConsumers[tableIndex], config, tableIndex));
+ }
+ break;
+ }
+ case EFormatType::Skiff: {
+ auto config = ConvertTo<TSkiffFormatConfigPtr>(&format.Attributes());
+ auto skiffSchemas = ParseSkiffSchemas(config->SkiffSchemaRegistry, config->TableSkiffSchemas);
+ for (int tableIndex = 0; tableIndex < parserCount; ++tableIndex) {
+ parsers.emplace_back(CreateParserForSkiff(valueConsumers[tableIndex], skiffSchemas, config, tableIndex));
+ }
+ break;
+ }
+ default:
+ for (int tableIndex = 0; tableIndex < parserCount; ++tableIndex) {
+ parsers.emplace_back(std::make_unique<TTableParserAdapter>(format, valueConsumers, tableIndex));
+ }
+ break;
+ }
+
+ return parsers;
+}
+
+std::unique_ptr<IParser> CreateParserForFormat(
+ const TFormat& format,
+ IValueConsumer* valueConsumer)
+{
+ auto parsers = CreateParsersForFormat(format, {valueConsumer});
+ return std::move(parsers.front());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ConfigureEscapeTable(const TSchemafulDsvFormatConfigPtr& config, TEscapeTable* escapeTable)
+{
+ std::vector<char> stopSymbols = {config->RecordSeparator, config->FieldSeparator};
+ if (config->EnableEscaping) {
+ stopSymbols.push_back(config->EscapingSymbol);
+ escapeTable->EscapingSymbol = config->EscapingSymbol;
+ }
+ escapeTable->FillStops(stopSymbols);
+}
+
+void ConfigureEscapeTables(
+ const TDsvFormatConfigBasePtr& config,
+ bool addCarriageReturn,
+ TEscapeTable* keyEscapeTable,
+ TEscapeTable* valueEscapeTable)
+{
+ std::vector<char> stopSymbols = {config->RecordSeparator, config->FieldSeparator, '\0'};
+
+ if (config->EnableEscaping) {
+ stopSymbols.push_back(config->EscapingSymbol);
+ keyEscapeTable->EscapingSymbol = valueEscapeTable->EscapingSymbol = config->EscapingSymbol;
+ }
+
+ if (addCarriageReturn) {
+ stopSymbols.push_back('\r');
+ }
+
+ valueEscapeTable->FillStops(stopSymbols);
+
+ stopSymbols.push_back(config->KeyValueSeparator);
+ keyEscapeTable->FillStops(stopSymbols);
+}
+
+void ConfigureEscapeTables(
+ const TYamrFormatConfigBasePtr& config,
+ bool enableKeyEscaping,
+ bool enableValueEscaping,
+ bool escapingForWriter,
+ TEscapeTable* keyEscapeTable,
+ TEscapeTable* valueEscapeTable)
+{
+ std::vector<char> valueStopSymbols = {config->RecordSeparator};
+ std::vector<char> keyStopSymbols = {config->RecordSeparator, config->FieldSeparator};
+
+ if (enableKeyEscaping) {
+ if (escapingForWriter) {
+ keyStopSymbols.push_back('\0');
+ keyStopSymbols.push_back('\r');
+ }
+ keyStopSymbols.push_back(config->EscapingSymbol);
+ keyEscapeTable->EscapingSymbol = config->EscapingSymbol;
+ }
+
+ if (enableValueEscaping) {
+ if (escapingForWriter) {
+ valueStopSymbols.push_back('\0');
+ valueStopSymbols.push_back('\r');
+ }
+ valueStopSymbols.push_back(config->EscapingSymbol);
+ valueEscapeTable->EscapingSymbol = config->EscapingSymbol;
+ }
+
+ keyEscapeTable->FillStops(keyStopSymbols);
+ valueEscapeTable->FillStops(valueStopSymbols);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/format.h b/yt/yt/client/formats/format.h
new file mode 100644
index 0000000000..752939aebe
--- /dev/null
+++ b/yt/yt/client/formats/format.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+#include <yt/yt/core/misc/property.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/attributes.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFormat
+{
+public:
+ TFormat();
+ TFormat(const TFormat& other);
+ TFormat(EFormatType type, const NYTree::IAttributeDictionary* attributes = nullptr);
+
+ TFormat& operator = (const TFormat& other);
+
+ DEFINE_BYVAL_RO_PROPERTY(EFormatType, Type);
+
+ const NYTree::IAttributeDictionary& Attributes() const;
+
+private:
+ NYTree::IAttributeDictionaryPtr Attributes_;
+
+};
+
+void Serialize(const TFormat& value, NYson::IYsonConsumer* consumer);
+void Deserialize(TFormat& value, NYTree::INodePtr node);
+void Deserialize(TFormat& value, NYson::TYsonPullParserCursor* cursor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ISchemalessFormatWriter
+ : public NTableClient::IUnversionedRowsetWriter
+{
+ virtual TBlob GetContext() const = 0;
+
+ virtual i64 GetWrittenSize() const = 0;
+
+ [[nodiscard]] virtual TFuture<void> Flush() = 0;
+
+ virtual bool WriteBatch(NTableClient::IUnversionedRowBatchPtr rowBatch) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ISchemalessFormatWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This function historically creates format for reading dynamic tables.
+// It slightly differs from format for static tables. :(
+NTableClient::IUnversionedRowsetWriterPtr CreateSchemafulWriterForFormat(
+ const TFormat& Format,
+ NTableClient::TTableSchemaPtr schema,
+ NConcurrency::IAsyncOutputStreamPtr output);
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTableClient::IVersionedWriterPtr CreateVersionedWriterForFormat(
+ const TFormat& Format,
+ NTableClient::TTableSchemaPtr schema,
+ NConcurrency::IAsyncOutputStreamPtr output);
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateStaticTableWriterForFormat(
+ const TFormat& format,
+ NTableClient::TNameTablePtr nameTable,
+ const std::vector<NTableClient::TTableSchemaPtr>& tableSchemas,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<NYson::IFlushableYsonConsumer> CreateConsumerForFormat(
+ const TFormat& format,
+ EDataType dataType,
+ IZeroCopyOutput* output);
+
+NYson::TYsonProducer CreateProducerForFormat(
+ const TFormat& format,
+ EDataType dataType,
+ IInputStream* input);
+
+std::unique_ptr<IParser> CreateParserForFormat(
+ const TFormat& format,
+ EDataType dataType,
+ NYson::IYsonConsumer* consumer);
+
+//! Create own parser for each value consumer.
+std::vector<std::unique_ptr<IParser>> CreateParsersForFormat(
+ const TFormat& format,
+ const std::vector<NTableClient::IValueConsumer*>& valueConsumers);
+
+//! Create parser for value consumer. Helper for previous method in singular case.
+std::unique_ptr<IParser> CreateParserForFormat(
+ const TFormat& format,
+ NTableClient::IValueConsumer* valueConsumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ConfigureEscapeTable(const TSchemafulDsvFormatConfigPtr& config, TEscapeTable* escapeTable);
+
+void ConfigureEscapeTables(
+ const TDsvFormatConfigBasePtr& config,
+ bool addCarriageReturn,
+ TEscapeTable* keyEscapeTable,
+ TEscapeTable* valueEscapeTable);
+
+void ConfigureEscapeTables(
+ const TYamrFormatConfigBasePtr& config,
+ bool enableKeyEscaping,
+ bool enableValueEscaping,
+ bool escapingForWriter,
+ TEscapeTable* keyEscapeTable,
+ TEscapeTable* valueEscapeTable);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/helpers.cpp b/yt/yt/client/formats/helpers.cpp
new file mode 100644
index 0000000000..9609b447fb
--- /dev/null
+++ b/yt/yt/client/formats/helpers.cpp
@@ -0,0 +1,104 @@
+#include "helpers.h"
+
+#include "escape.h"
+#include "format.h"
+
+#include <yt/yt/client/table_client/schema.h>
+
+namespace NYT::NFormats {
+
+using namespace NTableClient;
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFormatsConsumerBase::TFormatsConsumerBase()
+ : Parser(this)
+{ }
+
+void TFormatsConsumerBase::OnRaw(TStringBuf yson, EYsonType type)
+{
+ Parser.Parse(yson, type);
+}
+
+void TFormatsConsumerBase::Flush()
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void WriteInt(T value, IOutputStream* output)
+{
+ char buf[64];
+ char* end = buf + 64;
+ char* start = WriteDecIntToBufferBackwards(end, value);
+ output->Write(start, end - start);
+}
+
+void WriteDouble(double value, IOutputStream* output)
+{
+ char buf[64];
+ char* begin = buf;
+ auto length = FloatToString(value, buf, sizeof(buf));
+ if (std::find(begin, begin + length, '.') == begin + length &&
+ std::find(begin, begin + length, 'e') == begin + length)
+ {
+ begin[length++] = '.';
+ }
+ output->Write(begin, length);
+}
+
+void WriteUnversionedValue(const TUnversionedValue& value, IOutputStream* output, const TEscapeTable& escapeTable)
+{
+ switch (value.Type) {
+ case EValueType::Null:
+ return;
+ case EValueType::Int64:
+ WriteInt(value.Data.Int64, output);
+ return;
+ case EValueType::Uint64:
+ WriteInt(value.Data.Uint64, output);
+ return;
+ case EValueType::Double:
+ WriteDouble(value.Data.Double, output);
+ return;
+ case EValueType::Boolean:
+ output->Write(FormatBool(value.Data.Boolean));
+ return;
+ case EValueType::String:
+ EscapeAndWrite(value.AsStringBuf(), output, escapeTable);
+ return;
+
+ case EValueType::Any:
+ case EValueType::Composite:
+
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+ }
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Values of type %Qlv are not supported by the chosen format", value.Type)
+ << TErrorAttribute("value", ToString(value));
+}
+
+bool IsTrivialIntermediateSchema(const NTableClient::TTableSchema& schema)
+{
+ // Here we make expected objects.
+ // It might be not that efficient, but we don't want this code to be fast, we want it to be reliable.
+ // Creating expected objects checks that all attributes have their default values
+ // (including future attributes that might be introduced)
+
+ TKeyColumns columnNames;
+ for (const auto& column : schema.Columns()) {
+ columnNames.push_back(column.Name());
+ }
+
+ // Columns are ok, we check other schema attributes.
+ auto expectedSchema = TTableSchema::FromKeyColumns(columnNames);
+ return schema == *expectedSchema;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/helpers.h b/yt/yt/client/formats/helpers.h
new file mode 100644
index 0000000000..526a95db0f
--- /dev/null
+++ b/yt/yt/client/formats/helpers.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/yson/consumer.h>
+#include <yt/yt/core/yson/parser.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TFormatsConsumerBase
+ : public virtual NYson::IFlushableYsonConsumer
+{
+public:
+ TFormatsConsumerBase();
+
+ // This method has standard implementation for YAMR, DSV and YAMRED DSV formats.
+ void OnRaw(TStringBuf yson, NYson::EYsonType type) override;
+
+ void Flush() override;
+
+private:
+ NYson::TStatelessYsonParser Parser;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void WriteUnversionedValue(const NTableClient::TUnversionedValue& value, IOutputStream* output, const TEscapeTable& escapeTable);
+
+bool IsTrivialIntermediateSchema(const NTableClient::TTableSchema& schema);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/lenval_control_constants.h b/yt/yt/client/formats/lenval_control_constants.h
new file mode 100644
index 0000000000..ccbbc71fe4
--- /dev/null
+++ b/yt/yt/client/formats/lenval_control_constants.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <yt/yt/core/misc/common.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr ui32 LenvalTableIndexMarker = static_cast<ui32>(-1);
+constexpr ui32 LenvalKeySwitch = static_cast<ui32>(-2);
+constexpr ui32 LenvalRangeIndexMarker = static_cast<ui32>(-3);
+constexpr ui32 LenvalRowIndexMarker = static_cast<ui32>(-4);
+constexpr ui32 LenvalEndOfStream = static_cast<ui32>(-5);
+constexpr ui32 LenvalTabletIndexMarker = static_cast<ui32>(-6);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/parser.cpp b/yt/yt/client/formats/parser.cpp
new file mode 100644
index 0000000000..ab90721d4e
--- /dev/null
+++ b/yt/yt/client/formats/parser.cpp
@@ -0,0 +1,28 @@
+#include "parser.h"
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <array>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const size_t ParseBufferSize = 1 << 16;
+
+void Parse(IInputStream* input, IParser* parser)
+{
+ std::array<char, ParseBufferSize> buffer;
+ while (true) {
+ size_t bytesRead = input->Read(buffer.data(), ParseBufferSize);
+ if (bytesRead == 0) {
+ break;
+ }
+ parser->Read(TStringBuf(buffer.data(), bytesRead));
+ }
+ parser->Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/parser.h b/yt/yt/client/formats/parser.h
new file mode 100644
index 0000000000..6993e7e0b9
--- /dev/null
+++ b/yt/yt/client/formats/parser.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/core/yson/public.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IParser
+ : public TNonCopyable
+{
+ virtual ~IParser()
+ { }
+
+ virtual void Read(TStringBuf data) = 0;
+ virtual void Finish() = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Parse(IInputStream* input, IParser* parser);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/private.h b/yt/yt/client/formats/private.h
new file mode 100644
index 0000000000..805168dd30
--- /dev/null
+++ b/yt/yt/client/formats/private.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TProtobufParserType)
+DECLARE_REFCOUNTED_CLASS(TProtobufWriterType)
+
+class TProtobufParserFieldDescription;
+class TProtobufWriterFieldDescription;
+
+DECLARE_REFCOUNTED_CLASS(TProtobufParserFormatDescription)
+DECLARE_REFCOUNTED_CLASS(TProtobufWriterFormatDescription)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/protobuf.cpp b/yt/yt/client/formats/protobuf.cpp
new file mode 100644
index 0000000000..e4c29652fa
--- /dev/null
+++ b/yt/yt/client/formats/protobuf.cpp
@@ -0,0 +1,1633 @@
+#include "protobuf.h"
+
+#include "protobuf_options.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/json/json_writer.h>
+
+#include <yt/yt/core/misc/string_builder.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt_proto/yt/formats/extension.pb.h>
+
+#include <google/protobuf/io/tokenizer.h>
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/wire_format_lite.h>
+
+#include <stack>
+
+namespace NYT::NFormats {
+
+using ::google::protobuf::Descriptor;
+using ::google::protobuf::EnumDescriptor;
+using ::google::protobuf::OneofDescriptor;
+using ::google::protobuf::FieldDescriptor;
+using ::google::protobuf::FileDescriptor;
+using ::google::protobuf::Message;
+using ::google::protobuf::DescriptorPool;
+using ::google::protobuf::internal::WireFormatLite;
+
+using namespace NYT::NTableClient;
+using namespace NYT::NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEnumerationDescription::TEnumerationDescription(const TString& name)
+ : Name_(name)
+{ }
+
+const TString& TEnumerationDescription::GetEnumerationName() const
+{
+ return Name_;
+}
+
+const TString& TEnumerationDescription::GetValueName(i32 value) const
+{
+ if (auto valueName = TryGetValueName(value)) {
+ return *valueName;
+ }
+ THROW_ERROR_EXCEPTION("Invalid value for enum")
+ << TErrorAttribute("enum_name", GetEnumerationName())
+ << TErrorAttribute("value", value);
+}
+
+const TString* TEnumerationDescription::TryGetValueName(i32 value) const
+{
+ auto it = ValueToName_.find(value);
+ if (it == ValueToName_.end()) {
+ return nullptr;
+ }
+ return &it->second;
+}
+
+i32 TEnumerationDescription::GetValue(TStringBuf valueName) const
+{
+ if (auto value = TryGetValue(valueName)) {
+ return *value;
+ }
+ THROW_ERROR_EXCEPTION("Invalid value for enum")
+ << TErrorAttribute("enum_name", GetEnumerationName())
+ << TErrorAttribute("value", valueName);
+}
+
+std::optional<i32> TEnumerationDescription::TryGetValue(TStringBuf valueName) const
+{
+ auto it = NameToValue_.find(valueName);
+ if (it == NameToValue_.end()) {
+ return std::nullopt;
+ }
+ return it->second;
+}
+
+void TEnumerationDescription::Add(TString name, i32 value)
+{
+ if (NameToValue_.find(name) != NameToValue_.end()) {
+ THROW_ERROR_EXCEPTION("Enumeration %v already has value %v",
+ Name_,
+ name);
+ }
+ if (ValueToName_.find(value) != ValueToName_.end()) {
+ THROW_ERROR_EXCEPTION("Enumeration %v already has value %v",
+ Name_,
+ value);
+ }
+ NameToValue_.emplace(name, value);
+ ValueToName_.emplace(value, std::move(name));
+}
+
+static IMapNodePtr ConvertToEnumMap(const EnumDescriptor* enumDescriptor)
+{
+ auto node = BuildYsonNodeFluently().DoMap([&] (TFluentMap fluent) {
+ for (int i = 0; i < enumDescriptor->value_count(); ++i) {
+ auto valueDescriptor = enumDescriptor->value(i);
+ YT_VERIFY(valueDescriptor);
+ fluent.Item(valueDescriptor->name()).Value(valueDescriptor->number());
+ }
+ });
+
+ return node->AsMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TParserErrorCollector
+ : public ::google::protobuf::io::ErrorCollector
+{
+public:
+ void AddError(
+ int line,
+ ::google::protobuf::io::ColumnNumber column,
+ const TString& message) override
+ {
+ if (std::ssize(Errors_) < ErrorCountLimit) {
+ Errors_.push_back(TError("%v", message)
+ << TErrorAttribute("line_number", line)
+ << TErrorAttribute("column_number", column));
+ }
+ }
+
+ std::vector<TError> GetErrors() const
+ {
+ return Errors_;
+ }
+
+private:
+ static constexpr int ErrorCountLimit = 100;
+ std::vector<TError> Errors_;
+};
+
+class TDescriptorPoolErrorCollector
+ : public DescriptorPool::ErrorCollector
+{
+public:
+ void AddError(
+ const TString& fileName,
+ const TString& elementName,
+ const Message* /*descriptor*/,
+ DescriptorPool::ErrorCollector::ErrorLocation /*location*/,
+ const TString& message) override
+ {
+ if (std::ssize(Errors_) < ErrorCountLimit) {
+ Errors_.push_back(TError("%v", message)
+ << TErrorAttribute("file_name", fileName)
+ << TErrorAttribute("element_name", elementName));
+ }
+ }
+
+ void ThrowOnError()
+ {
+ if (!Errors_.empty()) {
+ THROW_ERROR_EXCEPTION("Error while building protobuf descriptors")
+ << Errors_;
+ }
+ }
+
+private:
+ static constexpr int ErrorCountLimit = 100;
+ std::vector<TError> Errors_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TEnumerationDescription CreateEnumerationMap(
+ const TString& enumName,
+ const IMapNodePtr& enumerationConfig)
+{
+ TEnumerationDescription result(enumName);
+ for (const auto& enumValue : enumerationConfig->GetChildren()) {
+ const auto& name = enumValue.first;
+ const auto& valueNode = enumValue.second;
+ switch (valueNode->GetType()) {
+ case ENodeType::Uint64:
+ result.Add(name, valueNode->GetValue<ui64>());
+ break;
+ case ENodeType::Int64:
+ result.Add(name, valueNode->GetValue<i64>());
+ break;
+ default:
+ THROW_ERROR_EXCEPTION("Invalid specification of %Qv enumeration: expected type \"int64\" or \"uint64\", actual type %Qlv",
+ enumName,
+ valueNode->GetType());
+ }
+ }
+ return result;
+}
+
+static std::optional<WireFormatLite::FieldType> ConvertFromInternalProtobufType(EProtobufType type)
+{
+ auto fieldType = [=] () -> std::optional<::google::protobuf::FieldDescriptor::Type>{
+ using namespace ::google::protobuf;
+ switch (type) {
+ case EProtobufType::Double:
+ return FieldDescriptor::TYPE_DOUBLE;
+ case EProtobufType::Float:
+ return FieldDescriptor::TYPE_FLOAT;
+
+ case EProtobufType::Int64:
+ return FieldDescriptor::TYPE_INT64;
+ case EProtobufType::Uint64:
+ return FieldDescriptor::TYPE_UINT64;
+ case EProtobufType::Sint64:
+ return FieldDescriptor::TYPE_SINT64;
+ case EProtobufType::Fixed64:
+ return FieldDescriptor::TYPE_FIXED64;
+ case EProtobufType::Sfixed64:
+ return FieldDescriptor::TYPE_SFIXED64;
+
+ case EProtobufType::Int32:
+ return FieldDescriptor::TYPE_INT32;
+ case EProtobufType::Uint32:
+ return FieldDescriptor::TYPE_UINT32;
+ case EProtobufType::Sint32:
+ return FieldDescriptor::TYPE_SINT32;
+ case EProtobufType::Fixed32:
+ return FieldDescriptor::TYPE_FIXED32;
+ case EProtobufType::Sfixed32:
+ return FieldDescriptor::TYPE_SFIXED32;
+
+ case EProtobufType::Bool:
+ return FieldDescriptor::TYPE_BOOL;
+ case EProtobufType::String:
+ return FieldDescriptor::TYPE_STRING;
+ case EProtobufType::Bytes:
+ return FieldDescriptor::TYPE_BYTES;
+
+ case EProtobufType::EnumInt:
+ case EProtobufType::EnumString:
+ return FieldDescriptor::TYPE_ENUM;
+
+ case EProtobufType::Message:
+ case EProtobufType::StructuredMessage:
+ case EProtobufType::EmbeddedMessage:
+ return FieldDescriptor::TYPE_MESSAGE;
+
+ case EProtobufType::Any:
+ case EProtobufType::OtherColumns:
+ return FieldDescriptor::TYPE_BYTES;
+
+ case EProtobufType::Oneof:
+ return std::nullopt;
+ }
+ YT_ABORT();
+ }();
+ if (fieldType) {
+ return static_cast<WireFormatLite::FieldType>(*fieldType);
+ }
+ return std::nullopt;
+}
+
+static std::optional<EProtobufType> ConvertToInternalProtobufType(
+ ::google::protobuf::FieldDescriptor::Type type,
+ bool enumAsString)
+{
+ using namespace ::google::protobuf;
+ switch (type) {
+ case FieldDescriptor::TYPE_DOUBLE:
+ return EProtobufType::Double;
+ case FieldDescriptor::TYPE_FLOAT:
+ return EProtobufType::Float;
+ case FieldDescriptor::TYPE_INT64:
+ return EProtobufType::Int64;
+ case FieldDescriptor::TYPE_UINT64:
+ return EProtobufType::Uint64;
+ case FieldDescriptor::TYPE_INT32:
+ return EProtobufType::Int32;
+ case FieldDescriptor::TYPE_FIXED64:
+ return EProtobufType::Fixed64;
+ case FieldDescriptor::TYPE_FIXED32:
+ return EProtobufType::Fixed32;
+ case FieldDescriptor::TYPE_BOOL:
+ return EProtobufType::Bool;
+ case FieldDescriptor::TYPE_STRING:
+ return EProtobufType::String;
+ case FieldDescriptor::TYPE_GROUP:
+ return std::nullopt;
+ case FieldDescriptor::TYPE_MESSAGE:
+ return EProtobufType::Message;
+ case FieldDescriptor::TYPE_BYTES:
+ return EProtobufType::Bytes;
+ case FieldDescriptor::TYPE_UINT32:
+ return EProtobufType::Uint32;
+ case FieldDescriptor::TYPE_ENUM:
+ return enumAsString ? EProtobufType::EnumString : EProtobufType::EnumInt;
+ case FieldDescriptor::TYPE_SFIXED32:
+ return EProtobufType::Sfixed32;
+ case FieldDescriptor::TYPE_SFIXED64:
+ return EProtobufType::Sfixed64;
+ case FieldDescriptor::TYPE_SINT32:
+ return EProtobufType::Sint32;
+ case FieldDescriptor::TYPE_SINT64:
+ return EProtobufType::Sint64;
+ }
+ // TODO(levysotsky): Throw exception here.
+ return std::nullopt;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ui32 TProtobufTag::GetFieldNumber() const
+{
+ return WireFormatLite::GetTagFieldNumber(WireTag);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+DEFINE_ENUM(EIntegerSignednessKind,
+ (SignedInteger)
+ (UnsignedInteger)
+ (Other)
+);
+
+} // namespace
+
+void ValidateSimpleType(
+ EProtobufType protobufType,
+ NTableClient::ESimpleLogicalValueType logicalType)
+{
+ if (logicalType == ESimpleLogicalValueType::Any) {
+ return;
+ }
+
+ using EKind = EIntegerSignednessKind;
+
+ auto getLogicalTypeKind = [] (ESimpleLogicalValueType type) {
+ switch (type) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+ return EKind::SignedInteger;
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+ return EKind::UnsignedInteger;
+ default:
+ return EKind::Other;
+ }
+ };
+
+ auto getProtobufTypeKind = [] (EProtobufType type) {
+ switch (type) {
+ case EProtobufType::Fixed64:
+ case EProtobufType::Uint64:
+ case EProtobufType::Uint32:
+ case EProtobufType::Fixed32:
+ return EKind::UnsignedInteger;
+
+ case EProtobufType::Int64:
+ case EProtobufType::Sint64:
+ case EProtobufType::Sfixed64:
+ case EProtobufType::Sfixed32:
+ case EProtobufType::Sint32:
+ case EProtobufType::Int32:
+ case EProtobufType::EnumInt:
+ return EKind::SignedInteger;
+
+ default:
+ return EKind::Other;
+ }
+ };
+
+ auto throwMismatchError = [&] (TStringBuf message) {
+ THROW_ERROR_EXCEPTION("Simple logical type %Qlv and protobuf type %Qlv mismatch: %v",
+ logicalType,
+ protobufType,
+ message);
+ };
+
+ auto validateLogicalType = [&] (auto... expectedTypes) {
+ if ((... && (logicalType != expectedTypes))) {
+ auto typeNameList = std::vector<TString>{FormatEnum(expectedTypes)...};
+ throwMismatchError(Format("expected logical type to be one of %v", typeNameList));
+ }
+ };
+
+ switch (protobufType) {
+ case EProtobufType::String:
+ validateLogicalType(ESimpleLogicalValueType::String, ESimpleLogicalValueType::Utf8);
+ return;
+
+ case EProtobufType::Bytes:
+ case EProtobufType::Message:
+ validateLogicalType(ESimpleLogicalValueType::String);
+ return;
+
+ case EProtobufType::Fixed64:
+ case EProtobufType::Uint64:
+ case EProtobufType::Uint32:
+ case EProtobufType::Fixed32:
+ case EProtobufType::Int64:
+ case EProtobufType::Sint64:
+ case EProtobufType::Sfixed64:
+ case EProtobufType::Sfixed32:
+ case EProtobufType::Sint32:
+ case EProtobufType::Int32: {
+ auto logicalTypeKind = getLogicalTypeKind(logicalType);
+ if (logicalTypeKind == EIntegerSignednessKind::Other) {
+ throwMismatchError("integer protobuf type can match only integer type in schema");
+ }
+ if (logicalTypeKind != getProtobufTypeKind(protobufType)) {
+ throwMismatchError("signedness of both types must be the same");
+ }
+ return;
+ }
+
+ case EProtobufType::Float:
+ validateLogicalType(ESimpleLogicalValueType::Float, ESimpleLogicalValueType::Double);
+ return;
+
+ case EProtobufType::Double:
+ validateLogicalType(ESimpleLogicalValueType::Double);
+ return;
+
+ case EProtobufType::Bool:
+ validateLogicalType(ESimpleLogicalValueType::Boolean);
+ return;
+
+ case EProtobufType::EnumInt:
+ if (getLogicalTypeKind(logicalType) != EKind::SignedInteger) {
+ throwMismatchError("logical type must be signed integer type");
+ }
+ return;
+ case EProtobufType::EnumString:
+ if (logicalType != ESimpleLogicalValueType::String &&
+ getLogicalTypeKind(logicalType) != EKind::SignedInteger)
+ {
+ throwMismatchError("logical type must be either signed integer type or string");
+ }
+ return;
+
+ case EProtobufType::Any:
+ // No check here: every type would be OK.
+ return;
+
+ case EProtobufType::StructuredMessage:
+ case EProtobufType::EmbeddedMessage:
+ case EProtobufType::OtherColumns:
+ case EProtobufType::Oneof:
+ throwMismatchError("protobuf type cannot match any simple type");
+ }
+ YT_ABORT();
+}
+
+static bool CanBePacked(EProtobufType type)
+{
+ auto wireFieldType = ConvertFromInternalProtobufType(type);
+ if (!wireFieldType) {
+ return false;
+ }
+ auto wireType = WireFormatLite::WireTypeForFieldType(*wireFieldType);
+ return
+ wireType == WireFormatLite::WireType::WIRETYPE_FIXED32 ||
+ wireType == WireFormatLite::WireType::WIRETYPE_FIXED64 ||
+ wireType == WireFormatLite::WireType::WIRETYPE_VARINT;
+}
+
+[[noreturn]] void ThrowSchemaMismatch(
+ TStringBuf message,
+ const TComplexTypeFieldDescriptor& descriptor,
+ const TProtobufTypeConfigPtr& protoTypeConfig)
+{
+ THROW_ERROR_EXCEPTION("Table schema and protobuf format config mismatch at %v: %v",
+ descriptor.GetDescription(),
+ message)
+ << TErrorAttribute("type_in_schema", ToString(*descriptor.GetType()))
+ << TErrorAttribute("protobuf_type", protoTypeConfig);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TType>
+void TProtobufFormatDescriptionBase<TType>::DoInit(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas)
+{
+ const bool tablesSpecified = !config->Tables.empty();
+ const bool fileDescriptorSetSpecified = !config->FileDescriptorSet.empty();
+ const bool fileDescriptorSetStructuredSpecified = config->FileDescriptorSetText.has_value();
+ if (tablesSpecified + fileDescriptorSetSpecified + fileDescriptorSetStructuredSpecified != 1) {
+ THROW_ERROR_EXCEPTION(R"(Exactly one of "tables", "file_descriptor_set" and "file_descriptor_set_text" )"
+ "must be specified in protobuf format");
+ }
+ if (tablesSpecified) {
+ InitFromProtobufSchema(config, schemas);
+ } else if (fileDescriptorSetSpecified) {
+ InitFromFileDescriptorsLegacy(config);
+ } else {
+ YT_VERIFY(fileDescriptorSetStructuredSpecified);
+ InitFromFileDescriptors(config, schemas);
+ }
+}
+
+static std::vector<const FileDescriptor*> BuildDescriptorPool(
+ const ::google::protobuf::FileDescriptorSet& fileDescriptorSet,
+ DescriptorPool* descriptorPool)
+{
+ std::vector<const FileDescriptor*> fileDescriptors;
+ TDescriptorPoolErrorCollector errorCollector;
+
+ for (const auto& fileDescriptorProto : fileDescriptorSet.file()) {
+ auto* file = descriptorPool->BuildFileCollectingErrors(
+ fileDescriptorProto,
+ &errorCollector);
+ errorCollector.ThrowOnError();
+ if (!file) {
+ THROW_ERROR_EXCEPTION("Unknown error building file %v",
+ fileDescriptorProto.name());
+ }
+
+ fileDescriptors.push_back(file);
+ }
+
+ return fileDescriptors;
+}
+
+static EProtobufType SpecialToProtobufType(ESpecialProtobufType specialType)
+{
+ switch (specialType) {
+ case ESpecialProtobufType::Any:
+ return EProtobufType::Any;
+ case ESpecialProtobufType::EnumInt:
+ return EProtobufType::EnumInt;
+ case ESpecialProtobufType::EnumString:
+ return EProtobufType::EnumString;
+ case ESpecialProtobufType::OtherColumns:
+ return EProtobufType::OtherColumns;
+ }
+ Y_FAIL();
+}
+
+class TCycleChecker
+{
+private:
+ class TGuard
+ {
+ public:
+ TGuard(TCycleChecker* checker, const Descriptor* descriptor)
+ : Checker_(checker)
+ , Descriptor_(descriptor)
+ {
+ Checker_->ActiveVertices_.insert(Descriptor_);
+ Checker_->Stack_.push(Descriptor_);
+ }
+
+ ~TGuard()
+ {
+ Checker_->ActiveVertices_.erase(Descriptor_);
+ Checker_->Stack_.pop();
+ }
+
+ private:
+ TCycleChecker* Checker_;
+ const Descriptor* Descriptor_;
+ };
+
+public:
+ [[nodiscard]] TGuard Enter(const Descriptor* descriptor)
+ {
+ if (ActiveVertices_.contains(descriptor)) {
+ Y_VERIFY(!Stack_.empty());
+ THROW_ERROR_EXCEPTION("Cyclic reference found for protobuf messages. "
+ "Consider removing %Qv flag somewhere on the cycle containing %Qv and %Qv",
+ EWrapperFieldFlag_Enum_Name(EWrapperFieldFlag::SERIALIZATION_YT),
+ Stack_.top()->full_name(),
+ descriptor->full_name());
+ }
+ return TGuard(this, descriptor);
+ }
+
+private:
+ THashSet<const Descriptor*> ActiveVertices_;
+ std::stack<const Descriptor*> Stack_;
+};
+
+class TProtobufTypeConfigBuilder
+{
+public:
+ TProtobufTypeConfigBuilder(bool enumsAsStrings)
+ : EnumsAsStrings_(enumsAsStrings)
+ , Enumerations_(GetEphemeralNodeFactory()->CreateMap())
+ { }
+
+ TProtobufTypeConfigPtr GetOrCreateTypeConfig(const Descriptor* descriptor)
+ {
+ if (auto it = DescriptorToTypeConfig_.find(descriptor); it != DescriptorToTypeConfig_.end()) {
+ return it->second;
+ }
+ auto typeConfig = CreateTypeConfig(descriptor, GetDefaultFieldOptions(descriptor));
+ DescriptorToTypeConfig_.emplace(descriptor, typeConfig);
+ return typeConfig;
+ }
+
+ IMapNodePtr GetEnumerations() const
+ {
+ return Enumerations_;
+ }
+
+private:
+ const bool EnumsAsStrings_;
+ const IMapNodePtr Enumerations_;
+ THashMap<const Descriptor*, TProtobufTypeConfigPtr> DescriptorToTypeConfig_;
+ TCycleChecker CycleChecker_;
+
+private:
+ TProtobufTypeConfigPtr CreateMapConfig(
+ const FieldDescriptor* fieldDescriptor,
+ const TProtobufFieldOptions& fieldOptions)
+ {
+ Y_VERIFY(fieldDescriptor->is_map());
+ const auto* descriptor = fieldDescriptor->message_type();
+ switch (fieldOptions.MapMode) {
+ case EProtobufMapMode::ListOfStructsLegacy:
+ return GetOrCreateTypeConfig(descriptor);
+ case EProtobufMapMode::ListOfStructs:
+ case EProtobufMapMode::Dict:
+ case EProtobufMapMode::OptionalDict:
+ return CreateTypeConfig(
+ descriptor,
+ TProtobufFieldOptions{.SerializationMode = EProtobufSerializationMode::Yt});
+ }
+ Y_FAIL();
+ }
+
+ TProtobufTypeConfigPtr CreateFieldTypeConfig(
+ const FieldDescriptor* fieldDescriptor,
+ const TProtobufFieldOptions& fieldOptions)
+ {
+ if (fieldDescriptor->type() == FieldDescriptor::TYPE_MESSAGE) {
+ switch (fieldOptions.SerializationMode) {
+ case EProtobufSerializationMode::Yt:
+ if (fieldDescriptor->is_map()) {
+ return CreateMapConfig(fieldDescriptor, fieldOptions);
+ } else {
+ return GetOrCreateTypeConfig(fieldDescriptor->message_type());
+ }
+ case EProtobufSerializationMode::Embedded: {
+ auto config = GetOrCreateTypeConfig(fieldDescriptor->message_type());
+ config->ProtoType = EProtobufType::EmbeddedMessage;
+ return config;
+ }
+ case EProtobufSerializationMode::Protobuf:
+ break;
+ // EProtobufSerializationMode::Protobuf handled later
+ }
+ }
+
+ auto typeConfig = New<TProtobufTypeConfig>();
+
+ EProtobufType type;
+ if (fieldOptions.Type) {
+ ValidateProtobufType(fieldDescriptor, *fieldOptions.Type);
+ type = SpecialToProtobufType(*fieldOptions.Type);
+ } else {
+ auto optionalType = ConvertToInternalProtobufType(fieldDescriptor->type(), EnumsAsStrings_);
+ if (!optionalType) {
+ return nullptr;
+ }
+ type = *optionalType;
+ }
+
+ typeConfig->ProtoType = type;
+
+ if (type == EProtobufType::EnumString || type == EProtobufType::EnumInt) {
+ auto enumDescriptor = fieldDescriptor->enum_type();
+ YT_VERIFY(enumDescriptor);
+ const auto& enumName = enumDescriptor->full_name();
+ auto child = Enumerations_->FindChild(enumName);
+ if (!child) {
+ Enumerations_->AddChild(enumName, ConvertToEnumMap(enumDescriptor));
+ }
+ typeConfig->EnumerationName = enumName;
+ }
+
+ return typeConfig;
+ }
+
+ TProtobufColumnConfigPtr CreateFieldConfig(
+ const FieldDescriptor* fieldDescriptor,
+ const TProtobufFieldOptions& defaultOptions)
+ {
+ auto fieldOptions = GetFieldOptions(fieldDescriptor, defaultOptions);
+ auto typeConfig = CreateFieldTypeConfig(fieldDescriptor, fieldOptions);
+ if (!typeConfig) {
+ return nullptr;
+ }
+
+ auto fieldConfig = New<TProtobufColumnConfig>();
+ fieldConfig->Name = GetColumnName(fieldDescriptor);
+ fieldConfig->FieldNumber = fieldDescriptor->number();
+ if (fieldDescriptor->is_repeated() && fieldOptions.SerializationMode != EProtobufSerializationMode::Yt) {
+ THROW_ERROR_EXCEPTION("Repeated field %Qv must have flag %Qv",
+ fieldDescriptor->full_name(),
+ EWrapperFieldFlag_Enum_Name(EWrapperFieldFlag::SERIALIZATION_YT));
+ }
+ fieldConfig->Repeated = fieldDescriptor->is_repeated();
+ fieldConfig->Packed = fieldDescriptor->is_packed();
+ fieldConfig->Type = std::move(typeConfig);
+ fieldConfig->EnumWritingMode = fieldOptions.EnumWritingMode;
+ fieldConfig->Postprocess();
+ return fieldConfig;
+ }
+
+ std::vector<TProtobufColumnConfigPtr> ProcessOneof(
+ const OneofDescriptor* oneofDescriptor,
+ const TProtobufFieldOptions& defaultFieldOptions,
+ const TProtobufOneofOptions& defaultOneofOptions)
+ {
+ std::vector<TProtobufColumnConfigPtr> fieldConfigs;
+ for (int i = 0; i < oneofDescriptor->field_count(); ++i) {
+ auto fieldConfig = CreateFieldConfig(oneofDescriptor->field(i), defaultFieldOptions);
+ if (!fieldConfig) {
+ continue;
+ }
+ fieldConfigs.push_back(std::move(fieldConfig));
+ }
+ if (fieldConfigs.empty()) {
+ THROW_ERROR_EXCEPTION("Parsing of oneof field %Qv resulted in zero variants",
+ oneofDescriptor->full_name());
+ }
+
+ auto oneofOptions = GetOneofOptions(oneofDescriptor, defaultOneofOptions);
+ switch (oneofOptions.Mode) {
+ case EProtobufOneofMode::SeparateFields:
+ return fieldConfigs;
+ case EProtobufOneofMode::Variant: {
+ auto field = New<TProtobufColumnConfig>();
+ field->Name = oneofOptions.VariantFieldName;
+ field->Type = New<TProtobufTypeConfig>();
+ field->Type->ProtoType = EProtobufType::Oneof;
+ field->Type->Fields = std::move(fieldConfigs);
+ return {field};
+ }
+ }
+ Y_FAIL();
+ }
+
+ // NB: This function does not caches the created config!
+ TProtobufTypeConfigPtr CreateTypeConfig(
+ const Descriptor* descriptor,
+ const TProtobufFieldOptions& defaultFieldOptions)
+ {
+ auto typeConfig = New<TProtobufTypeConfig>();
+ typeConfig->ProtoType = EProtobufType::StructuredMessage;
+ auto& fields = typeConfig->Fields;
+
+ auto defaultOneofOptions = GetDefaultOneofOptions(descriptor);
+
+ THashSet<const OneofDescriptor*> visitedOneofs;
+ auto guard = CycleChecker_.Enter(descriptor);
+ for (int fieldIndex = 0; fieldIndex < descriptor->field_count(); ++fieldIndex) {
+ auto fieldDescriptor = descriptor->field(fieldIndex);
+ auto oneofDescriptor = fieldDescriptor->containing_oneof();
+ if (!oneofDescriptor) {
+ auto fieldConfig = CreateFieldConfig(fieldDescriptor, defaultFieldOptions);
+ if (!fieldConfig) {
+ continue;
+ }
+ fields.push_back(std::move(fieldConfig));
+ } else if (!visitedOneofs.contains(oneofDescriptor)) {
+ auto addedFields = ProcessOneof(oneofDescriptor, defaultFieldOptions, defaultOneofOptions);
+ fields.insert(fields.end(), addedFields.begin(), addedFields.end());
+ visitedOneofs.insert(oneofDescriptor);
+ }
+ }
+
+ return typeConfig;
+ }
+};
+
+static TProtobufFormatConfigPtr CreateConfigWithTypes(
+ std::vector<const Descriptor*> descriptors,
+ bool enumsAsStrings = true)
+{
+ auto config = New<TProtobufFormatConfig>();
+ TProtobufTypeConfigBuilder builder(enumsAsStrings);
+ for (const auto* descriptor : descriptors) {
+ auto typeConfig = builder.GetOrCreateTypeConfig(descriptor);
+ auto tableConfig = New<TProtobufTableConfig>();
+ for (const auto& field : typeConfig->Fields) {
+ tableConfig->Columns.push_back(field);
+ }
+ config->Tables.push_back(std::move(tableConfig));
+ }
+ config->Enumerations = builder.GetEnumerations();
+ return config;
+}
+
+template <typename TType>
+void TProtobufFormatDescriptionBase<TType>::InitFromFileDescriptorsLegacy(
+ const TProtobufFormatConfigPtr& config)
+{
+ if (config->FileIndices.empty()) {
+ THROW_ERROR_EXCEPTION(R"("file_indices" attribute must be non empty in protobuf format)");
+ }
+ if (config->MessageIndices.size() != config->FileIndices.size()) {
+ THROW_ERROR_EXCEPTION(R"("message_indices" and "file_indices" must be of same size in protobuf format)");
+ }
+
+ ::google::protobuf::FileDescriptorSet fileDescriptorSet;
+ if (!fileDescriptorSet.ParseFromString(config->FileDescriptorSet)) {
+ THROW_ERROR_EXCEPTION(R"(Error parsing "file_descriptor_set" in protobuf config)");
+ }
+
+ DescriptorPool descriptorPool;
+ auto fileDescriptors = BuildDescriptorPool(fileDescriptorSet, &descriptorPool);
+
+ std::vector<const Descriptor*> messageDescriptors;
+ for (size_t i = 0; i < config->FileIndices.size(); ++i) {
+ if (config->FileIndices[i] >= static_cast<int>(fileDescriptors.size())) {
+ THROW_ERROR_EXCEPTION("File index is out of bound")
+ << TErrorAttribute("file_index", config->FileIndices[i])
+ << TErrorAttribute("file_count", fileDescriptors.size());
+ }
+ auto* fileDescriptor = fileDescriptors[config->FileIndices[i]];
+
+ if (config->MessageIndices[i] >= fileDescriptor->message_type_count()) {
+ THROW_ERROR_EXCEPTION("Message index is out of bound")
+ << TErrorAttribute("message_index", config->MessageIndices[i])
+ << TErrorAttribute("message_count", fileDescriptor->message_type_count());
+ }
+ messageDescriptors.push_back(fileDescriptor->message_type(config->MessageIndices[i]));
+ }
+
+ auto configWithTypes = CreateConfigWithTypes(messageDescriptors, config->EnumsAsStrings);
+ std::vector<TTableSchemaPtr> schemas(messageDescriptors.size(), New<NTableClient::TTableSchema>());
+ InitFromProtobufSchema(configWithTypes, schemas);
+}
+
+template <typename TType>
+void TProtobufFormatDescriptionBase<TType>::InitFromFileDescriptors(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas)
+{
+ if (config->TypeNames.empty()) {
+ THROW_ERROR_EXCEPTION(R"("type_names" attribute must be non empty if "file_descriptor_set_text" )"
+ "is specified");
+ }
+ YT_VERIFY(config->FileDescriptorSetText);
+
+ ::google::protobuf::FileDescriptorSet fileDescriptorSet;
+ ::google::protobuf::TextFormat::Parser parser;
+ // parser.AllowUnknownExtension(true);
+ TParserErrorCollector errorCollector;
+ parser.RecordErrorsTo(&errorCollector);
+ if (!parser.ParseFromString(*config->FileDescriptorSetText, &fileDescriptorSet)) {
+ THROW_ERROR_EXCEPTION(R"(Error parsing "file_descriptor_set_text" in protobuf config)")
+ << errorCollector.GetErrors();
+ }
+
+ DescriptorPool descriptorPool;
+ auto fileDescriptors = BuildDescriptorPool(fileDescriptorSet, &descriptorPool);
+
+ std::vector<const Descriptor*> messageDescriptors;
+ messageDescriptors.reserve(config->TypeNames.size());
+ for (const auto& typeName : config->TypeNames) {
+ auto descriptor = descriptorPool.FindMessageTypeByName(typeName);
+ if (!descriptor) {
+ THROW_ERROR_EXCEPTION("Message type %Qv not found in file descriptor set",
+ typeName);
+ }
+ messageDescriptors.push_back(descriptor);
+ }
+
+ auto configWithTypes = CreateConfigWithTypes(messageDescriptors);
+ InitFromProtobufSchema(configWithTypes, schemas);
+}
+
+template<>
+void TProtobufFormatDescriptionBase<TProtobufWriterType>::InitEmbeddedColumn(
+ int& fieldIndex,
+ const NTableClient::TTableSchemaPtr& tableSchema,
+ TProtobufTypeBuilder<TProtobufWriterType>& typeBuilder,
+ TTypePtr tableType,
+ TProtobufColumnConfigPtr columnConfig,
+ TTypePtr parent,
+ int parentEmbeddingIndex)
+{
+ auto embeddingIndex = tableType->AddEmbedding(parentEmbeddingIndex, columnConfig);
+
+ for (auto& fieldConfig: columnConfig->Type->Fields) {
+ InitColumn(fieldIndex, tableSchema, typeBuilder, tableType, fieldConfig, parent, embeddingIndex);
+ }
+}
+
+template<>
+void TProtobufFormatDescriptionBase<TProtobufParserType>::InitEmbeddedColumn(
+ int& fieldIndex,
+ const NTableClient::TTableSchemaPtr& tableSchema,
+ TProtobufTypeBuilder<TProtobufParserType>& typeBuilder,
+ TTypePtr tableType,
+ TProtobufColumnConfigPtr columnConfig,
+ TTypePtr parent,
+ int parentEmbeddingIndex)
+{
+ auto child = typeBuilder.CreateField(
+ fieldIndex,
+ columnConfig,
+ std::nullopt,
+ /* allowOtherColumns */ true,
+ true);
+
+ auto childPtr = child.get();
+
+ parent->AddChild(
+ std::nullopt,
+ std::move(child), //KMP
+ fieldIndex);
+
+ for (auto& fieldConfig: columnConfig->Type->Fields) {
+ InitColumn(fieldIndex, tableSchema, typeBuilder, tableType, fieldConfig, childPtr->Type, parentEmbeddingIndex);
+ }
+}
+
+template<typename TType>
+void TProtobufFormatDescriptionBase<TType>::InitColumn(
+ int& fieldIndex,
+ const NTableClient::TTableSchemaPtr& tableSchema,
+ TProtobufTypeBuilder<TType>& typeBuilder,
+ TTypePtr tableType,
+ TProtobufColumnConfigPtr columnConfig,
+ TTypePtr parent, // not used for Writer
+ int parentEmbeddingIndex) // not used for Parser
+{
+ if (columnConfig->Type->ProtoType == EProtobufType::EmbeddedMessage) {
+ if (columnConfig->Repeated) {
+ THROW_ERROR_EXCEPTION("Protobuf field %Qv of type %Qlv can not be repeated",
+ columnConfig->Name,
+ EProtobufType::EmbeddedMessage);
+ }
+
+ InitEmbeddedColumn(fieldIndex, tableSchema, typeBuilder, tableType, columnConfig, parent, parentEmbeddingIndex);
+ return;
+ }
+
+ auto columnSchema = tableSchema->FindColumn(columnConfig->Name);
+ TLogicalTypePtr logicalType = columnSchema ? columnSchema->LogicalType() : nullptr;
+ if (columnConfig->ProtoType == EProtobufType::OtherColumns) {
+ if (columnConfig->Repeated) {
+ THROW_ERROR_EXCEPTION("Protobuf field %Qv of type %Qlv can not be repeated",
+ columnConfig->Name,
+ EProtobufType::OtherColumns);
+ }
+ if (logicalType) {
+ THROW_ERROR_EXCEPTION("Protobuf field %Qv of type %Qlv should not match actual column in schema",
+ columnConfig->Name,
+ EProtobufType::OtherColumns);
+ }
+ }
+
+ std::optional<TComplexTypeFieldDescriptor> maybeDescriptor;
+ if (logicalType) {
+ YT_VERIFY(columnSchema);
+ maybeDescriptor = TComplexTypeFieldDescriptor(*columnSchema);
+ }
+
+ bool needSchema = columnConfig->Repeated
+ || columnConfig->ProtoType == EProtobufType::StructuredMessage
+ || columnConfig->ProtoType == EProtobufType::Oneof;
+ if (!logicalType && needSchema) {
+ if (columnConfig->FieldNumber) {
+ // Ignore missing column to facilitate schema evolution.
+ tableType->IgnoreChild(maybeDescriptor, *columnConfig->FieldNumber);
+ return;
+ }
+ THROW_ERROR_EXCEPTION(
+ "Field %Qv of type %Qlv requires a corresponding schematized column",
+ columnConfig->Name,
+ columnConfig->ProtoType);
+ }
+
+ parent->AddChild(
+ maybeDescriptor,
+ typeBuilder.CreateField(
+ fieldIndex,
+ columnConfig,
+ maybeDescriptor,
+ true),
+ fieldIndex,
+ parentEmbeddingIndex);
+ ++fieldIndex;
+}
+
+template <typename TType>
+void TProtobufFormatDescriptionBase<TType>::InitFromProtobufSchema(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas)
+{
+ if (config->Enumerations) {
+ const auto& enumerationConfigMap = config->Enumerations;
+ for (const auto& [name, field] : enumerationConfigMap->GetChildren()) {
+ if (field->GetType() != ENodeType::Map) {
+ THROW_ERROR_EXCEPTION(R"(Invalid enumeration specification type: expected "map", found %Qlv)",
+ field->GetType());
+ }
+ const auto& enumerationConfig = field->AsMap();
+ EnumerationDescriptionMap_.emplace(name, CreateEnumerationMap(name, enumerationConfig));
+ }
+ }
+
+ const auto& tableConfigs = config->Tables;
+ if (tableConfigs.size() < schemas.size()) {
+ THROW_ERROR_EXCEPTION("Number of schemas is greater than number of tables in protobuf config: %v > %v",
+ schemas.size(),
+ tableConfigs.size());
+ }
+
+ auto typeBuilder = TProtobufTypeBuilder<TType>(EnumerationDescriptionMap_);
+
+ for (size_t tableIndex = 0; tableIndex != schemas.size(); ++tableIndex) {
+ auto tableType = New<TType>();
+ tableType->ProtoType = EProtobufType::StructuredMessage;
+
+ int fieldIndex = 0;
+ const auto& tableConfig = tableConfigs[tableIndex];
+ const auto& tableSchema = schemas[tableIndex];
+
+ for (const auto& columnConfig : tableConfig->Columns) {
+ InitColumn(fieldIndex, tableSchema, typeBuilder, tableType, columnConfig, tableType, -1);
+ }
+
+ AddTable(std::move(tableType));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TType>
+TProtobufTypeBuilder<TType>::TProtobufTypeBuilder(const THashMap<TString, TEnumerationDescription>& enumerations)
+ : Enumerations_(enumerations)
+{ }
+
+template <typename TType>
+typename TProtobufTypeBuilder<TType>::TFieldPtr TProtobufTypeBuilder<TType>::CreateField(
+ int structFieldIndex,
+ const TProtobufColumnConfigPtr& columnConfig,
+ std::optional<TComplexTypeFieldDescriptor> maybeDescriptor,
+ bool allowOtherColumns,
+ bool allowEmbedded)
+{
+ const auto& typeConfig = columnConfig->Type;
+ if (!allowEmbedded && typeConfig->ProtoType == EProtobufType::EmbeddedMessage) {
+ THROW_ERROR_EXCEPTION("embedded_message inside of structured_message is not allowed");
+ }
+
+ if (!allowOtherColumns && columnConfig->ProtoType == EProtobufType::OtherColumns) {
+ YT_VERIFY(maybeDescriptor);
+ ThrowSchemaMismatch(
+ "protobuf field of type \"other_columns\" is not allowed inside complex types",
+ *maybeDescriptor,
+ typeConfig);
+ }
+
+ if (columnConfig->Packed && !CanBePacked(typeConfig->ProtoType)) {
+ YT_VERIFY(maybeDescriptor);
+ ThrowSchemaMismatch(
+ Format("packed protobuf field must have primitive numeric type, got %Qlv", typeConfig->ProtoType),
+ *maybeDescriptor,
+ typeConfig);
+ }
+
+ bool optional;
+ if (maybeDescriptor) {
+ if (maybeDescriptor->GetType()->GetMetatype() == ELogicalMetatype::Optional) {
+ maybeDescriptor = maybeDescriptor->OptionalElement();
+ optional = true;
+ } else {
+ optional = false;
+ }
+ } else {
+ optional = true;
+ }
+
+ if (columnConfig->Repeated) {
+ YT_VERIFY(maybeDescriptor);
+ if (maybeDescriptor->GetType()->GetMetatype() == ELogicalMetatype::Dict) {
+ // Do nothing, this case will be processed in the following switch.
+ } else if (maybeDescriptor->GetType()->GetMetatype() == ELogicalMetatype::List) {
+ maybeDescriptor = maybeDescriptor->ListElement();
+ } else {
+ ThrowSchemaMismatch(
+ Format("repeated field must correspond to list or dict, got %Qlv", maybeDescriptor->GetType()->GetMetatype()),
+ *maybeDescriptor,
+ typeConfig);
+ }
+ }
+
+ auto field = std::make_unique<TField>();
+
+ field->Name = columnConfig->Name;
+ field->Repeated = columnConfig->Repeated;
+ field->Packed = columnConfig->Packed;
+ field->StructFieldIndex = structFieldIndex;
+ field->Type = FindOrCreateType(
+ typeConfig,
+ maybeDescriptor,
+ optional,
+ columnConfig->Repeated);
+
+ if constexpr (IsWriter) {
+ field->EnumWritingMode = columnConfig->EnumWritingMode;
+ }
+
+ if (auto wireFieldType = ConvertFromInternalProtobufType(field->Type->ProtoType)) {
+ InitTag(*field, *wireFieldType, columnConfig);
+ }
+
+ return field;
+}
+
+template <typename TType>
+typename TProtobufTypeBuilder<TType>::TTypePtr TProtobufTypeBuilder<TType>::FindOrCreateType(
+ const TProtobufTypeConfigPtr& typeConfig,
+ std::optional<TComplexTypeFieldDescriptor> maybeDescriptor,
+ bool optional,
+ bool repeated)
+{
+ // XXX(levysotsky): Currently we always create new types.
+ // We may want to deduplicate them too.
+ auto type = New<TType>();
+
+ type->ProtoType = typeConfig->ProtoType;
+ type->Optional = optional;
+
+ if (typeConfig->ProtoType == EProtobufType::EnumString && !typeConfig->EnumerationName) {
+ THROW_ERROR_EXCEPTION("Invalid format config: missing \"enumeration_name\" for %Qlv type",
+ EProtobufType::EnumString);
+ }
+ if (typeConfig->EnumerationName) {
+ auto it = Enumerations_.find(*typeConfig->EnumerationName);
+ if (it == Enumerations_.end()) {
+ THROW_ERROR_EXCEPTION("Invalid format config: cannot find enumeration with name %Qv",
+ *typeConfig->EnumerationName);
+ }
+ type->EnumerationDescription = &it->second;
+ }
+
+ if (!maybeDescriptor) {
+ return type;
+ }
+
+ auto descriptor = std::move(*maybeDescriptor);
+
+ const auto& logicalType = descriptor.GetType();
+ YT_VERIFY(logicalType);
+
+ // list<optional<any>> is allowed.
+ if (repeated &&
+ typeConfig->ProtoType == EProtobufType::Any &&
+ logicalType->GetMetatype() == ELogicalMetatype::Optional &&
+ logicalType->GetElement()->GetMetatype() == ELogicalMetatype::Simple &&
+ logicalType->GetElement()->AsSimpleTypeRef().GetElement() == ESimpleLogicalValueType::Any)
+ {
+ return type;
+ }
+
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Struct:
+ if (typeConfig->ProtoType != EProtobufType::StructuredMessage) {
+ ThrowSchemaMismatch(
+ Format("expected \"structured_message\" protobuf type, got %Qlv", typeConfig->ProtoType),
+ descriptor,
+ typeConfig);
+ }
+ VisitStruct(type, typeConfig, descriptor);
+ return type;
+ case ELogicalMetatype::VariantStruct:
+ if (typeConfig->ProtoType != EProtobufType::Oneof) {
+ ThrowSchemaMismatch(
+ Format("expected \"oneof\" protobuf type, got %Qlv", typeConfig->ProtoType),
+ descriptor,
+ typeConfig);
+ }
+ VisitStruct(type, typeConfig, descriptor);
+ return type;
+ case ELogicalMetatype::Dict:
+ YT_VERIFY(repeated);
+ VisitDict(type, typeConfig, descriptor);
+ return type;
+ case ELogicalMetatype::Simple:
+ ValidateSimpleType(type->ProtoType, logicalType->AsSimpleTypeRef().GetElement());
+ return type;
+ default:
+ ThrowSchemaMismatch(
+ Format("unexpected logical metatype %Qlv", logicalType->GetMetatype()),
+ descriptor,
+ typeConfig);
+ }
+ YT_ABORT();
+}
+
+template <typename TType>
+void TProtobufTypeBuilder<TType>:: VisitStruct(
+ const TTypePtr& type,
+ const TProtobufTypeConfigPtr& typeConfig,
+ TComplexTypeFieldDescriptor descriptor)
+{
+ YT_VERIFY(
+ type->ProtoType == EProtobufType::StructuredMessage ||
+ type->ProtoType == EProtobufType::Oneof);
+
+ const auto isOneof = (type->ProtoType == EProtobufType::Oneof);
+
+ THashMap<TStringBuf, TProtobufColumnConfigPtr> nameToConfig;
+ for (const auto& config : typeConfig->Fields) {
+ auto inserted = nameToConfig.emplace(config->Name, config).second;
+ if (!inserted) {
+ ThrowSchemaMismatch(
+ Format("multiple fields with same name (%Qv) are forbidden", config->Name),
+ descriptor,
+ typeConfig);
+ }
+ }
+
+ auto processFieldMissingFromConfig = [&] (const TStructField& structField) {
+ if (isOneof) {
+ // It is OK, we will ignore this alternative when writing.
+ return;
+ }
+ if (structField.Type->GetMetatype() != ELogicalMetatype::Optional && !AreNonOptionalMissingFieldsAllowed) {
+ ThrowSchemaMismatch(
+ Format("non-optional field %Qv in schema is missing from protobuf config", structField.Name),
+ descriptor,
+ typeConfig);
+ }
+ };
+
+ const auto& structFields = descriptor.GetType()->GetFields();
+ if (!isOneof) {
+ type->StructFieldCount = static_cast<int>(structFields.size());
+ }
+ for (int fieldIndex = 0; fieldIndex != static_cast<int>(structFields.size()); ++fieldIndex) {
+ const auto& structField = structFields[fieldIndex];
+ auto configIt = nameToConfig.find(structField.Name);
+ if (configIt == nameToConfig.end()) {
+ processFieldMissingFromConfig(structField);
+ continue;
+ }
+ auto childDescriptor = isOneof
+ ? descriptor.VariantStructField(fieldIndex)
+ : descriptor.StructField(fieldIndex);
+ if (isOneof && childDescriptor.GetType()->IsNullable()) {
+ THROW_ERROR_EXCEPTION("Optional variant field %Qv can not match oneof field in protobuf format",
+ childDescriptor.GetDescription());
+ }
+ type->AddChild(
+ descriptor,
+ CreateField(fieldIndex, configIt->second, childDescriptor),
+ fieldIndex);
+ nameToConfig.erase(configIt);
+ }
+
+ for (const auto& [name, childConfig] : nameToConfig) {
+ if (childConfig->FieldNumber) {
+ type->IgnoreChild(descriptor, *childConfig->FieldNumber);
+ }
+ }
+}
+
+template <typename TType>
+void TProtobufTypeBuilder<TType>::VisitDict(
+ const TTypePtr& type,
+ const TProtobufTypeConfigPtr& typeConfig,
+ TComplexTypeFieldDescriptor descriptor)
+{
+ if (type->ProtoType != EProtobufType::StructuredMessage) {
+ ThrowSchemaMismatch(
+ Format("expected protobuf field of type %Qlv to match %Qlv type in schema",
+ EProtobufType::StructuredMessage,
+ ELogicalMetatype::Dict),
+ descriptor,
+ typeConfig);
+ }
+ const auto& fields = typeConfig->Fields;
+ if (fields.size() != 2 ||
+ fields[0]->Name != "key" ||
+ fields[1]->Name != "value")
+ {
+ ThrowSchemaMismatch(
+ Format(
+ "expected protobuf message with exactly fields \"key\" and \"value\" "
+ "to match %Qlv type in schema",
+ ELogicalMetatype::Dict),
+ descriptor,
+ typeConfig);
+ }
+ type->StructFieldCount = 2;
+ const auto& keyConfig = fields[0];
+ const auto& valueConfig = fields[1];
+ type->AddChild(
+ descriptor,
+ CreateField(0, keyConfig, descriptor.DictKey()),
+ /* fieldIndex */ 0);
+ type->AddChild(
+ descriptor,
+ CreateField(1, valueConfig, descriptor.DictValue()),
+ /* fieldIndex */ 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+static T& ResizeAndGetElement(std::vector<T>& vector, int index, const T& fill = {})
+{
+ if (index >= static_cast<int>(vector.size())) {
+ vector.resize(index + 1, fill);
+ }
+ return vector[index];
+}
+
+void InitTag(
+ TProtobufTag& field,
+ WireFormatLite::FieldType wireFieldType,
+ const TProtobufColumnConfigPtr& columnConfig)
+{
+ YT_VERIFY(columnConfig->FieldNumber);
+ field.TagSize = WireFormatLite::TagSize(*columnConfig->FieldNumber, wireFieldType);
+ WireFormatLite::WireType wireType;
+ if (columnConfig->Packed) {
+ wireType = WireFormatLite::WireType::WIRETYPE_LENGTH_DELIMITED;
+ } else {
+ wireType = WireFormatLite::WireTypeForFieldType(wireFieldType);
+ }
+ field.WireTag = WireFormatLite::MakeTag(*columnConfig->FieldNumber, wireType);
+}
+
+int TProtobufWriterType::AddEmbedding(
+ int parentEmbeddingIndex,
+ const TProtobufColumnConfigPtr& embeddingConfig)
+{
+ int myEmbeddingIndex = std::ssize(Embeddings);
+ auto& embedding = Embeddings.emplace_back();
+
+ embedding.ParentEmbeddingIndex = parentEmbeddingIndex;
+
+ auto wireFieldType = static_cast<WireFormatLite::FieldType>(::google::protobuf::FieldDescriptor::TYPE_MESSAGE);
+ InitTag(embedding, wireFieldType, embeddingConfig);
+
+ return myEmbeddingIndex;
+}
+
+void TProtobufWriterType::AddChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& /* descriptor */,
+ std::unique_ptr<TProtobufWriterFieldDescription> child,
+ std::optional<int> fieldIndex,
+ std::optional<int> parentEmbeddingIndex)
+{
+ if (parentEmbeddingIndex.has_value()) {
+ child->ParentEmbeddingIndex = parentEmbeddingIndex.value();
+ }
+ if (ProtoType == EProtobufType::Oneof) {
+ YT_VERIFY(fieldIndex);
+ ResizeAndGetElement(AlternativeToChildIndex_, *fieldIndex, InvalidChildIndex) = Children.size();
+ }
+ Children.push_back(std::move(child));
+}
+
+void TProtobufWriterType::IgnoreChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& /* descriptor */,
+ int /* fieldNumber */)
+{ }
+
+const TProtobufWriterFieldDescription* TProtobufWriterType::FindAlternative(int alternativeIndex) const
+{
+ if (alternativeIndex >= static_cast<int>(AlternativeToChildIndex_.size())) {
+ return nullptr;
+ }
+ if (AlternativeToChildIndex_[alternativeIndex] == InvalidChildIndex) {
+ return nullptr;
+ }
+ return Children[AlternativeToChildIndex_[alternativeIndex]].get();
+}
+
+void TProtobufWriterFormatDescription::AddTable(TProtobufWriterTypePtr tableType)
+{
+ auto& table = Tables_.emplace_back();
+ table.Type = std::move(tableType);
+ for (const auto& column : table.Type->Children) {
+ auto [it, inserted] = table.Columns.emplace(column->Name, column.get());
+ if (!inserted) {
+ THROW_ERROR_EXCEPTION("Multiple fields with same column name %Qv are forbidden in protobuf format",
+ column->Name)
+ << TErrorAttribute("table_index", std::ssize(Tables_) - 1);
+ }
+ if (column->Type->ProtoType == EProtobufType::OtherColumns) {
+ table.OtherColumnsField = column.get();
+ }
+ }
+ table.Embeddings = table.Type->Embeddings;
+}
+
+const TProtobufWriterFormatDescription::TTableDescription&
+TProtobufWriterFormatDescription::GetTableDescription(int tableIndex) const
+{
+ if (Y_UNLIKELY(tableIndex >= std::ssize(Tables_))) {
+ THROW_ERROR_EXCEPTION("Table with index %v is missing in format description",
+ tableIndex);
+ }
+ return Tables_[tableIndex];
+}
+
+const TProtobufWriterFieldDescription* TProtobufWriterFormatDescription::FindField(
+ int tableIndex,
+ int fieldIndex,
+ const TNameTablePtr& nameTable) const
+{
+ const auto& table = GetTableDescription(tableIndex);
+ auto currentSize = std::ssize(table.FieldIndexToDescription);
+ if (currentSize <= fieldIndex) {
+ table.FieldIndexToDescription.reserve(fieldIndex + 1);
+ for (int i = currentSize; i <= fieldIndex; ++i) {
+ YT_VERIFY(std::ssize(table.FieldIndexToDescription) == i);
+ auto fieldName = nameTable->GetName(i);
+ auto it = table.Columns.find(fieldName);
+ if (it == table.Columns.end()) {
+ table.FieldIndexToDescription.push_back(nullptr);
+ } else {
+ table.FieldIndexToDescription.emplace_back(it->second);
+ }
+ }
+ }
+ return table.FieldIndexToDescription[fieldIndex];
+}
+
+void TProtobufWriterFormatDescription::Init(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas)
+{
+ DoInit(config, schemas);
+}
+
+int TProtobufWriterFormatDescription::GetTableCount() const
+{
+ return std::ssize(Tables_);
+}
+
+const TProtobufWriterFieldDescription* TProtobufWriterFormatDescription::FindOtherColumnsField(int tableIndex) const
+{
+ return GetTableDescription(tableIndex).OtherColumnsField;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int TProtobufParserType::AddEmbedding(
+ int parentEmbeddingIndex,
+ const TProtobufColumnConfigPtr& embeddingConfig)
+{
+ (void) parentEmbeddingIndex;
+ (void) embeddingConfig;
+ return 0;
+}
+
+void TProtobufParserType::AddChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ std::unique_ptr<TProtobufParserFieldDescription> child,
+ std::optional<int> fieldIndex,
+ std::optional<int> embeddingIndex)
+{
+ Y_UNUSED(embeddingIndex);
+ if (child->Type->ProtoType == EProtobufType::Oneof) {
+ if (ProtoType == EProtobufType::Oneof) {
+ YT_VERIFY(descriptor);
+ THROW_ERROR_EXCEPTION("Invalid protobuf format: oneof group %Qv cannot have a oneof child %Qv",
+ descriptor->GetDescription(),
+ child->Name);
+ }
+ YT_VERIFY(!child->Type->Field);
+ child->Type->Field = child.get();
+ for (auto& grandChild : child->Type->Children) {
+ grandChild->StructFieldIndex = child->StructFieldIndex;
+ AddChild(descriptor, std::move(grandChild), /* fieldIndex */ {});
+ }
+ for (auto fieldNumber : child->Type->IgnoredChildFieldNumbers_) {
+ IgnoreChild(descriptor, fieldNumber);
+ }
+ child->Type->Children.clear();
+ child->Type->IgnoredChildFieldNumbers_.clear();
+ // YT_VERIFY(!child->Type->ContainingType);
+ // child->Type->ContainingType = this;
+ OneofDescriptions_.push_back(std::move(child));
+ } else if (ProtoType == EProtobufType::Oneof) {
+ YT_VERIFY(fieldIndex);
+ child->AlternativeIndex = *fieldIndex;
+ YT_VERIFY(!child->ContainingOneof);
+ child->ContainingOneof = this;
+ Children.push_back(std::move(child));
+ } else {
+ SetChildIndex(descriptor, child->GetFieldNumber(), Children.size());
+ Children.push_back(std::move(child));
+ }
+}
+
+void TProtobufParserType::IgnoreChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ int fieldNumber)
+{
+ SetChildIndex(descriptor, fieldNumber, IgnoredChildIndex);
+ IgnoredChildFieldNumbers_.push_back(fieldNumber);
+}
+
+void TProtobufParserType::SetChildIndex(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ int fieldNumber,
+ int childIndex,
+ TFieldNumberToChildIndex* store)
+{
+ if (store == nullptr) {
+ store = &FieldNumberToChildIndex_;
+ }
+ bool isNew = false;
+ if (fieldNumber < MaxFieldNumberVectorSize) {
+ auto& childIndexRef = ResizeAndGetElement(store->FieldNumberToChildIndexVector, fieldNumber, InvalidChildIndex);
+ isNew = (childIndexRef == InvalidChildIndex);
+ childIndexRef = childIndex;
+ } else {
+ isNew = store->FieldNumberToChildIndexMap.emplace(fieldNumber, childIndex).second;
+ }
+ if (!isNew) {
+ THROW_ERROR_EXCEPTION("Invalid protobuf format: duplicate field number %v (child of %Qv)",
+ fieldNumber,
+ descriptor ? descriptor->GetDescription() : "<root>");
+ }
+}
+
+void TProtobufParserType::SetEmbeddedChildIndex(
+ int fieldNumber,
+ int childIndex)
+{
+ SetChildIndex(std::nullopt, fieldNumber, childIndex, &FieldNumberToEmbeddedChildIndex_);
+}
+
+std::optional<int> TProtobufParserType::FieldNumberToChildIndex(int fieldNumber, const TFieldNumberToChildIndex* store) const
+{
+ if (store == nullptr) {
+ store = &FieldNumberToChildIndex_;
+ }
+ int index;
+ if (fieldNumber < std::ssize(store->FieldNumberToChildIndexVector)) {
+ index = store->FieldNumberToChildIndexVector[fieldNumber];
+ if (Y_UNLIKELY(index == InvalidChildIndex)) {
+ THROW_ERROR_EXCEPTION("Unexpected field number %v",
+ fieldNumber);
+ }
+ } else {
+ auto it = store->FieldNumberToChildIndexMap.find(fieldNumber);
+ if (Y_UNLIKELY(it == store->FieldNumberToChildIndexMap.end())) {
+ THROW_ERROR_EXCEPTION("Unexpected field number %v",
+ fieldNumber);
+ }
+ index = it->second;
+ }
+ if (index == IgnoredChildIndex) {
+ return {};
+ }
+ return index;
+}
+
+std::optional<int> TProtobufParserType::FieldNumberToEmbeddedChildIndex(int fieldNumber) const
+{
+ return FieldNumberToChildIndex(fieldNumber, &FieldNumberToEmbeddedChildIndex_);
+}
+
+void TProtobufParserFormatDescription::Init(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas)
+{
+ DoInit(config, schemas);
+}
+
+const TProtobufParserTypePtr& TProtobufParserFormatDescription::GetTableType() const
+{
+ return TableType_;
+}
+
+static int Process(
+ std::vector<std::pair<ui16, TProtobufParserFieldDescription*>>& ids,
+ int globalChildIndex,
+ const TNameTablePtr& nameTable,
+ const TProtobufParserTypePtr parent,
+ const std::unique_ptr<TProtobufParserFieldDescription>& child)
+{
+ if (child->Type->ProtoType == EProtobufType::EmbeddedMessage) {
+ for (const auto& grandChild: child->Type->Children) {
+ globalChildIndex = Process(ids, globalChildIndex, nameTable, child->Type, grandChild);
+ }
+ } else {
+ parent->SetEmbeddedChildIndex(child->GetFieldNumber(), globalChildIndex++);
+ auto name = child->Name;
+ if (child->IsOneofAlternative()) {
+ YT_VERIFY(child->ContainingOneof && child->ContainingOneof->Field);
+ name = child->ContainingOneof->Field->Name;
+ }
+ ids.emplace_back(static_cast<ui16>(nameTable->GetIdOrRegisterName(name)), child.get());
+ }
+ return globalChildIndex;
+}
+
+std::vector<std::pair<ui16, TProtobufParserFieldDescription*>> TProtobufParserFormatDescription::CreateRootChildColumnIds(const TNameTablePtr& nameTable) const
+{
+ std::vector<std::pair<ui16, TProtobufParserFieldDescription*>> ids;
+ int globalChildIndex = 0;
+
+ for (const auto& child: TableType_->Children) {
+ globalChildIndex = Process(ids, globalChildIndex, nameTable, TableType_, child);
+ }
+
+ return ids;
+}
+
+void TProtobufParserFormatDescription::AddTable(TProtobufParserTypePtr tableType)
+{
+ YT_VERIFY(!TableType_);
+ TableType_ = std::move(tableType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/protobuf.h b/yt/yt/client/formats/protobuf.h
new file mode 100644
index 0000000000..accf1e8b71
--- /dev/null
+++ b/yt/yt/client/formats/protobuf.h
@@ -0,0 +1,384 @@
+#pragma once
+
+#include "config.h"
+#include "private.h"
+
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEnumerationDescription
+{
+public:
+ explicit TEnumerationDescription(const TString& name);
+
+ const TString& GetEnumerationName() const;
+
+ const TString& GetValueName(i32 value) const;
+ const TString* TryGetValueName(i32 value) const;
+
+ i32 GetValue(TStringBuf name) const;
+ std::optional<i32> TryGetValue(TStringBuf name) const;
+
+ void Add(TString name, i32 value);
+
+private:
+ THashMap<TString, i32> NameToValue_;
+ THashMap<i32, TString> ValueToName_;
+ TString Name_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TProtobufTag
+{
+ ui64 WireTag = 0;
+ size_t TagSize = 0;
+ // Extracts field number from |WireTag|.
+ ui32 GetFieldNumber() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TProtobufFieldDescriptionBase
+ : public TProtobufTag
+{
+ TString Name;
+
+ // Index of field inside struct (for fields corresponding to struct fields in schema).
+ int StructFieldIndex = 0;
+
+ bool Repeated = false;
+
+ // Is a repeated field packed (i.e. it is encoded as `<tag> <length> <value1> ... <valueK>`)?
+ bool Packed = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TProtobufTypeBase
+ : public TRefCounted
+{
+ EProtobufType ProtoType = EProtobufType::Int64;
+
+ // Is the corresponding type in schema optional?
+ bool Optional = true;
+
+ // Number of fields in struct in schema (only for |Type == StructuredMessage|).
+ int StructFieldCount = 0;
+
+ const TEnumerationDescription* EnumerationDescription = nullptr;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TProtobufWriterEmbeddingDescription
+ : public TProtobufTag
+{
+ static const int InvalidIndex = -1;
+ // Index of wrapping proto message
+ int ParentEmbeddingIndex = InvalidIndex;
+};
+
+class TProtobufWriterFieldDescription
+ : public TProtobufFieldDescriptionBase
+{
+public:
+ // Index of wrapping proto message
+ int ParentEmbeddingIndex = TProtobufWriterEmbeddingDescription::InvalidIndex;
+
+ // Whether to fail on writing unknown value to the enumeration.
+ EProtobufEnumWritingMode EnumWritingMode = EProtobufEnumWritingMode::CheckValues;
+
+ TProtobufWriterTypePtr Type;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufWriterType
+ : public TProtobufTypeBase
+{
+public:
+ int AddEmbedding(
+ int parentParentEmbeddingIndex,
+ const TProtobufColumnConfigPtr& embeddingConfig);
+
+ void AddChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ std::unique_ptr<TProtobufWriterFieldDescription> childType,
+ std::optional<int> fieldIndex,
+ std::optional<int> parentParentEmbeddingIndex = std::nullopt);
+
+ void IgnoreChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ int fieldNumber);
+
+ const TProtobufWriterFieldDescription* FindAlternative(int alternativeIndex) const;
+
+public:
+ std::vector<std::unique_ptr<TProtobufWriterFieldDescription>> Children;
+ std::vector<TProtobufWriterEmbeddingDescription> Embeddings;
+
+private:
+ std::vector<int> AlternativeToChildIndex_;
+ static constexpr int InvalidChildIndex = -1;
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufWriterType)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufParserType
+ : public TProtobufTypeBase
+{
+private:
+ struct TFieldNumberToChildIndex
+ {
+ std::vector<int> FieldNumberToChildIndexVector;
+ THashMap<int, int> FieldNumberToChildIndexMap;
+ };
+
+public:
+ int AddEmbedding(
+ int parentParentEmbeddingIndex,
+ const TProtobufColumnConfigPtr& embeddingConfig);
+
+ void AddChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ std::unique_ptr<TProtobufParserFieldDescription> childType,
+ std::optional<int> fieldIndex,
+ std::optional<int> parentParentEmbeddingIndex = std::nullopt);
+
+ void IgnoreChild(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ int fieldNumber);
+
+ // Returns std::nullopt iff the field is ignored (missing from table schema).
+ // Throws an exception iff the field number is unknown.
+ std::optional<int> FieldNumberToChildIndex(int fieldNumber, const TFieldNumberToChildIndex* store = nullptr) const;
+ std::optional<int> FieldNumberToEmbeddedChildIndex(int fieldNumber) const;
+
+ void SetEmbeddedChildIndex(
+ int fieldNumber,
+ int childIndex);
+private:
+ void SetChildIndex(
+ const std::optional<NTableClient::TComplexTypeFieldDescriptor>& descriptor,
+ int fieldNumber,
+ int childIndex,
+ TFieldNumberToChildIndex* store = nullptr);
+
+public:
+ // For oneof types -- corresponding oneof description.
+ const TProtobufParserFieldDescription* Field = nullptr;
+
+ std::vector<std::unique_ptr<TProtobufParserFieldDescription>> Children;
+
+private:
+ static constexpr int InvalidChildIndex = -1;
+ static constexpr int IgnoredChildIndex = -2;
+ static constexpr int MaxFieldNumberVectorSize = 256;
+
+ std::vector<int> IgnoredChildFieldNumbers_;
+ std::vector<std::unique_ptr<TProtobufParserFieldDescription>> OneofDescriptions_;
+
+ TFieldNumberToChildIndex FieldNumberToChildIndex_;
+ TFieldNumberToChildIndex FieldNumberToEmbeddedChildIndex_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufParserType)
+
+
+class TProtobufParserFieldDescription
+ : public TProtobufFieldDescriptionBase
+{
+public:
+ bool IsOneofAlternative() const
+ {
+ return AlternativeIndex.has_value();
+ }
+
+public:
+ TProtobufParserTypePtr Type;
+
+ // For oneof members -- index of alternative.
+ std::optional<int> AlternativeIndex;
+ // For oneof members -- containing oneof type.
+ const TProtobufParserType* ContainingOneof;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TType>
+class TProtobufTypeBuilder
+{
+public:
+ static constexpr bool IsWriter = std::is_same_v<TType, TProtobufWriterType>;
+ static constexpr bool AreNonOptionalMissingFieldsAllowed = IsWriter;
+
+ using TTypePtr = NYT::TIntrusivePtr<TType>;
+ using TField = std::conditional_t<IsWriter, TProtobufWriterFieldDescription, TProtobufParserFieldDescription>;
+ using TFieldPtr = std::unique_ptr<TField>;
+
+ TProtobufTypeBuilder(const THashMap<TString, TEnumerationDescription>& enumerations);
+
+ TFieldPtr CreateField(
+ int structFieldIndex,
+ const TProtobufColumnConfigPtr& columnConfig,
+ std::optional<NTableClient::TComplexTypeFieldDescriptor> maybeDescriptor,
+ bool allowOtherColumns = false,
+ bool allowEmbedded = false);
+
+private:
+ const THashMap<TString, TEnumerationDescription>& Enumerations_;
+
+private:
+ // Traverse type config, matching it with type descriptor from schema.
+ //
+ // Matching of the type config and type descriptor is performed by the following rules:
+ // * Field of simple type matches simple type T "naturally"
+ // * Repeated field matches List<T> iff corresponding non-repeated field matches T and T is not Optional<...>
+ // (List<Optional<Any>> is allowed as an exception)
+ // * Non-repeated field matches Optional<T> iff it matches T and T is not Optional<...>
+ // * StructuredMessage field matches Struct<Name1: Type1, ..., NameN: TypeN> iff
+ // - the field has subfields whose names are in set {Name1, ..., NameN}
+ // - the subfield with name NameK matches TypeK
+ // - if |NonOptionalMissingFieldsAllowed()| is |false|,
+ // for each name NameK missing from subfields TypeK is Optional<...>
+ TTypePtr FindOrCreateType(
+ const TProtobufTypeConfigPtr& typeConfig,
+ std::optional<NTableClient::TComplexTypeFieldDescriptor> maybeDescriptor,
+ bool optional,
+ bool repeated);
+
+ void VisitStruct(
+ const TTypePtr& type,
+ const TProtobufTypeConfigPtr& typeConfig,
+ NTableClient::TComplexTypeFieldDescriptor descriptor);
+
+ void VisitDict(
+ const TTypePtr& type,
+ const TProtobufTypeConfigPtr& typeConfig,
+ NTableClient::TComplexTypeFieldDescriptor descriptor);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TType>
+class TProtobufFormatDescriptionBase
+ : public TRefCounted
+{
+ static constexpr bool IsWriter = std::is_same_v<TType, TProtobufWriterType>;
+ using TTypePtr = NYT::TIntrusivePtr<TType>;
+
+protected:
+ void DoInit(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas);
+
+private:
+ THashMap<TString, TEnumerationDescription> EnumerationDescriptionMap_;
+
+private:
+ virtual void AddTable(NYT::TIntrusivePtr<TType> tableType) = 0;
+
+ void InitFromFileDescriptorsLegacy(const TProtobufFormatConfigPtr& config);
+
+ void InitFromFileDescriptors(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas);
+
+ void InitEmbeddedColumn(
+ int& fieldIndex,
+ const NTableClient::TTableSchemaPtr& tableSchema,
+ TProtobufTypeBuilder<TType>& typeBuilder,
+ TTypePtr tableType,
+ TProtobufColumnConfigPtr columnConfig,
+ TTypePtr parent,
+ int parentParentEmbeddingIndex);
+
+ void InitColumn(
+ int& fieldIndex,
+ const NTableClient::TTableSchemaPtr& tableSchema,
+ TProtobufTypeBuilder<TType>& typeBuilder,
+ TTypePtr tableType,
+ TProtobufColumnConfigPtr columnConfig,
+ TTypePtr parent,
+ int parentParentEmbeddingIndex);
+
+ void InitFromProtobufSchema(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufWriterFormatDescription
+ : public TProtobufFormatDescriptionBase<TProtobufWriterType>
+{
+public:
+ void Init(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas);
+
+ const TProtobufWriterFieldDescription* FindField(
+ int tableIndex,
+ int fieldIndex,
+ const NTableClient::TNameTablePtr& nameTable) const;
+
+ int GetTableCount() const;
+
+ const TProtobufWriterFieldDescription* FindOtherColumnsField(int tableIndex) const;
+
+private:
+ void AddTable(TProtobufWriterTypePtr tableType) override;
+
+private:
+ struct TTableDescription
+ {
+ TProtobufWriterTypePtr Type;
+ THashMap<TString, const TProtobufWriterFieldDescription*> Columns;
+ std::vector<TProtobufWriterEmbeddingDescription> Embeddings;
+
+ // Cached data.
+ mutable std::vector<const TProtobufWriterFieldDescription*> FieldIndexToDescription;
+ mutable const TProtobufWriterFieldDescription* OtherColumnsField = nullptr;
+ };
+
+public:
+ const TTableDescription& GetTableDescription(int tableIndex) const;
+
+private:
+ std::vector<TTableDescription> Tables_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufWriterFormatDescription)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufParserFormatDescription
+ : public TProtobufFormatDescriptionBase<TProtobufParserType>
+{
+public:
+ void Init(
+ const TProtobufFormatConfigPtr& config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas);
+
+ const TProtobufParserTypePtr& GetTableType() const;
+ std::vector<std::pair<ui16, TProtobufParserFieldDescription*>> CreateRootChildColumnIds(const NTableClient::TNameTablePtr& nameTable) const;
+
+private:
+ void AddTable(TProtobufParserTypePtr tableType) override;
+
+private:
+ TProtobufParserTypePtr TableType_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TProtobufParserFormatDescription)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/protobuf_options.cpp b/yt/yt/client/formats/protobuf_options.cpp
new file mode 100644
index 0000000000..5e9543d844
--- /dev/null
+++ b/yt/yt/client/formats/protobuf_options.cpp
@@ -0,0 +1,503 @@
+#include "protobuf_options.h"
+
+#include <util/generic/variant.h>
+
+namespace NYT::NFormats {
+
+using ::google::protobuf::Descriptor;
+using ::google::protobuf::OneofDescriptor;
+using ::google::protobuf::FieldDescriptor;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TOneofOption = std::variant<
+ EProtobufOneofMode>;
+
+using TFieldOption = std::variant<
+ ESpecialProtobufType,
+ EProtobufSerializationMode,
+ EProtobufListMode,
+ EProtobufMapMode,
+ EProtobufEnumWritingMode>;
+
+using TMessageOption = std::variant<
+ EProtobufFieldSortOrder>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFieldOption FieldFlagToOption(EWrapperFieldFlag::Enum flag)
+{
+ using EFlag = EWrapperFieldFlag;
+ switch (flag) {
+ case EFlag::SERIALIZATION_PROTOBUF:
+ return EProtobufSerializationMode::Protobuf;
+ case EFlag::SERIALIZATION_YT:
+ return EProtobufSerializationMode::Yt;
+ case EFlag::EMBEDDED:
+ return EProtobufSerializationMode::Embedded;
+
+ case EFlag::ANY:
+ return ESpecialProtobufType::Any;
+ case EFlag::OTHER_COLUMNS:
+ return ESpecialProtobufType::OtherColumns;
+ case EFlag::ENUM_INT:
+ return ESpecialProtobufType::EnumInt;
+ case EFlag::ENUM_STRING:
+ return ESpecialProtobufType::EnumString;
+
+ case EFlag::OPTIONAL_LIST:
+ return EProtobufListMode::Optional;
+ case EFlag::REQUIRED_LIST:
+ return EProtobufListMode::Required;
+
+ case EFlag::MAP_AS_LIST_OF_STRUCTS_LEGACY:
+ return EProtobufMapMode::ListOfStructsLegacy;
+ case EFlag::MAP_AS_LIST_OF_STRUCTS:
+ return EProtobufMapMode::ListOfStructs;
+ case EFlag::MAP_AS_DICT:
+ return EProtobufMapMode::Dict;
+ case EFlag::MAP_AS_OPTIONAL_DICT:
+ return EProtobufMapMode::OptionalDict;
+
+ case EFlag::ENUM_SKIP_UNKNOWN_VALUES:
+ return EProtobufEnumWritingMode::SkipUnknownValues;
+ case EFlag::ENUM_CHECK_VALUES:
+ return EProtobufEnumWritingMode::CheckValues;
+ }
+ Y_FAIL();
+}
+
+TMessageOption MessageFlagToOption(EWrapperMessageFlag::Enum flag)
+{
+ using EFlag = EWrapperMessageFlag;
+ switch (flag) {
+ case EFlag::DEPRECATED_SORT_FIELDS_AS_IN_PROTO_FILE:
+ return EProtobufFieldSortOrder::AsInProtoFile;
+ case EFlag::SORT_FIELDS_BY_FIELD_NUMBER:
+ return EProtobufFieldSortOrder::ByFieldNumber;
+ }
+ Y_FAIL();
+}
+
+TOneofOption OneofFlagToOption(EWrapperOneofFlag::Enum flag)
+{
+ using EFlag = EWrapperOneofFlag;
+ switch (flag) {
+ case EFlag::SEPARATE_FIELDS:
+ return EProtobufOneofMode::SeparateFields;
+ case EFlag::VARIANT:
+ return EProtobufOneofMode::Variant;
+ }
+ Y_FAIL();
+}
+
+TString OptionToFieldFlagName(TFieldOption option)
+{
+ using EFlag = EWrapperFieldFlag;
+ struct TVisitor
+ {
+ EFlag::Enum operator() (ESpecialProtobufType type)
+ {
+ switch (type) {
+ case ESpecialProtobufType::Any:
+ return EFlag::ANY;
+ case ESpecialProtobufType::OtherColumns:
+ return EFlag::OTHER_COLUMNS;
+ case ESpecialProtobufType::EnumInt:
+ return EFlag::ENUM_INT;
+ case ESpecialProtobufType::EnumString:
+ return EFlag::ENUM_STRING;
+ }
+ Y_FAIL();
+ }
+ EFlag::Enum operator() (EProtobufSerializationMode serializationMode)
+ {
+ switch (serializationMode) {
+ case EProtobufSerializationMode::Yt:
+ return EFlag::SERIALIZATION_YT;
+ case EProtobufSerializationMode::Protobuf:
+ return EFlag::SERIALIZATION_PROTOBUF;
+ case EProtobufSerializationMode::Embedded:
+ return EFlag::EMBEDDED;
+ }
+ Y_FAIL();
+ }
+ EFlag::Enum operator() (EProtobufListMode listMode)
+ {
+ switch (listMode) {
+ case EProtobufListMode::Optional:
+ return EFlag::OPTIONAL_LIST;
+ case EProtobufListMode::Required:
+ return EFlag::REQUIRED_LIST;
+ }
+ Y_FAIL();
+ }
+ EFlag::Enum operator() (EProtobufMapMode mapMode)
+ {
+ switch (mapMode) {
+ case EProtobufMapMode::ListOfStructsLegacy:
+ return EFlag::MAP_AS_LIST_OF_STRUCTS_LEGACY;
+ case EProtobufMapMode::ListOfStructs:
+ return EFlag::MAP_AS_LIST_OF_STRUCTS;
+ case EProtobufMapMode::Dict:
+ return EFlag::MAP_AS_DICT;
+ case EProtobufMapMode::OptionalDict:
+ return EFlag::MAP_AS_OPTIONAL_DICT;
+ }
+ Y_FAIL();
+ }
+ EFlag::Enum operator() (EProtobufEnumWritingMode enumWritingMode)
+ {
+ switch (enumWritingMode) {
+ case EProtobufEnumWritingMode::SkipUnknownValues:
+ return EFlag::ENUM_SKIP_UNKNOWN_VALUES;
+ case EProtobufEnumWritingMode::CheckValues:
+ return EFlag::ENUM_CHECK_VALUES;
+ }
+ Y_FAIL();
+ }
+ };
+
+ return EWrapperFieldFlag_Enum_Name(std::visit(TVisitor(), option));
+}
+
+TString OptionToMessageFlagName(TMessageOption option)
+{
+ using EFlag = EWrapperMessageFlag;
+ struct TVisitor
+ {
+ EFlag::Enum operator() (EProtobufFieldSortOrder sortOrder)
+ {
+ switch (sortOrder) {
+ case EProtobufFieldSortOrder::AsInProtoFile:
+ return EFlag::DEPRECATED_SORT_FIELDS_AS_IN_PROTO_FILE;
+ case EProtobufFieldSortOrder::ByFieldNumber:
+ return EFlag::SORT_FIELDS_BY_FIELD_NUMBER;
+ }
+ Y_FAIL();
+ }
+ };
+
+ return EWrapperMessageFlag_Enum_Name(std::visit(TVisitor(), option));
+}
+
+TString OptionToOneofFlagName(TOneofOption option)
+{
+ using EFlag = EWrapperOneofFlag;
+ struct TVisitor
+ {
+ EFlag::Enum operator() (EProtobufOneofMode mode)
+ {
+ switch (mode) {
+ case EProtobufOneofMode::SeparateFields:
+ return EFlag::SEPARATE_FIELDS;
+ case EProtobufOneofMode::Variant:
+ return EFlag::VARIANT;
+ }
+ Y_FAIL();
+ }
+ };
+
+ return EWrapperOneofFlag_Enum_Name(std::visit(TVisitor(), option));
+}
+
+
+template <typename T, typename TOptionToFlagName>
+void SetOption(TMaybe<T>& option, T newOption, TOptionToFlagName optionToFlagName)
+{
+ if (option) {
+ if (*option == newOption) {
+ ythrow yexception() << "Duplicate protobuf flag " << optionToFlagName(newOption);
+ } else {
+ ythrow yexception() << "Incompatible protobuf flags " <<
+ optionToFlagName(*option) << " and " << optionToFlagName(newOption);
+ }
+ }
+ option = newOption;
+}
+
+class TParseProtobufFieldOptionsVisitor
+{
+public:
+ void operator() (ESpecialProtobufType type)
+ {
+ SetOption(Type, type);
+ }
+
+ void operator() (EProtobufSerializationMode serializationMode)
+ {
+ SetOption(SerializationMode, serializationMode);
+ }
+
+ void operator() (EProtobufListMode listMode)
+ {
+ SetOption(ListMode, listMode);
+ }
+
+ void operator() (EProtobufMapMode mapMode)
+ {
+ SetOption(MapMode, mapMode);
+ }
+
+ void operator() (EProtobufEnumWritingMode enumWritingMode)
+ {
+ SetOption(EnumWritingMode, enumWritingMode);
+ }
+
+ template <typename T>
+ void SetOption(TMaybe<T>& option, T newOption)
+ {
+ NYT::NFormats::SetOption(option, newOption, OptionToFieldFlagName);
+ }
+
+public:
+ TMaybe<ESpecialProtobufType> Type;
+ TMaybe<EProtobufSerializationMode> SerializationMode;
+ TMaybe<EProtobufListMode> ListMode;
+ TMaybe<EProtobufMapMode> MapMode;
+ TMaybe<EProtobufEnumWritingMode> EnumWritingMode;
+};
+
+class TParseProtobufMessageOptionsVisitor
+{
+public:
+ void operator() (EProtobufFieldSortOrder fieldSortOrder)
+ {
+ SetOption(FieldSortOrder, fieldSortOrder);
+ }
+
+ template <typename T>
+ void SetOption(TMaybe<T>& option, T newOption)
+ {
+ NYT::NFormats::SetOption(option, newOption, OptionToMessageFlagName);
+ }
+
+public:
+ TMaybe<EProtobufFieldSortOrder> FieldSortOrder;
+};
+
+class TParseProtobufOneofOptionsVisitor
+{
+public:
+ void operator() (EProtobufOneofMode mode)
+ {
+ SetOption(Mode, mode);
+ }
+
+ template <typename T>
+ void SetOption(TMaybe<T>& option, T newOption)
+ {
+ NYT::NFormats::SetOption(option, newOption, OptionToOneofFlagName);
+ }
+
+public:
+ TMaybe<EProtobufOneofMode> Mode;
+};
+
+void ParseProtobufFieldOptions(
+ const ::google::protobuf::RepeatedField<EWrapperFieldFlag::Enum>& flags,
+ TProtobufFieldOptions* fieldOptions)
+{
+ TParseProtobufFieldOptionsVisitor visitor;
+ for (auto flag : flags) {
+ std::visit(visitor, FieldFlagToOption(flag));
+ }
+ if (visitor.Type) {
+ fieldOptions->Type = *visitor.Type;
+ }
+ if (visitor.SerializationMode) {
+ fieldOptions->SerializationMode = *visitor.SerializationMode;
+ }
+ if (visitor.ListMode) {
+ fieldOptions->ListMode = *visitor.ListMode;
+ }
+ if (visitor.MapMode) {
+ fieldOptions->MapMode = *visitor.MapMode;
+ }
+ if (visitor.EnumWritingMode) {
+ fieldOptions->EnumWritingMode = *visitor.EnumWritingMode;
+ }
+}
+
+void ParseProtobufMessageOptions(
+ const ::google::protobuf::RepeatedField<EWrapperMessageFlag::Enum>& flags,
+ TProtobufMessageOptions* messageOptions)
+{
+ TParseProtobufMessageOptionsVisitor visitor;
+ for (auto flag : flags) {
+ std::visit(visitor, MessageFlagToOption(flag));
+ }
+ if (visitor.FieldSortOrder) {
+ messageOptions->FieldSortOrder = *visitor.FieldSortOrder;
+ }
+}
+
+void ParseProtobufOneofOptions(
+ const ::google::protobuf::RepeatedField<EWrapperOneofFlag::Enum>& flags,
+ TProtobufOneofOptions* messageOptions)
+{
+ TParseProtobufOneofOptionsVisitor visitor;
+ for (auto flag : flags) {
+ std::visit(visitor, OneofFlagToOption(flag));
+ }
+ if (visitor.Mode) {
+ messageOptions->Mode = *visitor.Mode;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TProtobufFieldOptions GetDefaultFieldOptions(
+ const Descriptor* descriptor,
+ TProtobufFieldOptions defaultFieldOptions)
+{
+ ParseProtobufFieldOptions(
+ descriptor->file()->options().GetRepeatedExtension(file_default_field_flags),
+ &defaultFieldOptions);
+ ParseProtobufFieldOptions(
+ descriptor->options().GetRepeatedExtension(default_field_flags),
+ &defaultFieldOptions);
+ return defaultFieldOptions;
+}
+
+TProtobufOneofOptions GetDefaultOneofOptions(const Descriptor* descriptor)
+{
+ TProtobufOneofOptions defaultOneofOptions;
+ ParseProtobufOneofOptions(
+ descriptor->file()->options().GetRepeatedExtension(file_default_oneof_flags),
+ &defaultOneofOptions);
+ ParseProtobufOneofOptions(
+ descriptor->options().GetRepeatedExtension(default_oneof_flags),
+ &defaultOneofOptions);
+ switch (defaultOneofOptions.Mode) {
+ case EProtobufOneofMode::Variant: {
+ auto defaultFieldOptions = GetDefaultFieldOptions(descriptor);
+ switch (defaultFieldOptions.SerializationMode) {
+ case EProtobufSerializationMode::Protobuf:
+ // For Protobuf serialization mode default is SeparateFields.
+ defaultOneofOptions.Mode = EProtobufOneofMode::SeparateFields;
+ return defaultOneofOptions;
+ case EProtobufSerializationMode::Yt:
+ case EProtobufSerializationMode::Embedded:
+ return defaultOneofOptions;
+ }
+ Y_FAIL();
+ }
+ case EProtobufOneofMode::SeparateFields:
+ return defaultOneofOptions;
+ }
+ Y_FAIL();
+}
+
+TProtobufFieldOptions GetFieldOptions(
+ const FieldDescriptor* fieldDescriptor,
+ const TMaybe<TProtobufFieldOptions>& defaultFieldOptions)
+{
+ TProtobufFieldOptions options;
+ if (defaultFieldOptions) {
+ options = *defaultFieldOptions;
+ } else {
+ options = GetDefaultFieldOptions(fieldDescriptor->containing_type());
+ }
+ ParseProtobufFieldOptions(fieldDescriptor->options().GetRepeatedExtension(flags), &options);
+ return options;
+}
+
+TProtobufOneofOptions GetOneofOptions(
+ const OneofDescriptor* oneofDescriptor,
+ const TMaybe<TProtobufOneofOptions>& defaultOneofOptions)
+{
+ TProtobufOneofOptions options;
+ if (defaultOneofOptions) {
+ options = *defaultOneofOptions;
+ } else {
+ options = GetDefaultOneofOptions(oneofDescriptor->containing_type());
+ }
+ ParseProtobufOneofOptions(oneofDescriptor->options().GetRepeatedExtension(oneof_flags), &options);
+
+ if (oneofDescriptor->is_synthetic()) {
+ options.Mode = EProtobufOneofMode::SeparateFields;
+ }
+
+ auto variantFieldName = oneofDescriptor->options().GetExtension(variant_field_name);
+ switch (options.Mode) {
+ case EProtobufOneofMode::SeparateFields:
+ if (variantFieldName) {
+ ythrow yexception() << "\"variant_field_name\" requires (NYT.oneof_flags) = VARIANT";
+ }
+ break;
+ case EProtobufOneofMode::Variant:
+ if (variantFieldName) {
+ options.VariantFieldName = variantFieldName;
+ } else {
+ options.VariantFieldName = oneofDescriptor->name();
+ }
+ break;
+ }
+ return options;
+}
+
+TProtobufMessageOptions GetMessageOptions(const Descriptor* descriptor)
+{
+ TProtobufMessageOptions options;
+ ParseProtobufMessageOptions(
+ descriptor->file()->options().GetRepeatedExtension(file_default_message_flags),
+ &options);
+ ParseProtobufMessageOptions(
+ descriptor->options().GetRepeatedExtension(message_flags),
+ &options);
+ return options;
+}
+
+TString GetColumnName(const FieldDescriptor* field)
+{
+ const auto& options = field->options();
+ const auto columnName = options.GetExtension(column_name);
+ if (!columnName.empty()) {
+ return columnName;
+ }
+ const auto keyColumnName = options.GetExtension(key_column_name);
+ if (!keyColumnName.empty()) {
+ return keyColumnName;
+ }
+ return field->name();
+}
+
+void ValidateProtobufType(const FieldDescriptor* fieldDescriptor, ESpecialProtobufType protobufType)
+{
+ const auto fieldType = fieldDescriptor->type();
+ auto ensureType = [&] (FieldDescriptor::Type expectedType) {
+ Y_ENSURE(fieldType == expectedType,
+ "Type of field " << fieldDescriptor->full_name() << "does not match specified field flag " <<
+ OptionToFieldFlagName(protobufType) << ": "
+ "expected " << FieldDescriptor::TypeName(expectedType) << ", " <<
+ "got " << FieldDescriptor::TypeName(fieldType));
+ };
+ switch (protobufType) {
+ case ESpecialProtobufType::Any:
+ ensureType(FieldDescriptor::TYPE_BYTES);
+ return;
+ case ESpecialProtobufType::OtherColumns:
+ ensureType(FieldDescriptor::TYPE_BYTES);
+ return;
+ case ESpecialProtobufType::EnumInt:
+ ensureType(FieldDescriptor::TYPE_ENUM);
+ return;
+ case ESpecialProtobufType::EnumString:
+ ensureType(FieldDescriptor::TYPE_ENUM);
+ return;
+ }
+ Y_FAIL();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/protobuf_options.h b/yt/yt/client/formats/protobuf_options.h
new file mode 100644
index 0000000000..03caaadaff
--- /dev/null
+++ b/yt/yt/client/formats/protobuf_options.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include "config.h"
+
+#include <yt/yt_proto/yt/formats/extension.pb.h>
+
+#include <google/protobuf/message.h>
+
+#include <util/generic/maybe.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+enum class ESpecialProtobufType
+{
+ EnumInt /* "enum_int" */,
+ EnumString /* "enum_string" */,
+ Any /* "any" */,
+ OtherColumns /* "other_columns" */,
+};
+
+enum class EProtobufSerializationMode
+{
+ Protobuf,
+ Yt,
+ Embedded,
+};
+
+enum class EProtobufListMode
+{
+ Optional,
+ Required,
+};
+
+enum class EProtobufMapMode
+{
+ ListOfStructsLegacy,
+ ListOfStructs,
+ Dict,
+ OptionalDict,
+};
+
+enum class EProtobufFieldSortOrder
+{
+ AsInProtoFile,
+ ByFieldNumber,
+};
+
+enum class EProtobufOneofMode
+{
+ SeparateFields,
+ Variant,
+};
+
+struct TProtobufOneofOptions
+{
+ EProtobufOneofMode Mode = EProtobufOneofMode::Variant;
+ TString VariantFieldName;
+};
+
+struct TProtobufFieldOptions
+{
+ TMaybe<ESpecialProtobufType> Type;
+ EProtobufSerializationMode SerializationMode = EProtobufSerializationMode::Protobuf;
+ EProtobufListMode ListMode = EProtobufListMode::Required;
+ EProtobufMapMode MapMode = EProtobufMapMode::ListOfStructsLegacy;
+ EProtobufEnumWritingMode EnumWritingMode = EProtobufEnumWritingMode::CheckValues;
+};
+
+struct TProtobufMessageOptions
+{
+ EProtobufFieldSortOrder FieldSortOrder = EProtobufFieldSortOrder::ByFieldNumber;
+};
+
+TProtobufFieldOptions GetDefaultFieldOptions(
+ const ::google::protobuf::Descriptor* descriptor,
+ TProtobufFieldOptions defaultFieldOptions = {});
+
+TProtobufOneofOptions GetDefaultOneofOptions(
+ const ::google::protobuf::Descriptor* descriptor);
+
+TProtobufFieldOptions GetFieldOptions(
+ const ::google::protobuf::FieldDescriptor* fieldDescriptor,
+ const TMaybe<TProtobufFieldOptions>& defaultFieldOptions = {});
+
+TProtobufOneofOptions GetOneofOptions(
+ const ::google::protobuf::OneofDescriptor* oneofDescriptor,
+ const TMaybe<TProtobufOneofOptions>& defaultOneofOptions = {});
+
+TString GetColumnName(const ::google::protobuf::FieldDescriptor* field);
+
+void ValidateProtobufType(
+ const ::google::protobuf::FieldDescriptor* fieldDescriptor,
+ ESpecialProtobufType protobufType);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/protobuf_parser.cpp b/yt/yt/client/formats/protobuf_parser.cpp
new file mode 100644
index 0000000000..abaef0cb22
--- /dev/null
+++ b/yt/yt/client/formats/protobuf_parser.cpp
@@ -0,0 +1,734 @@
+#include "protobuf_parser.h"
+
+#include "protobuf.h"
+#include "parser.h"
+#include "yson_map_to_unversioned_value.h"
+
+#include <yt/yt/client/table_client/helpers.h>
+#include <yt/yt/client/table_client/table_consumer.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/table_client/value_consumer.h>
+
+#include <yt/yt/core/misc/finally.h>
+
+#include <library/cpp/yt/coding/varint.h>
+
+#include <util/generic/buffer.h>
+#include <util/generic/scope.h>
+
+#include <util/string/escape.h>
+
+#include <google/protobuf/wire_format_lite.h>
+
+namespace NYT::NFormats {
+
+using namespace NYson;
+using namespace NTableClient;
+using namespace NComplexTypes;
+
+using ::google::protobuf::internal::WireFormatLite;
+
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRowParser
+{
+public:
+ explicit TRowParser(TStringBuf strbuf)
+ : Begin_(strbuf.data())
+ , End_(strbuf.data() + strbuf.size())
+ , Current_(strbuf.data())
+ { }
+
+ ui32 ReadVarUint32()
+ {
+ ui32 result;
+ Current_ += ::NYT::ReadVarUint32(Current_, End_, &result);
+ return result;
+ }
+
+ ui64 ReadVarUint64()
+ {
+ ui64 result;
+ Current_ += ::NYT::ReadVarUint64(Current_, End_, &result);
+ return result;
+ }
+
+ i64 ReadVarSint64()
+ {
+ i64 value;
+ Current_ += ReadVarInt64(Current_, End_, &value);
+ return value;
+ }
+
+ i32 ReadVarSint32()
+ {
+ i32 value;
+ Current_ += ReadVarInt32(Current_, End_, &value);
+ return value;
+ }
+
+ template <typename T>
+ T ReadFixed()
+ {
+ if (Current_ + sizeof(T) > End_) {
+ THROW_ERROR_EXCEPTION("Cannot read value of %v bytes, message exhausted",
+ sizeof(T));
+ }
+ T result = *reinterpret_cast<const T*>(Current_);
+ Current_ += sizeof(T);
+ return result;
+ }
+
+ TStringBuf ReadLengthDelimited()
+ {
+ ui32 length = ReadVarUint32();
+
+ ValidateLength(length);
+
+ auto result = TStringBuf(Current_, length);
+ Current_ += length;
+ return result;
+ }
+
+ void Skip(WireFormatLite::WireType wireType)
+ {
+ switch (wireType) {
+ case WireFormatLite::WIRETYPE_VARINT:
+ ReadVarUint64();
+ return;
+ case WireFormatLite::WIRETYPE_FIXED64:
+ ReadFixed<ui64>();
+ return;
+ case WireFormatLite::WIRETYPE_LENGTH_DELIMITED:
+ ReadLengthDelimited();
+ return;
+ case WireFormatLite::WIRETYPE_START_GROUP:
+ case WireFormatLite::WIRETYPE_END_GROUP:
+ THROW_ERROR_EXCEPTION("Unexpected wire type %v", static_cast<int>(wireType));
+ case WireFormatLite::WIRETYPE_FIXED32:
+ ReadFixed<ui32>();
+ return;
+ }
+ YT_ABORT();
+ }
+
+ bool IsExhausted() const
+ {
+ return Current_ >= End_;
+ }
+
+ std::vector<TErrorAttribute> GetContextErrorAttributes() const
+ {
+ constexpr int contextMargin = 50;
+
+ auto contextBegin = Begin_ + contextMargin > Current_ ? Begin_ : Current_ - contextMargin;
+ auto contextEnd = Current_ + contextMargin > End_ ? End_ : Current_ + contextMargin;
+ size_t contextPos = Current_ - contextBegin;
+
+ TString contextString;
+ return {
+ TErrorAttribute("context", EscapeC(TStringBuf(contextBegin, contextEnd), contextString)),
+ TErrorAttribute("context_pos", contextPos)
+ };
+ }
+
+private:
+ void ValidateLength(ui32 length) const
+ {
+ if (Current_ + length > End_) {
+ THROW_ERROR_EXCEPTION("Broken protobuf message: field with length %v is out of message bounds",
+ length)
+ << GetContextErrorAttributes();
+ }
+ }
+
+private:
+ const char* Begin_;
+ const char* End_;
+ const char* Current_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// This struct can represent either
+// 1) a real column; |ChildIndex| means index in protobuf config; OR
+// 2) protobuf binary representation (inside EValueType::String)
+// for a structured message field;
+// in this case |ChildIndex| means the index of a field inside the message; OR
+// 3) unversioned-value or protobuf representation for a repeated field;
+// meaning of |ChildIndex| is as in (1) or (2).
+struct TField
+{
+ TUnversionedValue Value;
+ int ChildIndex;
+};
+
+class TCountingSorter
+{
+public:
+ // Sort a vector of |TField| by |ChildIndex| using counting sort.
+ // |ChildIndex| must be in range [0, |rangeSize|).
+ void Sort(std::vector<TField>* elements, int rangeSize)
+ {
+ if (elements->size() <= 1) {
+ return;
+ }
+ Counts_.assign(rangeSize, 0);
+ for (const auto& element : *elements) {
+ ++Counts_[element.ChildIndex];
+ }
+ for (int i = 1; i < static_cast<int>(Counts_.size()); ++i) {
+ Counts_[i] += Counts_[i - 1];
+ }
+ Result_.resize(elements->size());
+ for (auto it = elements->rbegin(); it != elements->rend(); ++it) {
+ auto childIndex = it->ChildIndex;
+ Result_[Counts_[childIndex] - 1] = std::move(*it);
+ --Counts_[childIndex];
+ }
+ std::swap(Result_, *elements);
+ }
+
+private:
+ std::vector<TField> Result_;
+ std::vector<int> Counts_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+int ComputeDepth(const TProtobufParserTypePtr& type)
+{
+ int depth = 0;
+ for (const auto& child : type->Children) {
+ depth = std::max(depth, ComputeDepth(child->Type) + 1);
+ }
+ return depth;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EProtobufParserState,
+ (InsideLength)
+ (InsideData)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufParser
+ : public IParser
+{
+public:
+ using EState = EProtobufParserState;
+
+ // NB(levysotsky): We expect the description to have only one table,
+ // the |tableIndex| parameter is for debugging purposes only.
+ TProtobufParser(
+ IValueConsumer* valueConsumer,
+ TProtobufParserFormatDescriptionPtr description,
+ int tableIndex,
+ const TYsonConverterConfig& config)
+ : ValueConsumer_(valueConsumer)
+ , Description_(std::move(description))
+ , TableIndex_(tableIndex)
+ , RootChildColumnIds_(Description_->CreateRootChildColumnIds(ValueConsumer_->GetNameTable()))
+ , RootChildOutputFlags_(RootChildColumnIds_.size()) //Description_->GetTableType()->Children.size())
+ // NB. We use ColumnConsumer_ to generate yson representation of complex types we don't want additional
+ // conversions so we use Positional mode.
+ // At the same time we use OtherColumnsConsumer_ to feed yson passed by users.
+ // This YSON should be in format specified on the format config.
+ , ColumnConsumer_(
+ TYsonConverterConfig{
+ .ComplexTypeMode = EComplexTypeMode::Positional,
+ },
+ valueConsumer)
+ , OtherColumnsConsumer_(config, valueConsumer)
+ {
+ FieldVectors_.resize(ComputeDepth(Description_->GetTableType()) + 1);
+ }
+
+ void Read(TStringBuf data) override
+ {
+ auto current = data.begin();
+ while (current != data.end()) {
+ current = Consume(current, data.end());
+ }
+ }
+
+ void Finish() override
+ {
+ if (State_ != EState::InsideLength || ExpectedBytes_ != sizeof(ui32)) {
+ THROW_ERROR_EXCEPTION("Unexpected end of stream");
+ }
+ }
+
+private:
+ const char* Consume(const char* begin, const char* end)
+ {
+ switch (State_) {
+ case EState::InsideLength:
+ return ConsumeLength(begin, end);
+ case EState::InsideData:
+ return ConsumeData(begin, end);
+ }
+ YT_ABORT();
+ }
+
+ const char* ConsumeInt32(const char* begin, const char* end)
+ {
+ const char* current = begin;
+ for (; ExpectedBytes_ != 0 && current != end; ++current, --ExpectedBytes_) {
+ Length_.Bytes[sizeof(ui32) - ExpectedBytes_] = *current;
+ }
+ return current;
+ }
+
+ const char* ConsumeLength(const char* begin, const char* end)
+ {
+ const char* next = ConsumeInt32(begin, end);
+ if (ExpectedBytes_ != 0) {
+ return next;
+ }
+
+ State_ = EState::InsideData;
+ ExpectedBytes_ = Length_.Value;
+
+ return ConsumeData(next, end);
+ }
+
+ const char* ConsumeData(const char* begin, const char* end)
+ {
+ const char* current = begin + ExpectedBytes_;
+ if (current > end) {
+ Data_.append(begin, end);
+ ExpectedBytes_ -= (end - begin);
+ return end;
+ }
+
+ if (Data_.empty()) {
+ OutputRow(TStringBuf(begin, current));
+ } else {
+ Data_.append(begin, current);
+ OutputRow(Data_);
+ }
+
+ State_ = EState::InsideLength;
+ ExpectedBytes_ = sizeof(ui32);
+ Data_.clear();
+ return current;
+ }
+
+ void OutputRow(TStringBuf buffer)
+ {
+ ValueConsumer_->OnBeginRow();
+ const auto& tableType = Description_->GetTableType();
+ RootChildOutputFlags_.assign(RootChildColumnIds_.size(), false);
+ ProcessStructuredMessage(buffer, tableType, /* depth */ 0);
+ ValueConsumer_->OnEndRow();
+ }
+
+ auto EnterChild(const TProtobufParserFieldDescription& child)
+ {
+ if (child.IsOneofAlternative()) {
+ Path_.push_back(child.ContainingOneof->Field);
+ }
+ Path_.push_back(&child);
+ return Finally([&] {
+ Path_.pop_back();
+ if (child.IsOneofAlternative()) {
+ Path_.pop_back();
+ }
+ });
+ }
+
+ void ProcessStructuredMessageEmbedded(TStringBuf buffer, const TProtobufParserTypePtr& type, int depth)
+ {
+ auto& fields = FieldVectors_[depth];
+
+ TRowParser rowParser(buffer);
+ try {
+ while (!rowParser.IsExhausted()) {
+ ui32 wireTag = rowParser.ReadVarUint32();
+ auto fieldNumber = WireFormatLite::GetTagFieldNumber(wireTag);
+ auto maybeChildIndex = type->FieldNumberToChildIndex(fieldNumber);
+ if (!maybeChildIndex) {
+ rowParser.Skip(WireFormatLite::GetTagWireType(wireTag));
+ continue;
+ }
+ auto childIndex = *maybeChildIndex;
+ const auto& childDescription = *type->Children[childIndex];
+ auto guard = EnterChild(childDescription);
+ if (Y_UNLIKELY(wireTag != childDescription.WireTag)) {
+ THROW_ERROR_EXCEPTION("Expected wire tag for field %Qv to be %v, got %v",
+ GetPathString(),
+ childDescription.WireTag,
+ wireTag)
+ << TErrorAttribute("field_number", fieldNumber);
+ }
+
+ int embeddedChildIndex = childIndex;
+ if (depth == 0 && childDescription.Type->ProtoType != EProtobufType::EmbeddedMessage) {
+ auto maybeEmbeddedChildIndex = type->FieldNumberToEmbeddedChildIndex(fieldNumber);
+ Y_VERIFY(maybeEmbeddedChildIndex);
+ embeddedChildIndex = *maybeEmbeddedChildIndex;
+ }
+
+ if (childDescription.Packed) {
+ auto elementsParser = TRowParser(rowParser.ReadLengthDelimited());
+ while (!elementsParser.IsExhausted()) {
+ ReadAndProcessUnversionedValue(elementsParser, embeddedChildIndex, childDescription, depth, &fields);
+ }
+ } else {
+ ReadAndProcessUnversionedValue(rowParser, embeddedChildIndex, childDescription, depth, &fields);
+ }
+ }
+ } catch (const std::exception& exception) {
+ THROW_ERROR_EXCEPTION(exception)
+ << TErrorAttribute("table_index", TableIndex_)
+ << rowParser.GetContextErrorAttributes();
+ }
+ }
+
+ void ProcessStructuredMessage(TStringBuf buffer, const TProtobufParserTypePtr& type, int depth)
+ {
+ auto& fields = FieldVectors_[depth];
+ fields.clear();
+
+ ProcessStructuredMessageEmbedded(buffer, type, depth);
+ int childrenCount = depth == 0 ? std::ssize(RootChildColumnIds_) : std::ssize(type->Children);
+ CountingSorter_.Sort(&fields, childrenCount);
+ OutputChildren(fields, type, depth);
+ }
+
+ Y_FORCE_INLINE void OutputChild(
+ std::vector<TField>::const_iterator begin,
+ std::vector<TField>::const_iterator end,
+ const TProtobufParserFieldDescription& childDescription,
+ int depth)
+ {
+ if (childDescription.Repeated) {
+ ColumnConsumer_.OnBeginList();
+ for (auto it = begin; it != end; ++it) {
+ ColumnConsumer_.OnListItem();
+ OutputValue(it->Value, childDescription, depth);
+ }
+ ColumnConsumer_.OnEndList();
+ } else {
+ if (Y_UNLIKELY(std::distance(begin, end) > 1)) {
+ THROW_ERROR_EXCEPTION("Error parsing protobuf: found %v entries for non-repeated field %Qv",
+ std::distance(begin, end),
+ GetPathString())
+ << TErrorAttribute("table_index", TableIndex_);
+ }
+ OutputValue(begin->Value, childDescription, depth);
+ }
+ }
+
+ void OutputChildren(
+ const std::vector<TField>& fields,
+ const TProtobufParserTypePtr& type,
+ int depth)
+ {
+ const auto inRoot = (depth == 0);
+
+ auto skipElements = [&](int count) {
+ if (inRoot) {
+ return;
+ }
+ YT_VERIFY(count >= 0);
+ for (int i = 0; i < count; ++i) {
+ ColumnConsumer_.OnEntity();
+ }
+ };
+
+ auto fieldIt = fields.cbegin();
+ int childrenCount = inRoot ? std::ssize(RootChildColumnIds_) : std::ssize(type->Children);
+ auto lastOutputStructFieldIndex = -1;
+ for (int childIndex = 0; childIndex < childrenCount; ++childIndex) {
+ const auto& childDescription = inRoot ? *RootChildColumnIds_[childIndex].second : *type->Children[childIndex];
+ auto guard = EnterChild(childDescription);
+
+ auto fieldRangeBegin = fieldIt;
+ while (fieldIt != fields.cend() && fieldIt->ChildIndex == childIndex) {
+ ++fieldIt;
+ }
+ int structFieldIndex = childDescription.StructFieldIndex;
+ if (fieldRangeBegin != fieldIt || (childDescription.Repeated && !childDescription.Type->Optional)) {
+ if (Y_UNLIKELY(
+ childDescription.IsOneofAlternative() &&
+ lastOutputStructFieldIndex == structFieldIndex))
+ {
+ const auto* oneof = childDescription.ContainingOneof;
+ YT_VERIFY(oneof);
+ THROW_ERROR_EXCEPTION(
+ "Error parsing protobuf: multiple entries for oneof field %Qv; the second one is %Qv",
+ GetPathString(/* offset */ 1),
+ GetPathString())
+ << TErrorAttribute("table_index", TableIndex_);
+ }
+ skipElements(structFieldIndex - lastOutputStructFieldIndex - 1);
+ if (inRoot) {
+ ColumnConsumer_.SetColumnIndex(RootChildColumnIds_[childIndex].first);
+ RootChildOutputFlags_[childIndex] = true;
+ }
+ OutputChild(fieldRangeBegin, fieldIt, childDescription, depth);
+ lastOutputStructFieldIndex = structFieldIndex;
+ } else {
+ auto isStructFieldPresentOrLegallyMissing = [&] () {
+ if (ShouldOutputValueImmediately(inRoot, childDescription)) {
+ if (RootChildOutputFlags_[childIndex]) {
+ // The value is already output.
+ return true;
+ }
+ }
+ if (childDescription.Type->ProtoType == EProtobufType::EmbeddedMessage) {
+ // The value is already parsed and processed
+ return true;
+ }
+ if (!childDescription.IsOneofAlternative()) {
+ return childDescription.Type->Optional;
+ }
+ if (childDescription.ContainingOneof->Optional) {
+ return true;
+ }
+ if (lastOutputStructFieldIndex == structFieldIndex) {
+ // It is not missing.
+ return true;
+ }
+ if (childIndex + 1 == (inRoot ? std::ssize(RootChildColumnIds_) : std::ssize(type->Children))) {
+ return false;
+ }
+ const auto& nextChildDescription = inRoot ? *RootChildColumnIds_[childIndex + 1].second : *type->Children[childIndex + 1];
+ // The current alternative is missing, but the next one corresponds to the same field,
+ // so the check is deferred to the next alternative.
+ return structFieldIndex == nextChildDescription.StructFieldIndex;
+ };
+ if (Y_UNLIKELY(!isStructFieldPresentOrLegallyMissing())) {
+ int offset = 0;
+ if (childDescription.IsOneofAlternative()) {
+ offset = 1;
+ }
+ THROW_ERROR_EXCEPTION("Error parsing protobuf: required field %Qv is missing",
+ GetPathString(offset));
+ }
+ }
+ }
+ skipElements(type->StructFieldCount - lastOutputStructFieldIndex - 1);
+ }
+
+ Y_FORCE_INLINE void OutputValue(
+ TUnversionedValue value,
+ const TProtobufParserFieldDescription& description,
+ int depth)
+ {
+ const auto inRoot = (depth == 0);
+
+ if (description.IsOneofAlternative()) {
+ ColumnConsumer_.OnBeginList();
+ ColumnConsumer_.OnListItem();
+ ColumnConsumer_.OnInt64Scalar(*description.AlternativeIndex);
+ ColumnConsumer_.OnListItem();
+ }
+ switch (description.Type->ProtoType) {
+ case EProtobufType::StructuredMessage:
+ YT_VERIFY(value.Type == EValueType::String);
+ ColumnConsumer_.OnBeginList();
+ ProcessStructuredMessage(value.AsStringBuf(), description.Type, depth + 1);
+ ColumnConsumer_.OnEndList();
+ break;
+ case EProtobufType::OtherColumns:
+ UnversionedValueToYson(value, &OtherColumnsConsumer_);
+ break;
+ case EProtobufType::Any:
+ UnversionedValueToYson(value, &ColumnConsumer_);
+ break;
+ default:
+ if (ShouldOutputValueImmediately(inRoot, description)) {
+ ValueConsumer_->OnValue(value);
+ } else {
+ UnversionedValueToYson(value, &ColumnConsumer_);
+ }
+ break;
+ }
+ if (description.IsOneofAlternative()) {
+ ColumnConsumer_.OnEndList();
+ }
+ }
+
+ // Reads unversioned value depending on the field type.
+ // If |depth == 0| and the field is not repeated we process it according to type.
+ // Otherwise, we append it to |fields| vector.
+ Y_FORCE_INLINE void ReadAndProcessUnversionedValue(
+ TRowParser& rowParser,
+ int childIndex,
+ const TProtobufParserFieldDescription& description,
+ int depth,
+ std::vector<TField>* fields)
+ {
+ if (description.Type->ProtoType == EProtobufType::EmbeddedMessage) {
+ ProcessStructuredMessageEmbedded(rowParser.ReadLengthDelimited(), description.Type, depth); // NOT depth + 1
+ return;
+ }
+
+ const auto inRoot = (depth == 0);
+ const auto id = inRoot ? RootChildColumnIds_[childIndex].first : static_cast<ui16>(0);
+ auto value = [&] {
+ switch (description.Type->ProtoType) {
+ case EProtobufType::StructuredMessage:
+ return MakeUnversionedStringValue(rowParser.ReadLengthDelimited(), id);
+ case EProtobufType::OtherColumns:
+ return MakeUnversionedAnyValue(rowParser.ReadLengthDelimited(), id);
+ case EProtobufType::Any:
+ return MakeUnversionedAnyValue(rowParser.ReadLengthDelimited(), id);
+ case EProtobufType::String:
+ case EProtobufType::Message:
+ case EProtobufType::Bytes:
+ return MakeUnversionedStringValue(rowParser.ReadLengthDelimited(), id);
+ case EProtobufType::Uint64:
+ return MakeUnversionedUint64Value(rowParser.ReadVarUint64(), id);
+ case EProtobufType::Uint32:
+ return MakeUnversionedUint64Value(rowParser.ReadVarUint32(), id);
+ case EProtobufType::Int64:
+ // Value is *not* zigzag encoded, so we use Uint64 intentionally.
+ return MakeUnversionedInt64Value(static_cast<i64>(rowParser.ReadVarUint64()), id);
+ case EProtobufType::EnumInt:
+ case EProtobufType::Int32:
+ // Value is *not* zigzag encoded, so we use Uint64 intentionally.
+ return MakeUnversionedInt64Value(static_cast<i64>(rowParser.ReadVarUint64()), id);
+ case EProtobufType::Sint64:
+ return MakeUnversionedInt64Value(rowParser.ReadVarSint64(), id);
+ case EProtobufType::Sint32:
+ return MakeUnversionedInt64Value(rowParser.ReadVarSint32(), id);
+ case EProtobufType::Fixed64:
+ return MakeUnversionedUint64Value(rowParser.ReadFixed<ui64>(), id);
+ case EProtobufType::Fixed32:
+ return MakeUnversionedUint64Value(rowParser.ReadFixed<ui32>(), id);
+ case EProtobufType::Sfixed64:
+ return MakeUnversionedInt64Value(rowParser.ReadFixed<i64>(), id);
+ case EProtobufType::Sfixed32:
+ return MakeUnversionedInt64Value(rowParser.ReadFixed<i32>(), id);
+ case EProtobufType::Double:
+ return MakeUnversionedDoubleValue(rowParser.ReadFixed<double>(), id);
+ case EProtobufType::Float:
+ return MakeUnversionedDoubleValue(rowParser.ReadFixed<float>(), id);
+ case EProtobufType::Bool:
+ return MakeUnversionedBooleanValue(static_cast<bool>(rowParser.ReadVarUint64()), id);
+ case EProtobufType::EnumString: {
+ auto enumValue = static_cast<i32>(rowParser.ReadVarUint64());
+ YT_VERIFY(description.Type->EnumerationDescription);
+ const auto& enumString = description.Type->EnumerationDescription->GetValueName(enumValue);
+ return MakeUnversionedStringValue(enumString, id);
+ }
+ case EProtobufType::Oneof:
+ THROW_ERROR_EXCEPTION("Oneof inside oneof is not supported in protobuf format; offending field %Qv",
+ GetPathString())
+ << TErrorAttribute("table_index", TableIndex_);
+ case EProtobufType::EmbeddedMessage:
+ Y_FAIL();
+ }
+ YT_ABORT();
+ }();
+ if (ShouldOutputValueImmediately(inRoot, description)) {
+ ColumnConsumer_.SetColumnIndex(id);
+ RootChildOutputFlags_[childIndex] = true;
+ OutputValue(value, description, depth);
+ } else {
+ fields->push_back({value, childIndex});
+ }
+ }
+
+ Y_FORCE_INLINE static bool ShouldOutputValueImmediately(bool inRoot, const TProtobufParserFieldDescription& description)
+ {
+ return inRoot && !description.Repeated && !description.IsOneofAlternative();
+ }
+
+ TString GetPathString(int offset = 0)
+ {
+ TStringStream stream;
+ stream << "<root>";
+ YT_VERIFY(std::ssize(Path_) >= offset);
+ for (int i = 0; i < std::ssize(Path_) - offset; ++i) {
+ stream << '.' << Path_[i]->Name;
+ }
+ return stream.Str();
+ }
+
+private:
+ IValueConsumer* const ValueConsumer_;
+
+ TProtobufParserFormatDescriptionPtr Description_;
+ int TableIndex_;
+
+ std::vector<std::pair<ui16, TProtobufParserFieldDescription*>> RootChildColumnIds_;
+ std::vector<bool> RootChildOutputFlags_;
+
+ TYsonToUnversionedValueConverter ColumnConsumer_;
+ TYsonMapToUnversionedValueConverter OtherColumnsConsumer_;
+
+ std::vector<const TProtobufParserFieldDescription*> Path_;
+
+ std::vector<std::vector<TField>> FieldVectors_;
+ TCountingSorter CountingSorter_;
+
+ EState State_ = EState::InsideLength;
+ union
+ {
+ ui32 Value;
+ char Bytes[sizeof(ui32)];
+ } Length_;
+ ui32 ExpectedBytes_ = sizeof(ui32);
+
+ TString Data_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForProtobuf(
+ IValueConsumer* consumer,
+ TProtobufFormatConfigPtr config,
+ int tableIndex)
+{
+ if (!config->Tables.empty()) {
+ // Retain only one table config, as we have only one schema here.
+ config = NYTree::CloneYsonStruct(config);
+ if (tableIndex >= std::ssize(config->Tables)) {
+ THROW_ERROR_EXCEPTION("Protobuf format does not have table with index %v",
+ tableIndex);
+ }
+ config->Tables = {config->Tables[tableIndex]};
+ } else if (!config->TypeNames.empty()) {
+ // Retain only one type name, as we have only one schema here.
+ config = NYTree::CloneYsonStruct(config);
+ if (tableIndex >= std::ssize(config->TypeNames)) {
+ THROW_ERROR_EXCEPTION("Protobuf format does not have table with index %v",
+ tableIndex);
+ }
+ config->TypeNames = {config->TypeNames[tableIndex]};
+ }
+ auto formatDescription = New<TProtobufParserFormatDescription>();
+ formatDescription->Init(config, {consumer->GetSchema()});
+ TYsonConverterConfig ysonConfig{
+ .ComplexTypeMode = config->ComplexTypeMode,
+ .DecimalMode = config->DecimalMode,
+ .TimeMode = config->TimeMode,
+ .UuidMode = config->UuidMode,
+ };
+ return std::make_unique<TProtobufParser>(
+ consumer,
+ formatDescription,
+ tableIndex,
+ ysonConfig);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/protobuf_parser.h b/yt/yt/client/formats/protobuf_parser.h
new file mode 100644
index 0000000000..1ac356069f
--- /dev/null
+++ b/yt/yt/client/formats/protobuf_parser.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForProtobuf(
+ NTableClient::IValueConsumer* consumer,
+ TProtobufFormatConfigPtr config,
+ int tableIndex);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/protobuf_writer.cpp b/yt/yt/client/formats/protobuf_writer.cpp
new file mode 100644
index 0000000000..f4321cd68a
--- /dev/null
+++ b/yt/yt/client/formats/protobuf_writer.cpp
@@ -0,0 +1,1078 @@
+#include "protobuf_writer.h"
+
+#include "lenval_control_constants.h"
+#include "protobuf.h"
+#include "schemaless_writer_adapter.h"
+#include "unversioned_value_yson_writer.h"
+
+#include <yt/yt/client/table_client/helpers.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/misc/zerocopy_output_writer.h>
+
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/core/yson/token_writer.h>
+
+#include <library/cpp/yt/coding/varint.h>
+
+#include <util/generic/buffer.h>
+
+#include <util/stream/buffer.h>
+#include <util/stream/mem.h>
+
+#include <google/protobuf/wire_format_lite.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYTree;
+using namespace NYson;
+using namespace NTableClient;
+using namespace NComplexTypes;
+
+using ::google::protobuf::internal::WireFormatLite;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TZeroCopyWriterWithGapsBase
+{
+public:
+ TZeroCopyWriterWithGapsBase(TBlob& blob)
+ : Blob_(blob)
+ , InitialSize_(blob.Size())
+ { }
+
+protected:
+ TBlob& Blob_;
+ ui64 InitialSize_;
+};
+
+// Same as `TZeroCopyOutputStreamWriter` but also allows leaving small "gaps"
+// of fixed size in the output blob to be filled afterwards.
+//
+// Example usage:
+// ```
+// auto gap = writer->CreateGap(sizeof(ui64));
+// auto writtenSizeBefore = writer->GetTotalWrittenSize();
+// ... // Write something to the writer.
+// auto writtenSizeAfter = writer->GetTotalWrittenSize();
+// ui64 size = writtenSizeAfter - writtenSizeBefore;
+// memcpy(writer->GetGapPointer(gap), &size, sizeof(size));
+// ```
+class TZeroCopyWriterWithGaps
+ : public TZeroCopyWriterWithGapsBase
+ , public TZeroCopyOutputStreamWriter
+{
+public:
+ static constexpr ui64 MaxGapSize = 16;
+
+ using TGapPosition = ui64;
+
+ // NOTE: We need base class to initialize `InitialSize_` before `TZeroCopyOutputStreamWriter`.
+ TZeroCopyWriterWithGaps(TBlobOutput* blobOutput)
+ : TZeroCopyWriterWithGapsBase(blobOutput->Blob())
+ , TZeroCopyOutputStreamWriter(blobOutput)
+ { }
+
+ TGapPosition CreateGap(ui64 size)
+ {
+ auto position = InitialSize_ + GetTotalWrittenSize();
+ if (size <= RemainingBytes()) {
+ Advance(size);
+ } else {
+ char Buffer[MaxGapSize];
+ YT_VERIFY(size <= MaxGapSize);
+ Write(Buffer, size);
+ }
+ return position;
+ }
+
+ char* GetGapPointer(TGapPosition gap)
+ {
+ return Blob_.Begin() + gap;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This class is responsible for writing "other columns" field in protobuf format.
+//
+// |OnBeginRow|, |OnValue|, |OnEndRow|, |GetProtobufSize| and |WriteProtoField|
+// methods can be called even if there is no "other columns" field in current table descriptor,
+// in which case they will be effectively no-op.
+class TOtherColumnsWriter
+{
+public:
+ TOtherColumnsWriter(
+ const std::vector<TTableSchemaPtr>& schemas,
+ const TNameTablePtr& nameTable,
+ const TProtobufWriterFormatDescriptionPtr& description,
+ const TYsonConverterConfig& config)
+ : NameTableReader_(nameTable)
+ , Description_(description)
+ , TableIndexToConverter_(description->GetTableCount())
+ , Writer_(
+ &OutputStream_,
+ EYsonFormat::Binary,
+ EYsonType::Node,
+ /* enableRaw */ true)
+ {
+ for (int tableIndex = 0; tableIndex < description->GetTableCount(); ++tableIndex) {
+ if (auto fieldDescription = description->FindOtherColumnsField(tableIndex)) {
+ TableIndexToConverter_[tableIndex].emplace(
+ nameTable,
+ schemas[tableIndex],
+ config);
+ }
+ }
+
+ try {
+ RowIndexColumnId_ = nameTable->GetIdOrRegisterName(RowIndexColumnName);
+ RangeIndexColumnId_ = nameTable->GetIdOrRegisterName(RangeIndexColumnName);
+ TableIndexColumnId_ = nameTable->GetIdOrRegisterName(TableIndexColumnName);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Failed to add system columns to name table for protobuf writer")
+ << ex;
+ }
+ }
+
+ void SetTableIndex(i64 tableIndex)
+ {
+ YT_VERIFY(!InsideRow_);
+ FieldDescription_ = Description_->FindOtherColumnsField(tableIndex);
+ if (FieldDescription_) {
+ Converter_ = &*TableIndexToConverter_[tableIndex];
+ } else {
+ Converter_ = nullptr;
+ }
+ }
+
+ bool IsEnabled() const
+ {
+ return FieldDescription_ != nullptr;
+ }
+
+ void OnBeginRow()
+ {
+ if (!IsEnabled()) {
+ return;
+ }
+
+ YT_VERIFY(!InsideRow_);
+ OutputStream_.Clear();
+ Writer_.OnBeginMap();
+ InsideRow_ = true;
+ }
+
+ void OnValue(TUnversionedValue value)
+ {
+ if (!IsEnabled()) {
+ return;
+ }
+
+ if (IsSystemColumnId(value.Id)) {
+ return;
+ }
+
+ YT_VERIFY(InsideRow_);
+ Writer_.OnKeyedItem(NameTableReader_.GetName(value.Id));
+ Converter_->WriteValue(value, &Writer_);
+ }
+
+ void OnEndRow()
+ {
+ if (!IsEnabled()) {
+ return;
+ }
+
+ YT_VERIFY(InsideRow_);
+ Writer_.OnEndMap();
+ InsideRow_ = false;
+ }
+
+ i64 GetProtobufSize() const
+ {
+ if (!IsEnabled()) {
+ return 0;
+ }
+
+ YT_VERIFY(!InsideRow_);
+ auto length = GetYsonString().size();
+ return FieldDescription_->TagSize + WireFormatLite::UInt32Size(length) + length;
+ }
+
+ void WriteProtoField(TZeroCopyOutputStreamWriter* writer) const
+ {
+ if (!IsEnabled()) {
+ return;
+ }
+
+ YT_VERIFY(!InsideRow_);
+ WriteVarUint32(writer, FieldDescription_->WireTag);
+ auto buffer = GetYsonString();
+ WriteVarUint32(writer, buffer.size());
+ writer->Write(buffer.begin(), buffer.size());
+ }
+
+private:
+ TStringBuf GetYsonString() const
+ {
+ return OutputStream_.Blob().ToStringBuf();
+ }
+
+ bool IsSystemColumnId(int id) const
+ {
+ return
+ TableIndexColumnId_ == id ||
+ RangeIndexColumnId_ == id ||
+ RowIndexColumnId_ == id;
+ }
+
+private:
+ const TNameTableReader NameTableReader_;
+
+ TProtobufWriterFormatDescriptionPtr Description_;
+
+ const TProtobufWriterFieldDescription* FieldDescription_ = nullptr;
+ std::vector<std::optional<TUnversionedValueYsonWriter>> TableIndexToConverter_;
+ TUnversionedValueYsonWriter* Converter_ = nullptr;
+
+ TBlobOutput OutputStream_;
+ TYsonWriter Writer_;
+
+ bool InsideRow_ = false;
+
+ int RowIndexColumnId_ = -1;
+ int RangeIndexColumnId_ = -1;
+ int TableIndexColumnId_ = -1;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEnumVisitor
+{
+public:
+ Y_FORCE_INLINE void OnInt64(i64 value)
+ {
+ InRange = TryIntegralCast<i32>(value, &EnumValue);
+ }
+
+ Y_FORCE_INLINE void OnUint64(ui64 value)
+ {
+ InRange = TryIntegralCast<i32>(value, &EnumValue);
+ }
+
+ Y_FORCE_INLINE void OnString(TStringBuf value, const TProtobufWriterTypePtr& type)
+ {
+ if (Y_UNLIKELY(!type->EnumerationDescription)) {
+ THROW_ERROR_EXCEPTION("Enumeration description not found");
+ }
+ if (SuppressUnknownValueError) {
+ if (auto enumValue = type->EnumerationDescription->TryGetValue(value)) {
+ EnumValue = *enumValue;
+ } else {
+ InRange = false;
+ }
+ } else {
+ EnumValue = type->EnumerationDescription->GetValue(value);
+ }
+ }
+
+public:
+ bool SuppressUnknownValueError = false;
+ bool InRange = true;
+ i32 EnumValue;
+};
+
+template <typename TValueExtractor>
+Y_FORCE_INLINE void WriteProtobufEnum(
+ TZeroCopyOutputStreamWriter* writer,
+ const TProtobufWriterFieldDescription& fieldDescription,
+ const TValueExtractor& extractor)
+{
+ const auto& type = fieldDescription.Type;
+
+ TEnumVisitor visitor;
+
+ if ((fieldDescription.Repeated || type->Optional) &&
+ fieldDescription.EnumWritingMode == EProtobufEnumWritingMode::SkipUnknownValues) {
+ visitor.SuppressUnknownValueError = true;
+ }
+
+ extractor.ExtractEnum(&visitor, type);
+
+ auto getEnumerationName = [&] () {
+ return type->EnumerationDescription
+ ? type->EnumerationDescription->GetEnumerationName()
+ : "<unknown>";
+ };
+
+ if (Y_UNLIKELY(!visitor.InRange)) {
+ if (visitor.SuppressUnknownValueError) {
+ return;
+ } else {
+ THROW_ERROR_EXCEPTION("Value out of range for protobuf enumeration %Qv",
+ getEnumerationName());
+ }
+ }
+ if (!fieldDescription.Packed) {
+ WriteVarUint32(writer, fieldDescription.WireTag);
+ }
+ WriteVarUint64(writer, static_cast<ui64>(visitor.EnumValue)); // No zigzag int32.
+}
+
+template <typename TValueExtractor>
+Y_FORCE_INLINE void WriteProtobufField(
+ TZeroCopyOutputStreamWriter* writer,
+ const TProtobufWriterTypePtr& type,
+ const TValueExtractor& extractor)
+{
+ switch (type->ProtoType) {
+ case EProtobufType::String:
+ case EProtobufType::Bytes:
+ case EProtobufType::Message: {
+ auto stringBuf = extractor.ExtractString();
+ WriteVarUint32(writer, stringBuf.size());
+ writer->Write(stringBuf.data(), stringBuf.size());
+ return;
+ }
+ case EProtobufType::Uint64:
+ WriteVarUint64(writer, extractor.ExtractUint64());
+ return;
+ case EProtobufType::Uint32:
+ WriteVarUint32(writer, extractor.ExtractUint64());
+ return;
+ case EProtobufType::Int64:
+ WriteVarUint64(writer, extractor.ExtractInt64()); // no zigzag
+ return;
+ case EProtobufType::Int32:
+ WriteVarUint64(writer, extractor.ExtractInt64()); // no zigzag
+ return;
+ case EProtobufType::Sint64:
+ WriteVarInt64(writer, extractor.ExtractInt64()); // zigzag
+ return;
+ case EProtobufType::Sint32:
+ WriteVarInt32(writer, extractor.ExtractInt64()); // zigzag
+ return;
+ case EProtobufType::Fixed64:
+ WritePod(*writer, extractor.ExtractUint64());
+ return;
+ case EProtobufType::Fixed32:
+ WritePod(*writer, static_cast<ui32>(extractor.ExtractUint64()));
+ return;
+ case EProtobufType::Sfixed64:
+ WritePod(*writer, extractor.ExtractInt64());
+ return;
+ case EProtobufType::Sfixed32:
+ WritePod(*writer, static_cast<i32>(extractor.ExtractInt64()));
+ return;
+ case EProtobufType::Double:
+ WritePod(*writer, extractor.ExtractDouble());
+ return;
+ case EProtobufType::Float:
+ WritePod(*writer, static_cast<float>(extractor.ExtractDouble()));
+ return;
+ case EProtobufType::Bool:
+ WritePod(*writer, static_cast<ui8>(extractor.ExtractBoolean()));
+ return;
+ case EProtobufType::EnumInt:
+ case EProtobufType::EnumString:
+ // WriteProtobufEnum must be used for writing enums
+ YT_ABORT();
+ case EProtobufType::Any:
+ case EProtobufType::OtherColumns:
+ case EProtobufType::StructuredMessage:
+ case EProtobufType::EmbeddedMessage:
+ case EProtobufType::Oneof:
+ THROW_ERROR_EXCEPTION("Wrong protobuf type %Qlv",
+ type->ProtoType);
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateYsonCursorType(const TYsonPullParserCursor* cursor, EYsonItemType expected)
+{
+ auto actual = cursor->GetCurrent().GetType();
+ if (Y_UNLIKELY(actual != expected)) {
+ THROW_ERROR_EXCEPTION("Protobuf writing error: bad YSON item, expected %Qlv, actual %Qlv",
+ expected,
+ actual);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYsonValueExtractor
+{
+public:
+ explicit TYsonValueExtractor(TYsonPullParser* parser)
+ : Parser_(parser)
+ { }
+
+ void ExtractEnum(TEnumVisitor* visitor, const TProtobufWriterTypePtr& type) const
+ {
+ auto item = Parser_->Next();
+ switch (item.GetType()) {
+ case EYsonItemType::Int64Value:
+ visitor->OnInt64(item.UncheckedAsInt64());
+ return;
+ case EYsonItemType::Uint64Value:
+ visitor->OnUint64(item.UncheckedAsUint64());
+ return;
+ case EYsonItemType::StringValue:
+ visitor->OnString(item.UncheckedAsString(), type);
+ return;
+ default:
+ const auto* enumDescription = type->EnumerationDescription;
+ THROW_ERROR_EXCEPTION("Cannot parse protobuf enumeration %Qv from YSON value of type %Qlv",
+ enumDescription ? enumDescription->GetEnumerationName() : "<unknown>",
+ item.GetType());
+ }
+ }
+
+ i64 ExtractInt64() const
+ {
+ return Parser_->ParseInt64();
+ }
+
+ ui64 ExtractUint64() const
+ {
+ return Parser_->ParseUint64();
+ }
+
+ TStringBuf ExtractString() const
+ {
+ return Parser_->ParseString();
+ }
+
+ bool ExtractBoolean() const
+ {
+ return Parser_->ParseBoolean();
+ }
+
+ double ExtractDouble() const
+ {
+ return Parser_->ParseDouble();
+ }
+
+private:
+ TYsonPullParser* const Parser_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateUnversionedValueType(const TUnversionedValue& value, EValueType type)
+{
+ if (Y_UNLIKELY(value.Type != type)) {
+ THROW_ERROR_EXCEPTION("Invalid protobuf storage type: expected %Qlv, got %Qlv",
+ type,
+ value.Type);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This class actually doesn't change the `TUnversionedValue` passed to it,
+// but is named consistently with more tricky `TYsonValueExtractor`.
+class TUnversionedValueExtractor
+{
+public:
+ explicit TUnversionedValueExtractor(TUnversionedValue value)
+ : Value_(value)
+ { }
+
+ void ExtractEnum(TEnumVisitor* visitor, const TProtobufWriterTypePtr& type) const
+ {
+ switch (Value_.Type) {
+ case EValueType::Int64:
+ visitor->OnInt64(Value_.Data.Int64);
+ return;
+ case EValueType::Uint64:
+ visitor->OnUint64(Value_.Data.Uint64);
+ return;
+ case EValueType::String:
+ visitor->OnString(Value_.AsStringBuf(), type);
+ return;
+ default:
+ const auto* enumDescription = type->EnumerationDescription;
+ THROW_ERROR_EXCEPTION("Cannot parse protobuf enumeration %Qv from unverioned value of type %Qlv",
+ enumDescription ? enumDescription->GetEnumerationName() : "<unknown>",
+ Value_.Type);
+ }
+ }
+
+ i64 ExtractInt64() const
+ {
+ ValidateUnversionedValueType(Value_, EValueType::Int64);
+ return Value_.Data.Int64;
+ }
+
+ ui64 ExtractUint64() const
+ {
+ ValidateUnversionedValueType(Value_, EValueType::Uint64);
+ return Value_.Data.Uint64;
+ }
+
+ TStringBuf ExtractString() const
+ {
+ ValidateUnversionedValueType(Value_, EValueType::String);
+ return {Value_.Data.String, Value_.Length};
+ }
+
+ bool ExtractBoolean() const
+ {
+ ValidateUnversionedValueType(Value_, EValueType::Boolean);
+ return Value_.Data.Boolean;
+ }
+
+ double ExtractDouble() const
+ {
+ ValidateUnversionedValueType(Value_, EValueType::Double);
+ return Value_.Data.Double;
+ }
+
+private:
+ const TUnversionedValue Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Write varint representation occupying exactly `size` bytes.
+// If `value` is too small, `0x80` bytes will be added in due amount.
+int WriteVarUint64WithPadding(char* output, ui64 value, int size)
+{
+ for (int i = 0; i < size - 1; ++i) {
+ *output++ = static_cast<ui8>(value | 0x80);
+ value >>= 7;
+ }
+ *output++ = static_cast<ui8>(value);
+ YT_VERIFY(value < 0x80);
+ return size;
+}
+
+static bool MatchesCompositeType(const TProtobufWriterFieldDescription& field)
+{
+ return field.Repeated ||
+ field.Type->ProtoType == EProtobufType::StructuredMessage ||
+ field.Type->ProtoType == EProtobufType::Oneof;
+}
+
+static bool MatchesEnumerateType(const TProtobufWriterFieldDescription& field)
+{
+ return field.Type->ProtoType == EProtobufType::EnumInt ||
+ field.Type->ProtoType == EProtobufType::EnumString;
+}
+
+class TWriterImpl
+{
+private:
+ using TMessageSize = ui32;
+ std::vector<TBlobOutput> EmbeddedBuffers;
+ TProtobufWriterFormatDescriptionPtr FormatDescription;
+
+public:
+ TWriterImpl(
+ const std::vector<TTableSchemaPtr>& schemas,
+ const TNameTablePtr& nameTable,
+ const TProtobufWriterFormatDescriptionPtr& description,
+ const TYsonConverterConfig& config)
+ : EmbeddedBuffers(description->GetTableDescription(0).Embeddings.size())
+ , FormatDescription(description)
+ , OtherColumnsWriter_(schemas, nameTable, description, config)
+ { }
+
+ void SetTableIndex(i64 tableIndex)
+ {
+ OtherColumnsWriter_.SetTableIndex(tableIndex);
+ }
+
+ Y_FORCE_INLINE void OnBeginRow(TZeroCopyWriterWithGaps* writer)
+ {
+ Writer_ = writer;
+ MessageSizeGapPosition_ = writer->CreateGap(sizeof(TMessageSize));
+ OtherColumnsWriter_.OnBeginRow();
+ TotalWrittenSizeBefore_ = writer->GetTotalWrittenSize();
+ }
+
+ // It's quite likely that this value can be bounded by `WireFormatLite::UInt64Size(10 * ysonLength)`,
+ // but currently we return just maximum size of a varint representation of a 32-bit number.
+ static Y_FORCE_INLINE int GetMaxVarIntSizeOfProtobufSizeOfComplexType()
+ {
+ return MaxVarUint32Size;
+ }
+
+ static Y_FORCE_INLINE ui64 GetMaxBinaryYsonSize(TUnversionedValue value)
+ {
+ switch (value.Type) {
+ case EValueType::Uint64:
+ return 1 + MaxVarUint64Size;
+ case EValueType::Int64:
+ return 1 + MaxVarInt64Size;
+ case EValueType::Double:
+ return 1 + sizeof(double);
+ case EValueType::Boolean:
+ return 1;
+ case EValueType::String:
+ return 1 + MaxVarInt32Size + value.Length;
+ case EValueType::Any:
+ case EValueType::Composite:
+ return value.Length;
+ case EValueType::Null:
+ return 1;
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+ }
+ ThrowUnexpectedValueType(value.Type);
+ }
+
+ Y_FORCE_INLINE void OnValue(TUnversionedValue value, const TProtobufWriterFieldDescription& fieldDescription)
+ {
+ if (fieldDescription.ParentEmbeddingIndex != TProtobufWriterEmbeddingDescription::InvalidIndex) {
+ TZeroCopyWriterWithGaps writer(&EmbeddedBuffers[fieldDescription.ParentEmbeddingIndex]);
+ DoOnValue(&writer, value, fieldDescription);
+ } else {
+ DoOnValue(Writer_, value, fieldDescription);
+ }
+ }
+
+ Y_FORCE_INLINE void DoOnValue(TZeroCopyWriterWithGaps* writer, TUnversionedValue value, const TProtobufWriterFieldDescription& fieldDescription)
+ {
+ if (MatchesCompositeType(fieldDescription)) {
+ ValidateUnversionedValueType(value, EValueType::Composite);
+ TMemoryInput input(value.Data.String, value.Length);
+ TYsonPullParser parser(&input, EYsonType::Node);
+ auto maxVarIntSize = GetMaxVarIntSizeOfProtobufSizeOfComplexType();
+ Traverse(writer, fieldDescription, &parser, maxVarIntSize);
+ } else if (Y_UNLIKELY(MatchesEnumerateType(fieldDescription))) {
+ WriteProtobufEnum(writer,
+ fieldDescription,
+ TUnversionedValueExtractor(value));
+ } else {
+ WriteVarUint32(writer, fieldDescription.WireTag);
+ if (fieldDescription.Type->ProtoType == EProtobufType::Any) {
+ auto maxYsonSize = GetMaxBinaryYsonSize(value);
+ WriteWithSizePrefix(writer, WireFormatLite::UInt64Size(maxYsonSize), [&] {
+ TCheckedInDebugYsonTokenWriter tokenWriter(writer);
+ UnversionedValueToYson(value, &tokenWriter);
+ });
+ } else {
+ WriteProtobufField(writer, fieldDescription.Type, TUnversionedValueExtractor(value));
+ }
+ }
+ }
+
+ Y_FORCE_INLINE void OnUnknownValue(TUnversionedValue value)
+ {
+ OtherColumnsWriter_.OnValue(value);
+ }
+
+ Y_FORCE_INLINE void OnEndRow()
+ {
+ TZeroCopyWriterWithGaps* writer = Writer_;
+ auto& embeddings = FormatDescription->GetTableDescription(0).Embeddings;
+
+ int parentEmbeddingIndex = 0;
+
+ std::function<int(int)> EmitMessage = [&](int parentEmbeddingIndex) {
+ auto& embeddingDescription = embeddings[parentEmbeddingIndex];
+ auto& blob = EmbeddedBuffers[parentEmbeddingIndex];
+
+ int myParentEmbeddingIndex = parentEmbeddingIndex;
+
+ WriteVarUint32(writer, embeddingDescription.WireTag);
+
+ WriteWithSizePrefix(writer, sizeof(TMessageSize), [&] {
+
+ writer->Write(blob.Begin(), blob.Size());
+ blob.Clear();
+ parentEmbeddingIndex++;
+ while (parentEmbeddingIndex < std::ssize(embeddings) && embeddings[parentEmbeddingIndex].ParentEmbeddingIndex == myParentEmbeddingIndex) {
+ parentEmbeddingIndex = EmitMessage(parentEmbeddingIndex);
+ }
+ });
+ return parentEmbeddingIndex;
+ };
+
+ while (parentEmbeddingIndex < std::ssize(embeddings)) {
+ Y_VERIFY(embeddings[parentEmbeddingIndex].ParentEmbeddingIndex == TProtobufWriterEmbeddingDescription::InvalidIndex);
+ parentEmbeddingIndex = EmitMessage(parentEmbeddingIndex);
+ }
+
+ OtherColumnsWriter_.OnEndRow();
+ OtherColumnsWriter_.WriteProtoField(writer);
+ writer->UndoRemaining();
+ auto totalWrittenSizeAfter = writer->GetTotalWrittenSize();
+ auto messageSize = totalWrittenSizeAfter - TotalWrittenSizeBefore_;
+ if (messageSize >= std::numeric_limits<TMessageSize>::max()) {
+ THROW_ERROR_EXCEPTION("Too large protobuf message: limit is %v, actual size is %v",
+ std::numeric_limits<TMessageSize>::max(),
+ messageSize);
+ }
+ auto messageSizeCast = static_cast<TMessageSize>(messageSize);
+ memcpy(writer->GetGapPointer(MessageSizeGapPosition_), &messageSizeCast, sizeof(messageSizeCast));
+ }
+
+private:
+ void Traverse(
+ TZeroCopyWriterWithGaps* writer,
+ const TProtobufWriterFieldDescription& fieldDescription,
+ TYsonPullParser* parser,
+ int maxVarIntSize)
+ {
+ if (fieldDescription.Repeated) {
+ if (fieldDescription.Type->Optional && parser->IsEntity()) {
+ parser->ParseEntity();
+ return;
+ }
+ if (fieldDescription.Packed) {
+ TraversePackedRepeated(writer, fieldDescription, parser, maxVarIntSize);
+ } else {
+ parser->ParseBeginList();
+ while (!parser->IsEndList()) {
+ TraverseNonRepeated(writer, fieldDescription, parser, maxVarIntSize);
+ }
+ parser->ParseEndList();
+ }
+ } else {
+ TraverseNonRepeated(writer, fieldDescription, parser, maxVarIntSize);
+ }
+ }
+
+ void TraverseOneof(
+ TZeroCopyWriterWithGaps* writer,
+ const TProtobufWriterFieldDescription& fieldDescription,
+ TYsonPullParser* parser,
+ int maxVarIntSize)
+ {
+ if (fieldDescription.Type->Optional) {
+ if (!parser->ParseOptionalBeginList()) {
+ return;
+ }
+ } else {
+ parser->ParseBeginList();
+ }
+
+ auto alternativeIndex = parser->ParseInt64();
+ auto alternative = fieldDescription.Type->FindAlternative(alternativeIndex);
+ if (alternative) {
+ Traverse(writer, *alternative, parser, maxVarIntSize);
+ } else {
+ parser->SkipComplexValue();
+ }
+ parser->ParseEndList();
+ }
+
+ template <typename TFun>
+ Y_FORCE_INLINE void WriteWithSizePrefix(TZeroCopyWriterWithGaps* writer, int maxVarIntSize, TFun writerFun)
+ {
+ auto gap = writer->CreateGap(maxVarIntSize);
+ auto totalWrittenSizeBefore = writer->GetTotalWrittenSize();
+
+ writerFun();
+
+ auto totalWrittenSizeAfter = writer->GetTotalWrittenSize();
+ auto messageSize = totalWrittenSizeAfter - totalWrittenSizeBefore;
+ WriteVarUint64WithPadding(writer->GetGapPointer(gap), messageSize, maxVarIntSize);
+ }
+
+ Y_FORCE_INLINE void TraversePackedRepeated(
+ TZeroCopyWriterWithGaps* writer,
+ const TProtobufWriterFieldDescription& fieldDescription,
+ TYsonPullParser* parser,
+ int maxVarIntSize)
+ {
+ parser->ParseBeginList();
+ if (!parser->IsEndList()) {
+ WriteVarUint32(writer, fieldDescription.WireTag);
+ WriteWithSizePrefix(writer, maxVarIntSize, [&] {
+ while (!parser->IsEndList()) {
+ if (Y_UNLIKELY(MatchesEnumerateType(fieldDescription))) {
+ WriteProtobufEnum(writer,
+ fieldDescription,
+ TYsonValueExtractor(parser));
+ } else {
+ WriteProtobufField(writer, fieldDescription.Type, TYsonValueExtractor(parser));
+ }
+ }
+ });
+ }
+ parser->ParseEndList();
+ }
+
+ Y_FORCE_INLINE void TraverseNonRepeated(
+ TZeroCopyWriterWithGaps* writer,
+ const TProtobufWriterFieldDescription& fieldDescription,
+ TYsonPullParser* parser,
+ int maxVarIntSize)
+ {
+ if (fieldDescription.Type->ProtoType == EProtobufType::Oneof) {
+ TraverseOneof(writer, fieldDescription, parser, maxVarIntSize);
+ return;
+ }
+ if (fieldDescription.Type->Optional && parser->IsEntity()) {
+ parser->ParseEntity();
+ if (fieldDescription.Type->ProtoType == EProtobufType::Any) {
+ WriteVarUint32(writer, fieldDescription.WireTag);
+ WriteWithSizePrefix(writer, 1, [&] {
+ TCheckedInDebugYsonTokenWriter ysonWriter(writer);
+ ysonWriter.WriteEntity();
+ });
+ }
+ return;
+ }
+ if (MatchesEnumerateType(fieldDescription)) {
+ WriteProtobufEnum(writer,
+ fieldDescription,
+ TYsonValueExtractor(parser));
+ return;
+ }
+
+ WriteVarUint32(writer, fieldDescription.WireTag);
+ switch (fieldDescription.Type->ProtoType) {
+ case EProtobufType::StructuredMessage:
+ WriteWithSizePrefix(writer, maxVarIntSize, [&] {
+ TraverseStruct(writer, fieldDescription, parser, maxVarIntSize);
+ });
+ return;
+ case EProtobufType::Any:
+ WriteWithSizePrefix(writer, maxVarIntSize, [&] {
+ TCheckedInDebugYsonTokenWriter ysonWriter(writer);
+ parser->TransferComplexValue(&ysonWriter);
+ });
+ return;
+ case EProtobufType::Oneof:
+ YT_ABORT();
+ default:
+ WriteProtobufField(writer, fieldDescription.Type, TYsonValueExtractor(parser));
+ return;
+ }
+ YT_ABORT();
+ }
+
+ Y_FORCE_INLINE void TraverseStruct(
+ TZeroCopyWriterWithGaps* writer,
+ const TProtobufWriterFieldDescription& fieldDescription,
+ TYsonPullParser* parser,
+ int maxVarIntSize)
+ {
+ parser->ParseBeginList();
+ auto childIterator = fieldDescription.Type->Children.cbegin();
+ int elementIndex = 0;
+ while (!parser->IsEndList()) {
+ if (childIterator == fieldDescription.Type->Children.cend() || (*childIterator)->StructFieldIndex != elementIndex) {
+ parser->SkipComplexValue();
+ ++elementIndex;
+ continue;
+ }
+ const auto& child = **childIterator;
+ if (child.Repeated) {
+ Traverse(writer, child, parser, maxVarIntSize);
+ } else {
+ TraverseNonRepeated(writer, child, parser, maxVarIntSize);
+ }
+ ++childIterator;
+ ++elementIndex;
+ }
+ parser->ParseEndList();
+ }
+
+private:
+ TOtherColumnsWriter OtherColumnsWriter_;
+ TZeroCopyWriterWithGaps* Writer_;
+ TZeroCopyWriterWithGaps::TGapPosition MessageSizeGapPosition_;
+ ui64 TotalWrittenSizeBefore_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessWriterForProtobuf
+ : public TSchemalessFormatWriterBase
+{
+public:
+ TSchemalessWriterForProtobuf(
+ const std::vector<TTableSchemaPtr>& schemas,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount,
+ TProtobufWriterFormatDescriptionPtr description,
+ const TYsonConverterConfig& ysonConfig)
+ : TSchemalessFormatWriterBase(
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount)
+ , Description_(description)
+ , WriterImpl_(schemas, nameTable, description, ysonConfig)
+ , StreamWriter_(GetOutputStream())
+ {
+ WriterImpl_.SetTableIndex(CurrentTableIndex_);
+ }
+
+private:
+ void DoWrite(TRange<TUnversionedRow> rows) override
+ {
+ int rowCount = static_cast<int>(rows.Size());
+ for (int index = 0; index < rowCount; ++index) {
+ auto row = rows[index];
+
+ if (CheckKeySwitch(row, index + 1 == rowCount)) {
+ WritePod(*StreamWriter_, LenvalKeySwitch);
+ }
+
+ WriteControlAttributes(row);
+
+ WriterImpl_.OnBeginRow(&*StreamWriter_);
+ for (const auto& value : row) {
+ const auto* fieldDescription = Description_->FindField(
+ CurrentTableIndex_,
+ value.Id,
+ NameTable_);
+
+ if (!fieldDescription) {
+ WriterImpl_.OnUnknownValue(value);
+ continue;
+ }
+
+ if (value.Type == EValueType::Null) {
+ continue;
+ }
+
+ try {
+ WriterImpl_.OnValue(value, *fieldDescription);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error writing value of field %Qv",
+ fieldDescription->Name)
+ << ex;
+ }
+ }
+ WriterImpl_.OnEndRow();
+ TryFlushBufferAndUpdateWriter(false);
+ }
+ TryFlushBufferAndUpdateWriter(true);
+ }
+
+ void TryFlushBufferAndUpdateWriter(bool force)
+ {
+ TryFlushBuffer(force);
+ // |StreamWriter_| could have been reset in |FlushWriter()|.
+ if (!StreamWriter_) {
+ StreamWriter_.emplace(GetOutputStream());
+ }
+ }
+
+ void FlushWriter() override
+ {
+ // Reset |StreamWriter_| to ensure it will never touch the
+ // underlying |TBlobOutput| as it will be |Flush()|-ed soon.
+ StreamWriter_.reset();
+ TSchemalessFormatWriterBase::FlushWriter();
+ }
+
+ void WriteTableIndex(i64 tableIndex) override
+ {
+ CurrentTableIndex_ = tableIndex;
+ WriterImpl_.SetTableIndex(tableIndex);
+
+ WritePod(*StreamWriter_, static_cast<ui32>(LenvalTableIndexMarker));
+ WritePod(*StreamWriter_, static_cast<ui32>(tableIndex));
+ }
+
+ void WriteTabletIndex(i64 tabletIndex) override
+ {
+ WritePod(*StreamWriter_, static_cast<ui32>(LenvalTabletIndexMarker));
+ WritePod(*StreamWriter_, static_cast<ui64>(tabletIndex));
+ }
+
+ void WriteRangeIndex(i64 rangeIndex) override
+ {
+ WritePod(*StreamWriter_, static_cast<ui32>(LenvalRangeIndexMarker));
+ WritePod(*StreamWriter_, static_cast<ui32>(rangeIndex));
+ }
+
+ void WriteRowIndex(i64 rowIndex) override
+ {
+ WritePod(*StreamWriter_, static_cast<ui32>(LenvalRowIndexMarker));
+ WritePod(*StreamWriter_, static_cast<ui64>(rowIndex));
+ }
+
+ void WriteEndOfStream() override
+ {
+ WritePod(*StreamWriter_, static_cast<ui32>(LenvalEndOfStream));
+ }
+
+private:
+ const TProtobufWriterFormatDescriptionPtr Description_;
+ TWriterImpl WriterImpl_;
+
+ // Use optional to be able to destruct underlying object when switching output streams.
+ std::optional<TZeroCopyWriterWithGaps> StreamWriter_;
+
+ int CurrentTableIndex_ = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateWriterForProtobuf(
+ TProtobufFormatConfigPtr config,
+ const std::vector<TTableSchemaPtr>& schemas,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ auto description = New<TProtobufWriterFormatDescription>();
+ description->Init(config, schemas);
+ TYsonConverterConfig ysonConfig{
+ .ComplexTypeMode = config->ComplexTypeMode,
+ .DecimalMode = config->DecimalMode,
+ .TimeMode = config->TimeMode,
+ .UuidMode = config->UuidMode,
+ };
+
+ return New<TSchemalessWriterForProtobuf>(
+ schemas,
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount,
+ std::move(description),
+ ysonConfig);
+}
+
+ISchemalessFormatWriterPtr CreateWriterForProtobuf(
+ const IAttributeDictionary& attributes,
+ const std::vector<TTableSchemaPtr>& schemas,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ try {
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(&attributes);
+ return CreateWriterForProtobuf(
+ config,
+ schemas,
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(EErrorCode::InvalidFormat, "Failed to parse config for protobuf format") << ex;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/protobuf_writer.h b/yt/yt/client/formats/protobuf_writer.h
new file mode 100644
index 0000000000..a6f7936405
--- /dev/null
+++ b/yt/yt/client/formats/protobuf_writer.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateWriterForProtobuf(
+ TProtobufFormatConfigPtr config,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+ISchemalessFormatWriterPtr CreateWriterForProtobuf(
+ const NYTree::IAttributeDictionary& attributes,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/public.h b/yt/yt/client/formats/public.h
new file mode 100644
index 0000000000..4087a81d44
--- /dev/null
+++ b/yt/yt/client/formats/public.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((InvalidFormat) (2800))
+);
+
+DEFINE_ENUM(EComplexTypeMode,
+ (Positional)
+ (Named)
+);
+
+DEFINE_ENUM(EDictMode,
+ (Positional)
+ (Named)
+);
+
+DEFINE_ENUM(EDecimalMode,
+ (Text)
+ (Binary)
+);
+
+DEFINE_ENUM(ETimeMode,
+ (Text)
+ (Binary)
+);
+
+DEFINE_ENUM(EUuidMode,
+ (TextYql)
+ (TextYt)
+ (Binary)
+);
+
+//! Type of data that can be read or written by a driver command.
+DEFINE_ENUM(EDataType,
+ (Null)
+ (Binary)
+ (Structured)
+ (Tabular)
+);
+
+DEFINE_ENUM(EFormatType,
+ (Null)
+ (Yson)
+ (Json)
+ (Dsv)
+ (Yamr)
+ (YamredDsv)
+ (SchemafulDsv)
+ (Protobuf)
+ (WebJson)
+ (Skiff)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TYsonFormatConfig)
+DECLARE_REFCOUNTED_CLASS(TTableFormatConfigBase)
+DECLARE_REFCOUNTED_CLASS(TYamrFormatConfig)
+DECLARE_REFCOUNTED_CLASS(TYamrFormatConfigBase)
+DECLARE_REFCOUNTED_CLASS(TDsvFormatConfig)
+DECLARE_REFCOUNTED_CLASS(TDsvFormatConfigBase)
+DECLARE_REFCOUNTED_CLASS(TYamredDsvFormatConfig)
+DECLARE_REFCOUNTED_CLASS(TSchemafulDsvFormatConfig)
+DECLARE_REFCOUNTED_CLASS(TProtobufTypeConfig)
+DECLARE_REFCOUNTED_CLASS(TProtobufColumnConfig)
+DECLARE_REFCOUNTED_CLASS(TProtobufTableConfig)
+DECLARE_REFCOUNTED_CLASS(TProtobufFormatConfig)
+DECLARE_REFCOUNTED_CLASS(TWebJsonFormatConfig)
+DECLARE_REFCOUNTED_CLASS(TSkiffFormatConfig)
+
+DECLARE_REFCOUNTED_STRUCT(IYamrConsumer)
+
+DECLARE_REFCOUNTED_STRUCT(ISchemalessFormatWriter)
+
+DECLARE_REFCOUNTED_CLASS(TControlAttributesConfig)
+
+struct IParser;
+
+class TEscapeTable;
+
+class TFormat;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/schemaful_dsv_parser.cpp b/yt/yt/client/formats/schemaful_dsv_parser.cpp
new file mode 100644
index 0000000000..8fb0bda433
--- /dev/null
+++ b/yt/yt/client/formats/schemaful_dsv_parser.cpp
@@ -0,0 +1,198 @@
+#include "schemaful_dsv_parser.h"
+
+#include "parser.h"
+#include "escape.h"
+#include "format.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+namespace NYT::NFormats {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemafulDsvParser
+ : public IParser
+{
+public:
+ TSchemafulDsvParser(
+ IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config);
+
+ void Read(TStringBuf data) override;
+ void Finish() override;
+
+private:
+ IYsonConsumer* Consumer_;
+ TSchemafulDsvFormatConfigPtr Config_;
+ const std::vector<TString>& Columns_;
+
+ TEscapeTable EscapeTable_;
+
+ bool NewRecordStarted_ = false;
+ bool ExpectingEscapedChar_ = false;
+
+ int RowIndex_ = 0;
+ int FieldIndex_ = 0;
+
+ int TableIndex_ = 0;
+
+ TString CurrentToken_;
+
+ const char* Consume(const char* begin, const char* end);
+ void SwitchTable(int newTableIndex);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSchemafulDsvParser::TSchemafulDsvParser(
+ IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config)
+ : Consumer_(consumer)
+ , Config_(config)
+ , Columns_(Config_->GetColumnsOrThrow())
+{
+ ConfigureEscapeTable(Config_, &EscapeTable_);
+}
+
+void TSchemafulDsvParser::Read(TStringBuf data)
+{
+ auto current = data.begin();
+ while (current != data.end()) {
+ current = Consume(current, data.end());
+ }
+}
+
+const char* TSchemafulDsvParser::Consume(const char* begin, const char* end)
+{
+ // Process escaping symbols.
+ if (Config_->EnableEscaping && !ExpectingEscapedChar_ && *begin == Config_->EscapingSymbol) {
+ ExpectingEscapedChar_ = true;
+ return begin + 1;
+ }
+ if (ExpectingEscapedChar_) {
+ CurrentToken_.append(EscapeBackward[static_cast<ui8>(*begin)]);
+ ExpectingEscapedChar_ = false;
+ return begin + 1;
+ }
+
+ // Process common case.
+ auto next = EscapeTable_.FindNext(begin, end);
+ CurrentToken_.append(begin, next);
+ if (next == end || *next == Config_->EscapingSymbol) {
+ return next;
+ }
+
+ YT_VERIFY(*next == Config_->FieldSeparator ||
+ *next == Config_->RecordSeparator);
+
+ if (!NewRecordStarted_) {
+ NewRecordStarted_ = true;
+
+ if (Config_->EnableTableIndex) {
+ SwitchTable(FromString<int>(CurrentToken_));
+ }
+
+ Consumer_->OnListItem();
+ Consumer_->OnBeginMap();
+
+ if (Config_->EnableTableIndex) {
+ CurrentToken_.clear();
+ return next + 1;
+ }
+ }
+
+ if (FieldIndex_ == std::ssize(Columns_)) {
+ THROW_ERROR_EXCEPTION("Too many fields in row: expected %v but found more",
+ std::ssize(Columns_));
+ }
+
+ Consumer_->OnKeyedItem(Columns_[FieldIndex_++]);
+
+ if (Config_->MissingValueMode == EMissingSchemafulDsvValueMode::PrintSentinel &&
+ CurrentToken_ == Config_->MissingValueSentinel)
+ {
+ Consumer_->OnEntity();
+ } else {
+ Consumer_->OnStringScalar(CurrentToken_);
+ }
+
+ CurrentToken_.clear();
+
+ if (*next == Config_->RecordSeparator) {
+ if (FieldIndex_ != std::ssize(Columns_)) {
+ THROW_ERROR_EXCEPTION("Row %v is incomplete: expected %v fields but found %v",
+ RowIndex_,
+ std::ssize(Columns_),
+ FieldIndex_);
+ }
+ Consumer_->OnEndMap();
+ NewRecordStarted_ = false;
+ FieldIndex_ = 0;
+
+ RowIndex_ += 1;
+ }
+ return next + 1;
+}
+
+void TSchemafulDsvParser::SwitchTable(int newTableIndex)
+{
+ static const TString key = FormatEnum(NTableClient::EControlAttribute(
+ NTableClient::EControlAttribute::TableIndex));
+ if (newTableIndex != TableIndex_) {
+ TableIndex_ = newTableIndex;
+
+ Consumer_->OnListItem();
+ Consumer_->OnBeginAttributes();
+ Consumer_->OnKeyedItem(key);
+ Consumer_->OnInt64Scalar(TableIndex_);
+ Consumer_->OnEndAttributes();
+ Consumer_->OnEntity();
+ }
+}
+
+void TSchemafulDsvParser::Finish()
+{
+ if (NewRecordStarted_ || !CurrentToken_.empty() || ExpectingEscapedChar_) {
+ THROW_ERROR_EXCEPTION("Row %v is not finished", RowIndex_);
+ }
+ CurrentToken_.clear();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseSchemafulDsv(
+ IInputStream* input,
+ IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config)
+{
+ auto parser = CreateParserForSchemafulDsv(consumer, config);
+ Parse(input, parser.get());
+}
+
+void ParseSchemafulDsv(
+ TStringBuf data,
+ IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config)
+{
+ auto parser = CreateParserForSchemafulDsv(consumer, config);
+ parser->Read(data);
+ parser->Finish();
+}
+
+std::unique_ptr<IParser> CreateParserForSchemafulDsv(
+ IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config)
+{
+ if (config->EnableColumnNamesHeader) {
+ THROW_ERROR_EXCEPTION("Parameter %Qv must not be specified for schemaful DSV parser",
+ "enable_column_names_header");
+ }
+ return std::unique_ptr<IParser>(new TSchemafulDsvParser(consumer, config));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/schemaful_dsv_parser.h b/yt/yt/client/formats/schemaful_dsv_parser.h
new file mode 100644
index 0000000000..cc01a9b399
--- /dev/null
+++ b/yt/yt/client/formats/schemaful_dsv_parser.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+#include <yt/yt/core/yson/consumer.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForSchemafulDsv(
+ NYson::IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config = New<TSchemafulDsvFormatConfig>());
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseSchemafulDsv(
+ IInputStream* input,
+ NYson::IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config = New<TSchemafulDsvFormatConfig>());
+
+void ParseSchemafulDsv(
+ TStringBuf data,
+ NYson::IYsonConsumer* consumer,
+ TSchemafulDsvFormatConfigPtr config = New<TSchemafulDsvFormatConfig>());
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/schemaful_dsv_writer.cpp b/yt/yt/client/formats/schemaful_dsv_writer.cpp
new file mode 100644
index 0000000000..17b9210ff9
--- /dev/null
+++ b/yt/yt/client/formats/schemaful_dsv_writer.cpp
@@ -0,0 +1,397 @@
+#include "schemaful_dsv_writer.h"
+
+#include "escape.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/format.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <limits>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYTree;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This class contains methods common for TSchemafulWriterForSchemafulDsv and TSchemalessWriterForSchemafulDsv.
+class TSchemafulDsvWriterBase
+{
+protected:
+ TSchemafulDsvFormatConfigPtr Config_;
+
+ // This array indicates on which position should each
+ // column stay in the resulting row.
+ std::vector<int> IdToIndexInRow_;
+
+ // This array contains TUnversionedValue's reordered
+ // according to the desired order.
+ std::vector<const TUnversionedValue*> CurrentRowValues_;
+
+ TEscapeTable EscapeTable_;
+
+ TSchemafulDsvWriterBase(TSchemafulDsvFormatConfigPtr config, std::vector<int> idToIndexInRow)
+ : Config_(config)
+ , IdToIndexInRow_(idToIndexInRow)
+ {
+ ConfigureEscapeTable(Config_, &EscapeTable_);
+ if (!IdToIndexInRow_.empty()) {
+ CurrentRowValues_.resize(
+ *std::max_element(IdToIndexInRow_.begin(), IdToIndexInRow_.end()) + 1);
+ }
+ YT_VERIFY(Config_->Columns);
+ }
+
+ int FindMissingValueIndex() const
+ {
+ for (int valueIndex = 0; valueIndex < static_cast<int>(CurrentRowValues_.size()); ++valueIndex) {
+ const auto* value = CurrentRowValues_[valueIndex];
+ if (!value || value->Type == EValueType::Null) {
+ return valueIndex;
+ }
+ }
+ return -1;
+ }
+
+ template <class TFunction>
+ void WriteColumnNamesHeader(TFunction writeCallback)
+ {
+ if (Config_->EnableColumnNamesHeader && *Config_->EnableColumnNamesHeader) {
+ const auto& columns = *Config_->Columns;
+ for (size_t index = 0; index < columns.size(); ++index) {
+ writeCallback(
+ columns[index],
+ (index + 1 == columns.size()) ? Config_->RecordSeparator : Config_->FieldSeparator);
+ }
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessWriterForSchemafulDsv
+ : public TSchemalessFormatWriterBase
+ , public TSchemafulDsvWriterBase
+{
+public:
+ TSchemalessWriterForSchemafulDsv(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ TSchemafulDsvFormatConfigPtr config,
+ std::vector<int> idToIndexInRow)
+ : TSchemalessFormatWriterBase(
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ 0 /* keyColumnCount */)
+ , TSchemafulDsvWriterBase(
+ config,
+ idToIndexInRow)
+ , TableIndexColumnId_(Config_->EnableTableIndex && controlAttributesConfig->EnableTableIndex
+ ? nameTable->GetId(TableIndexColumnName)
+ : -1)
+ {
+ BlobOutput_ = GetOutputStream();
+ WriteColumnNamesHeader([this](TStringBuf buf, char c) {
+ WriteRaw(buf);
+ WriteRaw(c);
+ });
+ }
+
+private:
+ TBlobOutput* BlobOutput_;
+ const int TableIndexColumnId_;
+
+ // ISchemalessFormatWriter overrides.
+ void DoWrite(TRange<TUnversionedRow> rows) override
+ {
+ for (const auto& row : rows) {
+ CurrentRowValues_.assign(CurrentRowValues_.size(), nullptr);
+ for (auto item = row.Begin(); item != row.End(); ++item) {
+ if (item->Id < IdToIndexInRow_.size() && IdToIndexInRow_[item->Id] != -1) {
+ CurrentRowValues_[IdToIndexInRow_[item->Id]] = item;
+ }
+ }
+
+ if (Config_->EnableTableIndex && ControlAttributesConfig_->EnableTableIndex &&
+ !CurrentRowValues_[IdToIndexInRow_[TableIndexColumnId_]])
+ {
+ THROW_ERROR_EXCEPTION("Table index column is missing");
+ }
+
+ int missingValueIndex = FindMissingValueIndex();
+ if (missingValueIndex != -1) {
+ if (Config_->MissingValueMode == EMissingSchemafulDsvValueMode::SkipRow) {
+ continue;
+ } else if (Config_->MissingValueMode == EMissingSchemafulDsvValueMode::Fail) {
+ THROW_ERROR_EXCEPTION("Column %Qv is in schema but missing", (*Config_->Columns)[missingValueIndex]);
+ }
+ }
+
+ bool firstValue = true;
+ for (const auto* item : CurrentRowValues_) {
+ if (!firstValue) {
+ WriteRaw(Config_->FieldSeparator);
+ } else {
+ firstValue = false;
+ }
+ if (!item || item->Type == EValueType::Null) {
+ // If we got here, MissingValueMode is PrintSentinel.
+ WriteRaw(Config_->MissingValueSentinel);
+ } else {
+ WriteUnversionedValue(*item, BlobOutput_, EscapeTable_);
+ }
+ }
+ WriteRaw(Config_->RecordSeparator);
+ TryFlushBuffer(false);
+ }
+ TryFlushBuffer(true);
+ }
+
+ void WriteRaw(TStringBuf str)
+ {
+ BlobOutput_->Write(str.begin(), str.length());
+ }
+
+ void WriteRaw(char ch)
+ {
+ BlobOutput_->Write(ch);
+ }
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemafulWriterForSchemafulDsv
+ : public IUnversionedRowsetWriter
+ , public TSchemafulDsvWriterBase
+{
+public:
+ TSchemafulWriterForSchemafulDsv(
+ IAsyncOutputStreamPtr stream,
+ TSchemafulDsvFormatConfigPtr config,
+ std::vector<int> IdToIndexInRow)
+ : TSchemafulDsvWriterBase(
+ config,
+ IdToIndexInRow)
+ , Output_(CreateBufferedSyncAdapter(stream))
+ {
+ WriteColumnNamesHeader([this](TStringBuf buf, char c) {
+ Output_->Write(buf);
+ Output_->Write(c);
+ });
+ }
+
+ TFuture<void> Close() override
+ {
+ DoFlushBuffer();
+ return VoidFuture;
+ }
+
+ bool Write(TRange<TUnversionedRow> rows) override
+ {
+ for (const auto& row : rows) {
+ if (!row) {
+ THROW_ERROR_EXCEPTION("Empty rows are not supported by schemaful dsv writer");
+ }
+
+ CurrentRowValues_.assign(CurrentRowValues_.size(), nullptr);
+ for (auto item = row.Begin(); item != row.End(); ++item) {
+ YT_ASSERT(item->Id >= 0 && item->Id < IdToIndexInRow_.size());
+ if (IdToIndexInRow_[item->Id] != -1) {
+ CurrentRowValues_[IdToIndexInRow_[item->Id]] = item;
+ }
+ }
+
+ int missingValueIndex = FindMissingValueIndex();
+ if (missingValueIndex != -1) {
+ if (Config_->MissingValueMode == EMissingSchemafulDsvValueMode::SkipRow) {
+ continue;
+ } else if (Config_->MissingValueMode == EMissingSchemafulDsvValueMode::Fail) {
+ THROW_ERROR_EXCEPTION("Column %Qv is in schema but missing", (*Config_->Columns)[missingValueIndex]);
+ }
+ }
+
+ bool firstValue = true;
+ for (const auto* item : CurrentRowValues_) {
+ if (!firstValue) {
+ Output_->Write(Config_->FieldSeparator);
+ } else {
+ firstValue = false;
+ }
+ if (!item || item->Type == EValueType::Null) {
+ // If we got here, MissingValueMode is PrintSentinel.
+ Output_->Write(Config_->MissingValueSentinel);
+ } else {
+ WriteUnversionedValue(*item, Output_.get(), EscapeTable_);
+ }
+ }
+ Output_->Write(Config_->RecordSeparator);
+ }
+ DoFlushBuffer();
+
+ return true;
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ return Result_;
+ }
+
+private:
+ std::unique_ptr<IOutputStream> Output_;
+
+ void DoFlushBuffer()
+ {
+ Output_->Flush();
+ }
+
+ TFuture<void> Result_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateDuplicateColumns(const std::vector<TString>& columns)
+{
+ THashSet<TString> names;
+ for (const auto& name : columns) {
+ if (!names.insert(name).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column name %Qv in schemaful DSV config",
+ name);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForSchemafulDsv(
+ TSchemafulDsvFormatConfigPtr config,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int /* keyColumnCount */)
+{
+ if (controlAttributesConfig->EnableKeySwitch) {
+ THROW_ERROR_EXCEPTION("Key switches are not supported in schemaful DSV format");
+ }
+
+ if (controlAttributesConfig->EnableRangeIndex) {
+ THROW_ERROR_EXCEPTION("Range indices are not supported in schemaful DSV format");
+ }
+
+ if (controlAttributesConfig->EnableRowIndex) {
+ THROW_ERROR_EXCEPTION("Row indices are not supported in schemaful DSV format");
+ }
+
+ if (controlAttributesConfig->EnableTabletIndex) {
+ THROW_ERROR_EXCEPTION("Tablet indices are not supported in schemaful DSV format");
+ }
+
+ if (!config->Columns) {
+ THROW_ERROR_EXCEPTION("Config must contain columns for schemaful DSV schemaless writer");
+ }
+
+ std::vector<int> idToIndexInRow;
+ auto columns = *config->Columns;
+
+ if (config->EnableTableIndex && controlAttributesConfig->EnableTableIndex) {
+ columns.insert(columns.begin(), TableIndexColumnName);
+ }
+
+ ValidateDuplicateColumns(columns);
+
+ try {
+ for (int columnIndex = 0; columnIndex < static_cast<int>(columns.size()); ++columnIndex) {
+ nameTable->GetIdOrRegisterName(columns[columnIndex]);
+ }
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Failed to add columns to name table for schemaful DSV format")
+ << ex;
+ }
+
+ idToIndexInRow.resize(nameTable->GetSize(), -1);
+ for (int columnIndex = 0; columnIndex < static_cast<int>(columns.size()); ++columnIndex) {
+ idToIndexInRow[nameTable->GetId(columns[columnIndex])] = columnIndex;
+ }
+
+ return New<TSchemalessWriterForSchemafulDsv>(
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ config,
+ idToIndexInRow);
+}
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForSchemafulDsv(
+ const IAttributeDictionary& attributes,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ try {
+ auto config = ConvertTo<TSchemafulDsvFormatConfigPtr>(&attributes);
+ return CreateSchemalessWriterForSchemafulDsv(
+ config,
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(EErrorCode::InvalidFormat, "Failed to parse config for schemaful DSV format") << ex;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IUnversionedRowsetWriterPtr CreateSchemafulWriterForSchemafulDsv(
+ TSchemafulDsvFormatConfigPtr config,
+ TTableSchemaPtr schema,
+ IAsyncOutputStreamPtr stream)
+{
+ std::vector<int> idToIndexInRow(schema->GetColumnCount(), -1);
+ if (config->Columns) {
+ ValidateDuplicateColumns(*config->Columns);
+ for (int columnIndex = 0; columnIndex < static_cast<int>(config->Columns->size()); ++columnIndex) {
+ idToIndexInRow[schema->GetColumnIndexOrThrow((*config->Columns)[columnIndex])] = columnIndex;
+ }
+ } else {
+ std::iota(idToIndexInRow.begin(), idToIndexInRow.end(), 0);
+ }
+
+ return New<TSchemafulWriterForSchemafulDsv>(
+ std::move(stream),
+ std::move(config),
+ idToIndexInRow);
+}
+
+IUnversionedRowsetWriterPtr CreateSchemafulWriterForSchemafulDsv(
+ const IAttributeDictionary& attributes,
+ TTableSchemaPtr schema,
+ IAsyncOutputStreamPtr stream)
+{
+ auto config = ConvertTo<TSchemafulDsvFormatConfigPtr>(&attributes);
+ return CreateSchemafulWriterForSchemafulDsv(
+ std::move(config),
+ std::move(schema),
+ std::move(stream));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/schemaful_dsv_writer.h b/yt/yt/client/formats/schemaful_dsv_writer.h
new file mode 100644
index 0000000000..c420f9e7ea
--- /dev/null
+++ b/yt/yt/client/formats/schemaful_dsv_writer.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+#include "helpers.h"
+#include "schemaless_writer_adapter.h"
+
+#include <yt/yt/client/table_client/unversioned_writer.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <yt/yt/core/misc/blob.h>
+#include <yt/yt/core/misc/blob_output.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForSchemafulDsv(
+ TSchemafulDsvFormatConfigPtr config,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForSchemafulDsv(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTableClient::IUnversionedRowsetWriterPtr CreateSchemafulWriterForSchemafulDsv(
+ TSchemafulDsvFormatConfigPtr config,
+ NTableClient::TTableSchemaPtr schema,
+ NConcurrency::IAsyncOutputStreamPtr stream);
+
+NTableClient::IUnversionedRowsetWriterPtr CreateSchemafulWriterForSchemafulDsv(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TTableSchemaPtr schema,
+ NConcurrency::IAsyncOutputStreamPtr stream);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/schemaful_writer.cpp b/yt/yt/client/formats/schemaful_writer.cpp
new file mode 100644
index 0000000000..c7f72d5544
--- /dev/null
+++ b/yt/yt/client/formats/schemaful_writer.cpp
@@ -0,0 +1,119 @@
+#include "schemaful_writer.h"
+#include "config.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYson;
+using namespace NTableClient;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSchemafulWriter::TSchemafulWriter(
+ NConcurrency::IAsyncOutputStreamPtr stream,
+ NTableClient::TTableSchemaPtr schema,
+ const std::function<std::unique_ptr<IFlushableYsonConsumer>(IZeroCopyOutput*)>& consumerBuilder)
+ : Stream_(std::move(stream))
+ , Schema_(std::move(schema))
+ , Consumer_(consumerBuilder(&Buffer_))
+{
+ auto nameTable = TNameTable::FromSchema(*Schema_);
+
+ for (const auto& column : Schema_->Columns()) {
+ if (IsV3Composite(column.LogicalType())) {
+ auto id = nameTable->GetIdOrThrow(column.Name());
+ TComplexTypeFieldDescriptor descriptor(column.Name(), column.LogicalType());
+ auto converter = CreateYsonServerToClientConverter(descriptor, /*config*/ {});
+ if (converter) {
+ ColumnConverters_.emplace(id, std::move(converter));
+ }
+ }
+ }
+}
+
+TFuture<void> TSchemafulWriter::Close()
+{
+ return Result_;
+}
+
+bool TSchemafulWriter::Write(TRange<TUnversionedRow> rows)
+{
+ Buffer_.Clear();
+
+ int columnCount = Schema_->GetColumnCount();
+ for (auto row : rows) {
+ if (!row) {
+ Consumer_->OnEntity();
+ continue;
+ }
+
+ YT_ASSERT(static_cast<int>(row.GetCount()) >= columnCount);
+ Consumer_->OnBeginMap();
+ for (int index = 0; index < columnCount; ++index) {
+ const auto& value = row[index];
+
+ const auto& column = Schema_->Columns()[index];
+ Consumer_->OnKeyedItem(column.Name());
+
+ switch (value.Type) {
+ case EValueType::Int64:
+ Consumer_->OnInt64Scalar(value.Data.Int64);
+ break;
+ case EValueType::Uint64:
+ Consumer_->OnUint64Scalar(value.Data.Uint64);
+ break;
+ case EValueType::Double:
+ Consumer_->OnDoubleScalar(value.Data.Double);
+ break;
+ case EValueType::Boolean:
+ Consumer_->OnBooleanScalar(value.Data.Boolean);
+ break;
+ case EValueType::String:
+ Consumer_->OnStringScalar(value.AsStringBuf());
+ break;
+ case EValueType::Null:
+ Consumer_->OnEntity();
+ break;
+ case EValueType::Any:
+ Consumer_->OnRaw(value.AsStringBuf(), EYsonType::Node);
+ break;
+
+ case EValueType::Composite: {
+ if (auto it = ColumnConverters_.find(value.Id); it != ColumnConverters_.end()) {
+ it->second(value, Consumer_.get());
+ } else {
+ Consumer_->OnRaw(value.AsStringBuf(), EYsonType::Node);
+ }
+ break;
+ }
+
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ ThrowUnexpectedValueType(value.Type);
+ }
+ }
+ Consumer_->OnEndMap();
+ }
+
+ Consumer_->Flush();
+ auto buffer = Buffer_.Flush();
+ Result_ = Stream_->Write(buffer);
+ return Result_.IsSet() && Result_.Get().IsOK();
+}
+
+TFuture<void> TSchemafulWriter::GetReadyEvent()
+{
+ return Result_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/schemaful_writer.h b/yt/yt/client/formats/schemaful_writer.h
new file mode 100644
index 0000000000..2d4848431f
--- /dev/null
+++ b/yt/yt/client/formats/schemaful_writer.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/complex_types/yson_format_conversion.h>
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+
+#include <yt/yt/core/yson/writer.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemafulWriter
+ : public NTableClient::IUnversionedRowsetWriter
+{
+public:
+ TSchemafulWriter(
+ NConcurrency::IAsyncOutputStreamPtr stream,
+ NTableClient::TTableSchemaPtr schema,
+ const std::function<std::unique_ptr<NYson::IFlushableYsonConsumer>(IZeroCopyOutput*)>& consumerBuilder);
+
+ TFuture<void> Close() override;
+
+ bool Write(TRange<NTableClient::TUnversionedRow> rows) override;
+
+ TFuture<void> GetReadyEvent() override;
+
+private:
+ const NConcurrency::IAsyncOutputStreamPtr Stream_;
+ const NTableClient::TTableSchemaPtr Schema_;
+
+ TBlobOutput Buffer_;
+
+ TFuture<void> Result_;
+
+ const std::unique_ptr<NYson::IFlushableYsonConsumer> Consumer_;
+
+ THashMap<int, NComplexTypes::TYsonServerToClientConverter> ColumnConverters_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/schemaless_writer_adapter.cpp b/yt/yt/client/formats/schemaless_writer_adapter.cpp
new file mode 100644
index 0000000000..8970177132
--- /dev/null
+++ b/yt/yt/client/formats/schemaless_writer_adapter.cpp
@@ -0,0 +1,462 @@
+#include "schemaless_writer_adapter.h"
+#include "config.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_batch.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NFormats {
+
+using namespace NTableClient;
+using namespace NConcurrency;
+using namespace NYson;
+using namespace NYTree;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const i64 ContextBufferSize = static_cast<i64>(128 * 7) * 1024;
+static const i64 ContextBufferCapacity = static_cast<i64>(1024) * 1024;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSchemalessFormatWriterBase::TSchemalessFormatWriterBase(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+ : NameTable_(nameTable)
+ , Output_(output)
+ , EnableContextSaving_(enableContextSaving)
+ , ControlAttributesConfig_(controlAttributesConfig)
+ , KeyColumnCount_(keyColumnCount)
+ , NameTableReader_(std::make_unique<TNameTableReader>(NameTable_))
+{
+ CurrentBuffer_.Reserve(ContextBufferCapacity);
+
+ EnableRowControlAttributes_ = ControlAttributesConfig_->EnableTableIndex ||
+ ControlAttributesConfig_->EnableRangeIndex ||
+ ControlAttributesConfig_->EnableRowIndex ||
+ ControlAttributesConfig_->EnableTabletIndex;
+
+ try {
+ RowIndexId_ = NameTable_->GetIdOrRegisterName(RowIndexColumnName);
+ RangeIndexId_ = NameTable_->GetIdOrRegisterName(RangeIndexColumnName);
+ TableIndexId_ = NameTable_->GetIdOrRegisterName(TableIndexColumnName);
+ TabletIndexId_ = NameTable_->GetIdOrRegisterName(TabletIndexColumnName);
+ } catch (const std::exception& ex) {
+ Error_ = TError("Failed to add system columns to name table for a format writer") << ex;
+ }
+}
+
+TFuture<void> TSchemalessFormatWriterBase::GetReadyEvent()
+{
+ return MakeFuture(Error_);
+}
+
+TFuture<void> TSchemalessFormatWriterBase::Close()
+{
+ if (Closed_) {
+ return MakeFuture(Error_);
+ }
+
+ try {
+ if (ControlAttributesConfig_->EnableEndOfStream) {
+ WriteEndOfStream();
+ }
+ DoFlushBuffer();
+ } catch (const std::exception& ex) {
+ Error_ = TError(ex);
+ }
+
+ Closed_ = true;
+
+ return MakeFuture(Error_);
+}
+
+TBlobOutput* TSchemalessFormatWriterBase::GetOutputStream()
+{
+ return &CurrentBuffer_;
+}
+
+TBlob TSchemalessFormatWriterBase::GetContext() const
+{
+ TBlob result;
+ result.Append(PreviousBuffer_);
+ result.Append(TRef::FromBlob(CurrentBuffer_.Blob()));
+ return result;
+}
+
+void TSchemalessFormatWriterBase::TryFlushBuffer(bool force)
+{
+ if (CurrentBuffer_.Size() > ContextBufferSize || (!EnableContextSaving_ && force)) {
+ DoFlushBuffer();
+ }
+}
+
+i64 TSchemalessFormatWriterBase::GetWrittenSize() const
+{
+ return WrittenSize_;
+}
+
+void TSchemalessFormatWriterBase::FlushWriter()
+{ }
+
+void TSchemalessFormatWriterBase::DoFlushBuffer()
+{
+ FlushWriter();
+
+ if (CurrentBuffer_.Size() == 0) {
+ return;
+ }
+
+ WrittenSize_ += CurrentBuffer_.Size();
+
+ PreviousBuffer_ = CurrentBuffer_.Flush();
+ WaitFor(Output_->Write(PreviousBuffer_))
+ .ThrowOnError();
+
+ CurrentBuffer_.Reserve(ContextBufferCapacity);
+}
+
+bool TSchemalessFormatWriterBase::Write(TRange<TUnversionedRow> rows)
+{
+ if (!Error_.IsOK()) {
+ return false;
+ }
+
+ try {
+ DoWrite(rows);
+ } catch (const std::exception& ex) {
+ Error_ = TError(ex);
+ return false;
+ }
+
+ return true;
+}
+
+bool TSchemalessFormatWriterBase::WriteBatch(IUnversionedRowBatchPtr rowBatch)
+{
+ if (!Error_.IsOK()) {
+ return false;
+ }
+
+ try {
+ DoWriteBatch(rowBatch);
+ } catch (const std::exception& ex) {
+ Error_ = TError(ex);
+ return false;
+ }
+
+ return true;
+}
+
+void TSchemalessFormatWriterBase::DoWriteBatch(IUnversionedRowBatchPtr rowBatch)
+{
+ return DoWrite(rowBatch->MaterializeRows());
+}
+
+bool TSchemalessFormatWriterBase::CheckKeySwitch(TUnversionedRow row, bool isLastRow)
+{
+ if (!ControlAttributesConfig_->EnableKeySwitch) {
+ return false;
+ }
+
+ bool needKeySwitch = false;
+ try {
+ needKeySwitch = CurrentKey_ && CompareRows(row, CurrentKey_, KeyColumnCount_);
+ CurrentKey_ = row;
+ } catch (const std::exception& ex) {
+ // COMPAT(psushin): composite values are not comparable anymore.
+ THROW_ERROR_EXCEPTION("Cannot inject key switch into output stream") << ex;
+ }
+
+ if (isLastRow && CurrentKey_) {
+ // After processing last row we create a copy of CurrentKey.
+ LastKey_ = GetKeyPrefix(CurrentKey_, KeyColumnCount_);
+ CurrentKey_ = LastKey_;
+ }
+
+ return needKeySwitch;
+}
+
+bool TSchemalessFormatWriterBase::IsSystemColumnId(int id) const
+{
+ return IsTableIndexColumnId(id) ||
+ IsRangeIndexColumnId(id) ||
+ IsRowIndexColumnId(id) ||
+ IsTabletIndexColumnId(id);
+}
+
+bool TSchemalessFormatWriterBase::IsTableIndexColumnId(int id) const
+{
+ return id == TableIndexId_;
+}
+
+bool TSchemalessFormatWriterBase::IsRowIndexColumnId(int id) const
+{
+ return id == RowIndexId_;
+}
+
+bool TSchemalessFormatWriterBase::IsRangeIndexColumnId(int id) const
+{
+ return id == RangeIndexId_;
+}
+
+bool TSchemalessFormatWriterBase::IsTabletIndexColumnId(int id) const
+{
+ return id == TabletIndexId_;
+}
+
+int TSchemalessFormatWriterBase::GetRangeIndexColumnId() const
+{
+ return RangeIndexId_;
+}
+
+int TSchemalessFormatWriterBase::GetRowIndexColumnId() const
+{
+ return RowIndexId_;
+}
+
+int TSchemalessFormatWriterBase::GetTableIndexColumnId() const
+{
+ return TableIndexId_;
+}
+
+int TSchemalessFormatWriterBase::GetTabletIndexColumnId() const
+{
+ return TabletIndexId_;
+}
+
+void TSchemalessFormatWriterBase::WriteControlAttributes(TUnversionedRow row)
+{
+ if (!EnableRowControlAttributes_) {
+ return;
+ }
+
+ ++RowIndex_;
+
+ std::optional<i64> tableIndex;
+ std::optional<i64> rangeIndex;
+ std::optional<i64> rowIndex;
+ std::optional<i64> tabletIndex;
+
+ for (auto* it = row.Begin(); it != row.End(); ++it) {
+ if (it->Id == TableIndexId_) {
+ tableIndex = it->Data.Int64;
+ } else if (it->Id == RowIndexId_) {
+ rowIndex = it->Data.Int64;
+ } else if (it->Id == RangeIndexId_) {
+ rangeIndex = it->Data.Int64;
+ } else if (it->Id == TabletIndexId_) {
+ tabletIndex = it->Data.Int64;
+ }
+ }
+
+ bool needRowIndex = false;
+ if (tableIndex && *tableIndex != TableIndex_) {
+ if (ControlAttributesConfig_->EnableTableIndex) {
+ WriteTableIndex(*tableIndex);
+ }
+ TableIndex_ = *tableIndex;
+ needRowIndex = true;
+ }
+
+ if (rangeIndex && *rangeIndex != RangeIndex_) {
+ if (ControlAttributesConfig_->EnableRangeIndex) {
+ WriteRangeIndex(*rangeIndex);
+ }
+ RangeIndex_ = *rangeIndex;
+ needRowIndex = true;
+ }
+
+ if (tabletIndex && *tabletIndex != TabletIndex_) {
+ if (ControlAttributesConfig_->EnableTabletIndex) {
+ WriteTabletIndex(*tabletIndex);
+ }
+ TabletIndex_ = *tabletIndex;
+ needRowIndex = true;
+ }
+
+ if (rowIndex) {
+ needRowIndex = needRowIndex || (*rowIndex != RowIndex_);
+ RowIndex_ = *rowIndex;
+ if (ControlAttributesConfig_->EnableRowIndex && needRowIndex) {
+ WriteRowIndex(*rowIndex);
+ }
+ }
+}
+
+void TSchemalessFormatWriterBase::WriteTableIndex(i64 /* tableIndex */)
+{ }
+
+void TSchemalessFormatWriterBase::WriteRangeIndex(i64 /* rangeIndex */)
+{ }
+
+void TSchemalessFormatWriterBase::WriteRowIndex(i64 /* rowIndex */)
+{ }
+
+void TSchemalessFormatWriterBase::WriteTabletIndex(i64 /* tabletIndex */)
+{ }
+
+void TSchemalessFormatWriterBase::WriteEndOfStream()
+{ }
+
+bool TSchemalessFormatWriterBase::HasError() const
+{
+ return !Error_.IsOK();
+}
+
+const TError& TSchemalessFormatWriterBase::GetError() const
+{
+ return Error_;
+}
+
+void TSchemalessFormatWriterBase::RegisterError(const TError& error)
+{
+ Error_ = error;
+}
+
+TFuture<void> TSchemalessFormatWriterBase::Flush()
+{
+ TryFlushBuffer(/*force*/ true);
+ return MakeFuture(Error_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSchemalessWriterAdapter::TSchemalessWriterAdapter(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+ : TSchemalessFormatWriterBase(
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount)
+{ }
+
+// CreateConsumerForFormat may throw an exception if there is no consumer for the given format,
+// so we set Consumer_ inside Init function rather than inside the constructor.
+void TSchemalessWriterAdapter::Init(const std::vector<NTableClient::TTableSchemaPtr>& tableSchemas, const TFormat& format)
+{
+ // This is generic code for those formats, that support skipping nulls.
+ // See #TYsonFormatConfig and #TJsonFormatConfig.
+ SkipNullValues_ = format.Attributes().Get("skip_null_values", false);
+ TYsonConverterConfig config{
+ .ComplexTypeMode = format.Attributes().Get("complex_type_mode", EComplexTypeMode::Named),
+ .StringKeyedDictMode = format.Attributes().Get("string_keyed_dict_mode", EDictMode::Positional),
+ .DecimalMode = format.Attributes().Get("decimal_mode", EDecimalMode::Binary),
+ .TimeMode = format.Attributes().Get("time_mode", ETimeMode::Binary),
+ .UuidMode = format.Attributes().Get("uuid_mode", EUuidMode::Binary),
+ .SkipNullValues = SkipNullValues_,
+ };
+
+ Consumer_ = CreateConsumerForFormat(format, EDataType::Tabular, GetOutputStream());
+
+ ValueWriters_.reserve(tableSchemas.size());
+ for (const auto& schema : tableSchemas) {
+ ValueWriters_.emplace_back(NameTable_, schema, config);
+ }
+}
+
+void TSchemalessWriterAdapter::DoWrite(TRange<TUnversionedRow> rows)
+{
+ int count = static_cast<int>(rows.Size());
+ for (int index = 0; index < count; ++index) {
+ if (CheckKeySwitch(rows[index], index + 1 == count /* isLastRow */)) {
+ WriteControlAttribute(EControlAttribute::KeySwitch, true);
+ }
+
+ ConsumeRow(rows[index]);
+ FlushWriter();
+ TryFlushBuffer(false);
+ }
+
+ TryFlushBuffer(true);
+}
+
+void TSchemalessWriterAdapter::FlushWriter()
+{
+ Consumer_->Flush();
+}
+
+template <class T>
+void TSchemalessWriterAdapter::WriteControlAttribute(
+ EControlAttribute controlAttribute,
+ T value)
+{
+ BuildYsonListFragmentFluently(Consumer_.get())
+ .Item()
+ .BeginAttributes()
+ .Item(FormatEnum(controlAttribute)).Value(value)
+ .EndAttributes()
+ .Entity();
+}
+
+void TSchemalessWriterAdapter::WriteTableIndex(i64 tableIndex)
+{
+ CurrentTableIndex_ = tableIndex;
+ WriteControlAttribute(EControlAttribute::TableIndex, tableIndex);
+}
+
+void TSchemalessWriterAdapter::WriteRowIndex(i64 rowIndex)
+{
+ WriteControlAttribute(EControlAttribute::RowIndex, rowIndex);
+}
+
+void TSchemalessWriterAdapter::WriteRangeIndex(i64 rangeIndex)
+{
+ WriteControlAttribute(EControlAttribute::RangeIndex, rangeIndex);
+}
+
+void TSchemalessWriterAdapter::WriteTabletIndex(i64 tabletIndex)
+{
+ WriteControlAttribute(EControlAttribute::TabletIndex, tabletIndex);
+}
+
+void TSchemalessWriterAdapter::WriteEndOfStream()
+{
+ WriteControlAttribute(EControlAttribute::EndOfStream, true);
+}
+
+void TSchemalessWriterAdapter::ConsumeRow(TUnversionedRow row)
+{
+ WriteControlAttributes(row);
+
+ Consumer_->OnListItem();
+ Consumer_->OnBeginMap();
+ for (auto* it = row.Begin(); it != row.End(); ++it) {
+ auto& value = *it;
+
+ if (IsSystemColumnId(value.Id)) {
+ continue;
+ }
+
+ if (value.Type == EValueType::Null && SkipNullValues_) {
+ continue;
+ }
+
+ Consumer_->OnKeyedItem(NameTableReader_->GetName(value.Id));
+ ValueWriters_[CurrentTableIndex_].WriteValue(value, Consumer_.get());
+ }
+ Consumer_->OnEndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/schemaless_writer_adapter.h b/yt/yt/client/formats/schemaless_writer_adapter.h
new file mode 100644
index 0000000000..52c85c7ffa
--- /dev/null
+++ b/yt/yt/client/formats/schemaless_writer_adapter.h
@@ -0,0 +1,159 @@
+#pragma once
+
+#include "public.h"
+#include "format.h"
+#include "helpers.h"
+#include "unversioned_value_yson_writer.h"
+
+#include <yt/yt/client/table_client/unversioned_writer.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <memory>
+#include <limits>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessFormatWriterBase
+ : public ISchemalessFormatWriter
+{
+public:
+ bool Write(TRange<NTableClient::TUnversionedRow> rows) override;
+ bool WriteBatch(NTableClient::IUnversionedRowBatchPtr rowBatch) override;
+
+ TFuture<void> GetReadyEvent() override;
+
+ TFuture<void> Close() override;
+
+ TBlob GetContext() const override;
+
+ i64 GetWrittenSize() const override;
+
+ TFuture<void> Flush() override;
+
+protected:
+ const NTableClient::TNameTablePtr NameTable_;
+ const NConcurrency::IAsyncOutputStreamPtr Output_;
+ const bool EnableContextSaving_;
+ const TControlAttributesConfigPtr ControlAttributesConfig_;
+ const int KeyColumnCount_;
+
+ const std::unique_ptr<NTableClient::TNameTableReader> NameTableReader_;
+
+ NTableClient::TLegacyOwningKey LastKey_;
+ NTableClient::TLegacyKey CurrentKey_;
+
+ TSchemalessFormatWriterBase(
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+ TBlobOutput* GetOutputStream();
+
+ void TryFlushBuffer(bool force);
+ virtual void FlushWriter();
+
+ virtual void DoWrite(TRange<NTableClient::TUnversionedRow> rows) = 0;
+ virtual void DoWriteBatch(NTableClient::IUnversionedRowBatchPtr rowBatch);
+
+ bool CheckKeySwitch(NTableClient::TUnversionedRow row, bool isLastRow);
+
+ bool IsSystemColumnId(int id) const;
+ bool IsTableIndexColumnId(int id) const;
+ bool IsRangeIndexColumnId(int id) const;
+ bool IsRowIndexColumnId(int id) const;
+ bool IsTabletIndexColumnId(int id) const;
+
+ int GetRangeIndexColumnId() const;
+ int GetRowIndexColumnId() const;
+ int GetTableIndexColumnId() const;
+ int GetTabletIndexColumnId() const;
+
+ // This is suitable only for switch-based control attributes,
+ // e.g. in such formats as YAMR or YSON.
+ void WriteControlAttributes(NTableClient::TUnversionedRow row);
+ virtual void WriteTableIndex(i64 tableIndex);
+ virtual void WriteRangeIndex(i64 rangeIndex);
+ virtual void WriteRowIndex(i64 rowIndex);
+ virtual void WriteTabletIndex(i64 tabletIndex);
+ virtual void WriteEndOfStream();
+
+ bool HasError() const;
+ const TError& GetError() const;
+ void RegisterError(const TError& error);
+
+private:
+ TBlobOutput CurrentBuffer_;
+ TSharedRef PreviousBuffer_;
+
+ int RowIndexId_ = -1;
+ int RangeIndexId_ = -1;
+ int TableIndexId_ = -1;
+ int TabletIndexId_ = -1;
+
+ i64 RangeIndex_ = std::numeric_limits<i64>::min();
+ i64 TableIndex_ = std::numeric_limits<i64>::min();
+ i64 RowIndex_ = std::numeric_limits<i64>::min();
+ i64 TabletIndex_ = std::numeric_limits<i64>::min();
+
+ bool EnableRowControlAttributes_;
+
+ TError Error_;
+
+ i64 WrittenSize_ = 0;
+
+ bool Closed_ = false;
+
+ void DoFlushBuffer();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessWriterAdapter
+ : public TSchemalessFormatWriterBase
+{
+public:
+ TSchemalessWriterAdapter(
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+ void Init(const std::vector<NTableClient::TTableSchemaPtr>& tableSchemas, const TFormat& format);
+
+private:
+ template <class T>
+ void WriteControlAttribute(
+ NTableClient::EControlAttribute controlAttribute,
+ T value);
+
+ void ConsumeRow(NTableClient::TUnversionedRow row);
+
+ void DoWrite(TRange<NTableClient::TUnversionedRow> rows) override;
+ void FlushWriter() override;
+
+ void WriteTableIndex(i64 tableIndex) override;
+ void WriteRangeIndex(i64 rangeIndex) override;
+ void WriteRowIndex(i64 rowIndex) override;
+ void WriteTabletIndex(i64 tabletIndex) override;
+ void WriteEndOfStream() override;
+
+private:
+ std::vector<TUnversionedValueYsonWriter> ValueWriters_;
+ std::unique_ptr<NYson::IFlushableYsonConsumer> Consumer_;
+ i64 CurrentTableIndex_ = 0;
+ bool SkipNullValues_ = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/skiff_parser.cpp b/yt/yt/client/formats/skiff_parser.cpp
new file mode 100644
index 0000000000..8b2d71238b
--- /dev/null
+++ b/yt/yt/client/formats/skiff_parser.cpp
@@ -0,0 +1,631 @@
+#include "skiff_parser.h"
+#include "skiff_yson_converter.h"
+
+#include "helpers.h"
+#include "parser.h"
+#include "yson_map_to_unversioned_value.h"
+
+#include <yt/yt/library/decimal/decimal.h>
+#include <yt/yt/library/skiff_ext/schema_match.h>
+#include <yt/yt/library/skiff_ext/parser.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/table_consumer.h>
+#include <yt/yt/client/table_client/value_consumer.h>
+
+#include <yt/yt/core/yson/parser.h>
+#include <yt/yt/core/yson/token_writer.h>
+
+#include <util/generic/strbuf.h>
+#include <util/stream/zerocopy.h>
+#include <util/stream/buffer.h>
+
+namespace NYT::NFormats {
+
+using namespace NTableClient;
+using namespace NSkiff;
+using namespace NSkiffExt;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TSkiffToUnversionedValueConverter = std::function<void(TCheckedInDebugSkiffParser*, IValueConsumer*)>;
+
+template <bool IsNullable, typename TFunction>
+class TPrimitiveTypeConverter
+{
+public:
+ explicit TPrimitiveTypeConverter(ui32 columnId, TFunction function = {})
+ : ColumnId_(columnId)
+ , Function_(function)
+ { }
+
+ void operator() (TCheckedInDebugSkiffParser* parser, IValueConsumer* valueConsumer)
+ {
+ if constexpr (IsNullable) {
+ ui8 tag = parser->ParseVariant8Tag();
+ if (tag == 0) {
+ valueConsumer->OnValue(MakeUnversionedNullValue(ColumnId_));
+ return;
+ } else if (tag > 1) {
+ const auto name = valueConsumer->GetNameTable()->GetName(ColumnId_);
+ THROW_ERROR_EXCEPTION(
+ "Found bad variant8 tag %Qv when parsing optional field %Qv",
+ tag,
+ name);
+ }
+ }
+
+ auto value = Function_(parser);
+ using TValueType = std::decay_t<decltype(value)>;
+
+ if constexpr (std::is_same_v<TValueType, TStringBuf>) {
+ valueConsumer->OnValue(MakeUnversionedStringValue(value, ColumnId_));
+ } else if constexpr (
+ std::is_same_v<TValueType, i8> ||
+ std::is_same_v<TValueType, i16> ||
+ std::is_same_v<TValueType, i32> ||
+ std::is_same_v<TValueType, i64>)
+ {
+ valueConsumer->OnValue(MakeUnversionedInt64Value(value, ColumnId_));
+ } else if constexpr (
+ std::is_same_v<TValueType, ui8> ||
+ std::is_same_v<TValueType, ui16> ||
+ std::is_same_v<TValueType, ui32> ||
+ std::is_same_v<TValueType, ui64>)
+ {
+ valueConsumer->OnValue(MakeUnversionedUint64Value(value, ColumnId_));
+ } else if constexpr (std::is_same_v<TValueType, bool>) {
+ valueConsumer->OnValue(MakeUnversionedBooleanValue(value, ColumnId_));
+ } else if constexpr (std::is_same_v<TValueType, double>) {
+ valueConsumer->OnValue(MakeUnversionedDoubleValue(value, ColumnId_));
+ } else if constexpr (std::is_same_v<TValueType, std::nullptr_t>) {
+ valueConsumer->OnValue(MakeUnversionedNullValue(ColumnId_));
+ } else {
+ static_assert(std::is_same_v<TValueType, TStringBuf>);
+ }
+ }
+
+private:
+ ui32 ColumnId_;
+ TFunction Function_;
+};
+
+template <bool IsNullable, typename TFunction>
+TPrimitiveTypeConverter<IsNullable, TFunction> CreatePrimitiveTypeConverter(ui32 columnId, TFunction function)
+{
+ return TPrimitiveTypeConverter<IsNullable, TFunction>(columnId, function);
+}
+
+template<bool isNullable>
+class TYson32TypeConverterImpl
+{
+public:
+ explicit TYson32TypeConverterImpl(ui16 columnId, TYsonToUnversionedValueConverter* ysonConverter)
+ : ColumnId_(columnId)
+ , YsonConverter_(ysonConverter)
+ {}
+
+ void operator()(TCheckedInDebugSkiffParser* parser, IValueConsumer* valueConsumer)
+ {
+ if constexpr (isNullable) {
+ ui8 tag = parser->ParseVariant8Tag();
+ if (tag == 0) {
+ valueConsumer->OnValue(MakeUnversionedNullValue(ColumnId_));
+ return;
+ } else if (tag > 1) {
+ const auto name = valueConsumer->GetNameTable()->GetName(ColumnId_);
+ THROW_ERROR_EXCEPTION(
+ "Found bad variant8 tag %Qv when parsing optional field %Qv",
+ tag,
+ name);
+ }
+ }
+ YT_VERIFY(YsonConverter_);
+ auto ysonString = parser->ParseYson32();
+ YsonConverter_->SetColumnIndex(ColumnId_);
+ {
+ auto consumer = YsonConverter_->SwitchToTable(0);
+ YT_VERIFY(consumer == valueConsumer);
+ }
+ ParseYsonStringBuffer(ysonString, NYson::EYsonType::Node, YsonConverter_);
+ }
+
+private:
+ const ui16 ColumnId_;
+ TYsonToUnversionedValueConverter* YsonConverter_ = nullptr;
+};
+
+TSkiffToUnversionedValueConverter CreatePrimitiveTypeConverter(
+ EWireType wireType,
+ bool required,
+ int columnId,
+ TYsonToUnversionedValueConverter* ysonConverter)
+{
+ switch (wireType) {
+#define CASE(x) \
+ case ((x)): \
+ do { \
+ if (required) { \
+ return TPrimitiveTypeConverter<false, TSimpleSkiffParser<(x)>>(columnId); \
+ } else { \
+ return TPrimitiveTypeConverter<true, TSimpleSkiffParser<(x)>>(columnId); \
+ } \
+ } while (0)
+ CASE(EWireType::Int8);
+ CASE(EWireType::Int16);
+ CASE(EWireType::Int32);
+ CASE(EWireType::Int64);
+ CASE(EWireType::Uint8);
+ CASE(EWireType::Uint16);
+ CASE(EWireType::Uint32);
+ CASE(EWireType::Uint64);
+ CASE(EWireType::Boolean);
+ CASE(EWireType::Double);
+ CASE(EWireType::String32);
+ CASE(EWireType::Nothing);
+#undef CASE
+ case EWireType::Yson32:
+ if (required) {
+ return TYson32TypeConverterImpl<false>(columnId, ysonConverter);
+ } else {
+ return TYson32TypeConverterImpl<true>(columnId, ysonConverter);
+ }
+ default:
+ break;
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSkiffToUnversionedValueConverter CreateSimpleValueConverter(
+ ESimpleLogicalValueType columnType,
+ const TFieldDescription& fieldDescription,
+ ui16 columnId,
+ TYsonToUnversionedValueConverter* ysonConverter)
+{
+ EWireType wireType = fieldDescription.ValidatedSimplify();
+ bool required = fieldDescription.IsRequired();
+ switch (columnType) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+
+ case ESimpleLogicalValueType::Interval:
+ CheckWireType(
+ wireType,
+ {EWireType::Int8, EWireType::Int16, EWireType::Int32, EWireType::Int64, EWireType::Yson32});
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ CheckWireType(
+ wireType,
+ {EWireType::Uint8, EWireType::Uint16, EWireType::Uint32, EWireType::Uint64, EWireType::Yson32});
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Json:
+ case ESimpleLogicalValueType::Utf8:
+ CheckWireType(wireType, {EWireType::String32, EWireType::Yson32});
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+
+ case ESimpleLogicalValueType::Float:
+ case ESimpleLogicalValueType::Double:
+ CheckWireType(wireType, {EWireType::Double, EWireType::Yson32});
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+
+ case ESimpleLogicalValueType::Boolean:
+ CheckWireType(wireType, {EWireType::Boolean, EWireType::Yson32});
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+
+ case ESimpleLogicalValueType::Any:
+ CheckWireType(
+ wireType,
+ {
+ EWireType::Int8,
+ EWireType::Int16,
+ EWireType::Int32,
+ EWireType::Int64,
+
+ EWireType::Uint8,
+ EWireType::Uint16,
+ EWireType::Uint32,
+ EWireType::Uint64,
+
+ EWireType::Double,
+ EWireType::Boolean,
+ EWireType::String32,
+ EWireType::Nothing,
+ EWireType::Yson32
+ });
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ CheckWireType(wireType, {EWireType::Nothing, EWireType::Yson32});
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+ case ESimpleLogicalValueType::Uuid:
+ CheckWireType(wireType, {EWireType::Uint128, EWireType::String32, EWireType::Yson32});
+ if (wireType == EWireType::Uint128) {
+ if (fieldDescription.IsNullable()) {
+ return CreatePrimitiveTypeConverter<true>(columnId, TUuidParser());
+ } else {
+ return CreatePrimitiveTypeConverter<false>(columnId, TUuidParser());
+ }
+ } else {
+ return CreatePrimitiveTypeConverter(wireType, required, columnId, ysonConverter);
+ }
+ }
+}
+
+TSkiffToUnversionedValueConverter CreateDecimalValueConverter(
+ const TFieldDescription& fieldDescription,
+ ui16 columnId,
+ const TDecimalLogicalType& denullifiedType,
+ TYsonToUnversionedValueConverter* ysonConverter)
+{
+const auto precision = denullifiedType.GetPrecision();
+ const auto wireType = fieldDescription.ValidatedSimplify();
+ switch (wireType) {
+#define CASE(x) \
+ case x: \
+ do { \
+ if (fieldDescription.IsNullable()) { \
+ return CreatePrimitiveTypeConverter<true>(columnId, TDecimalSkiffParser<x>(precision)); \
+ } else { \
+ return CreatePrimitiveTypeConverter<false>(columnId, TDecimalSkiffParser<x>(precision)); \
+ } \
+ } while (0)
+ CASE(EWireType::Int32);
+ CASE(EWireType::Int64);
+ CASE(EWireType::Int128);
+#undef CASE
+ case EWireType::Yson32:
+ return CreatePrimitiveTypeConverter(wireType, fieldDescription.IsRequired(), columnId, ysonConverter);
+ default:
+ CheckSkiffWireTypeForDecimal(precision, wireType);
+ YT_ABORT();
+ }
+}
+
+class TComplexValueConverter
+{
+public:
+ TComplexValueConverter(TSkiffToYsonConverter converter, ui16 columnId)
+ : Converter_(std::move(converter))
+ , ColumnId_(columnId)
+ { }
+
+ void operator() (TCheckedInDebugSkiffParser* parser, IValueConsumer* valueConsumer)
+ {
+ Buffer_.Clear();
+ {
+ TBufferOutput out(Buffer_);
+ NYson::TCheckedInDebugYsonTokenWriter ysonTokenWriter(&out);
+ Converter_(parser, &ysonTokenWriter);
+ ysonTokenWriter.Finish();
+ }
+ auto value = TStringBuf(Buffer_.Data(), Buffer_.Size());
+ constexpr TStringBuf entity = "#";
+ if (value == entity) {
+ valueConsumer->OnValue(MakeUnversionedNullValue(ColumnId_));
+ } else {
+ valueConsumer->OnValue(MakeUnversionedCompositeValue(value, ColumnId_));
+ }
+ }
+
+private:
+ const TSkiffToYsonConverter Converter_;
+ const ui16 ColumnId_;
+ TBuffer Buffer_;
+};
+
+TSkiffToUnversionedValueConverter CreateComplexValueConverter(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ ui16 columnId,
+ bool sparseColumn)
+{
+ TSkiffToYsonConverterConfig config;
+ config.AllowOmitTopLevelOptional = sparseColumn;
+ auto converter = CreateSkiffToYsonConverter(descriptor, skiffSchema, config);
+ return TComplexValueConverter(converter, columnId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSkiffParserImpl
+{
+
+public:
+ TSkiffParserImpl(
+ std::shared_ptr<TSkiffSchema> skiffSchema,
+ const TTableSchemaPtr& tableSchema,
+ IValueConsumer* valueConsumer)
+ : SkiffSchemaList_({std::move(skiffSchema)})
+ , ValueConsumer_(valueConsumer)
+ , YsonToUnversionedValueConverter_(TYsonConverterConfig(), ValueConsumer_)
+ , OtherColumnsConsumer_(TYsonConverterConfig(), ValueConsumer_)
+ {
+ THashMap<TString, const TColumnSchema*> columnSchemas;
+ for (const auto& column : tableSchema->Columns()) {
+ columnSchemas[column.Name()] = &column;
+ }
+
+ auto genericTableDescriptions = CreateTableDescriptionList(
+ SkiffSchemaList_, RangeIndexColumnName, RowIndexColumnName);
+
+ for (int tableIndex = 0; tableIndex < std::ssize(genericTableDescriptions); ++tableIndex) {
+ const auto& genericTableDescription = genericTableDescriptions[tableIndex];
+ auto& parserTableDescription = TableDescriptions_.emplace_back();
+ parserTableDescription.HasOtherColumns = genericTableDescription.HasOtherColumns;
+ for (const auto& fieldDescription : genericTableDescription.DenseFieldDescriptionList) {
+ const auto columnId = ValueConsumer_->GetNameTable()->GetIdOrRegisterName(fieldDescription.Name());
+ auto columnSchema = columnSchemas.FindPtr(fieldDescription.Name());
+ TSkiffToUnversionedValueConverter converter;
+ try {
+ converter = CreateSkiffToUnversionedValueConverter(
+ columnId,
+ columnSchema == nullptr ? nullptr : *columnSchema,
+ fieldDescription,
+ /*sparseColumn*/ false);
+ } catch (const std::exception& ex) {
+ auto logicalType = OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any));
+ if (columnSchema && *columnSchema) {
+ logicalType = (*columnSchema)->LogicalType();
+ }
+ THROW_ERROR_EXCEPTION("Cannot create Skiff parser for table #%v",
+ tableIndex)
+ << TErrorAttribute("logical_type", logicalType)
+ << ex;
+ }
+ parserTableDescription.DenseFieldConverters.emplace_back(converter);
+ }
+
+ for (const auto& fieldDescription : genericTableDescription.SparseFieldDescriptionList) {
+ const auto columnId = ValueConsumer_->GetNameTable()->GetIdOrRegisterName(fieldDescription.Name());
+ TSkiffToUnversionedValueConverter converter;
+ auto columnSchema = columnSchemas.FindPtr(fieldDescription.Name());
+ try {
+ converter = CreateSkiffToUnversionedValueConverter(
+ columnId,
+ columnSchema == nullptr ? nullptr : *columnSchema,
+ fieldDescription,
+ /*sparseColumn*/ true);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Cannot create Skiff parser for table #%v",
+ tableIndex)
+ << ex;
+ }
+ parserTableDescription.SparseFieldConverters.emplace_back(converter);
+ }
+ }
+ }
+
+ void DoParse(IZeroCopyInput* stream)
+ {
+ Parser_ = std::make_unique<TCheckedInDebugSkiffParser>(CreateVariant16Schema(SkiffSchemaList_), stream);
+
+ while (Parser_->HasMoreData()) {
+ auto tag = Parser_->ParseVariant16Tag();
+ if (tag > 0) {
+ THROW_ERROR_EXCEPTION("Unknown table index variant16 tag %v",
+ tag);
+ }
+ ValueConsumer_->OnBeginRow();
+
+ for (const auto& converter : TableDescriptions_[tag].DenseFieldConverters) {
+ converter(Parser_.get(), ValueConsumer_);
+ }
+
+ if (!TableDescriptions_[tag].SparseFieldConverters.empty()) {
+ for (auto sparseFieldIdx = Parser_->ParseVariant16Tag();
+ sparseFieldIdx != EndOfSequenceTag<ui16>();
+ sparseFieldIdx = Parser_->ParseVariant16Tag()) {
+ if (sparseFieldIdx >= TableDescriptions_[tag].SparseFieldConverters.size()) {
+ THROW_ERROR_EXCEPTION("Bad sparse field index %Qv, total sparse field count %Qv",
+ sparseFieldIdx,
+ TableDescriptions_[tag].SparseFieldConverters.size());
+ }
+
+ const auto& converter = TableDescriptions_[tag].SparseFieldConverters[sparseFieldIdx];
+ converter(Parser_.get(), ValueConsumer_);
+ }
+ }
+
+ if (TableDescriptions_[tag].HasOtherColumns) {
+ auto buf = Parser_->ParseYson32();
+ ParseYsonStringBuffer(
+ buf,
+ NYson::EYsonType::Node,
+ &OtherColumnsConsumer_);
+ }
+
+ ValueConsumer_->OnEndRow();
+ }
+ }
+
+ ui64 GetReadBytesCount()
+ {
+ return Parser_->GetReadBytesCount();
+ }
+
+private:
+ TSkiffToUnversionedValueConverter CreateSkiffToUnversionedValueConverter(
+ int columnId,
+ const TColumnSchema* columnSchema,
+ const TFieldDescription& skiffField,
+ bool sparseColumn)
+ {
+ const auto columnType = columnSchema ?
+ columnSchema->LogicalType() :
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any));
+
+ const auto denullifiedLogicalType = DenullifyLogicalType(columnType);
+
+ try {
+ switch (denullifiedLogicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return CreateSimpleValueConverter(
+ denullifiedLogicalType->AsSimpleTypeRef().GetElement(),
+ skiffField,
+ columnId,
+ &YsonToUnversionedValueConverter_);
+ case ELogicalMetatype::Decimal:
+ return CreateDecimalValueConverter(
+ skiffField,
+ columnId,
+ denullifiedLogicalType->AsDecimalTypeRef(),
+ &YsonToUnversionedValueConverter_);
+ case ELogicalMetatype::Optional:
+ case ELogicalMetatype::List:
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantTuple:
+ case ELogicalMetatype::VariantStruct:
+ case ELogicalMetatype::Dict:
+ return CreateComplexValueConverter(
+ TComplexTypeFieldDescriptor(skiffField.Name(), columnType),
+ skiffField.Schema(),
+ columnId,
+ /*sparseColumn*/ sparseColumn);
+ case ELogicalMetatype::Tagged:
+ // denullified type should not contain tagged type
+ break;
+ }
+ YT_ABORT();
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Cannot create Skiff parser for column %Qv",
+ skiffField.Name())
+ << ex;
+ }
+ }
+
+private:
+ struct TTableDescription
+ {
+ std::vector<TSkiffToUnversionedValueConverter> DenseFieldConverters;
+ std::vector<TSkiffToUnversionedValueConverter> SparseFieldConverters;
+ bool HasOtherColumns = false;
+ };
+
+ const TSkiffSchemaList SkiffSchemaList_;
+
+ IValueConsumer* const ValueConsumer_;
+
+ TYsonToUnversionedValueConverter YsonToUnversionedValueConverter_;
+ TYsonMapToUnversionedValueConverter OtherColumnsConsumer_;
+
+ std::unique_ptr<TCheckedInDebugSkiffParser> Parser_;
+ std::vector<TTableDescription> TableDescriptions_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSkiffPushParser
+ : public IParser
+{
+public:
+ TSkiffPushParser(
+ std::shared_ptr<TSkiffSchema> skiffSchema,
+ const TTableSchemaPtr& tableSchema,
+ IValueConsumer* consumer)
+ : ParserImpl_(std::make_unique<TSkiffParserImpl>(
+ std::move(skiffSchema),
+ tableSchema,
+ consumer))
+ , ParserCoroPipe_(
+ BIND([this] (IZeroCopyInput* stream) {
+ ParserImpl_->DoParse(stream);
+ }))
+ {}
+
+ void Read(TStringBuf data) override
+ {
+ if (!data.empty()) {
+ ParserCoroPipe_.Feed(data);
+ }
+ }
+
+ void Finish() override
+ {
+ ParserCoroPipe_.Finish();
+ }
+
+private:
+ std::unique_ptr<TSkiffParserImpl> ParserImpl_;
+ TCoroPipe ParserCoroPipe_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace // anonymous
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForSkiff(
+ std::shared_ptr<TSkiffSchema> skiffSchema,
+ const TTableSchemaPtr& tableSchema,
+ IValueConsumer* consumer)
+{
+ auto tableDescriptionList = CreateTableDescriptionList({skiffSchema}, RangeIndexColumnName, RowIndexColumnName);
+ if (tableDescriptionList.size() != 1) {
+ THROW_ERROR_EXCEPTION("Expected to have single table, actual table description count %Qv",
+ tableDescriptionList.size());
+ }
+ return std::make_unique<TSkiffPushParser>(
+ std::move(skiffSchema),
+ tableSchema,
+ consumer);
+}
+
+std::unique_ptr<IParser> CreateParserForSkiff(
+ IValueConsumer* consumer,
+ const std::vector<std::shared_ptr<TSkiffSchema>>& skiffSchemas,
+ const TSkiffFormatConfigPtr& config,
+ int tableIndex)
+{
+ if (tableIndex >= static_cast<int>(skiffSchemas.size())) {
+ THROW_ERROR_EXCEPTION("Skiff format config does not describe table #%v",
+ tableIndex);
+ }
+ if (tableIndex == 0 && config->OverrideIntermediateTableSchema) {
+ if (!IsTrivialIntermediateSchema(*consumer->GetSchema())) {
+ THROW_ERROR_EXCEPTION("Cannot use \"override_intermediate_table_schema\" since output table #0 has nontrivial schema")
+ << TErrorAttribute("schema", *consumer->GetSchema());
+ }
+ return CreateParserForSkiff(
+ skiffSchemas[tableIndex],
+ New<TTableSchema>(*config->OverrideIntermediateTableSchema),
+ consumer);
+ } else {
+ return CreateParserForSkiff(
+ skiffSchemas[tableIndex],
+ consumer);
+ }
+}
+
+std::unique_ptr<IParser> CreateParserForSkiff(
+ std::shared_ptr<TSkiffSchema> skiffSchema,
+ IValueConsumer* consumer)
+{
+ return CreateParserForSkiff(std::move(skiffSchema), consumer->GetSchema(), consumer);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/skiff_parser.h b/yt/yt/client/formats/skiff_parser.h
new file mode 100644
index 0000000000..35cea6666e
--- /dev/null
+++ b/yt/yt/client/formats/skiff_parser.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+#include <library/cpp/skiff/skiff.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForSkiff(
+ std::shared_ptr<NSkiff::TSkiffSchema> skiffSchema,
+ NTableClient::IValueConsumer* consumer);
+
+std::unique_ptr<IParser> CreateParserForSkiff(
+ std::shared_ptr<NSkiff::TSkiffSchema> skiffSchema,
+ const NTableClient::TTableSchemaPtr& tableSchema,
+ NTableClient::IValueConsumer* consumer);
+
+std::unique_ptr<IParser> CreateParserForSkiff(
+ NTableClient::IValueConsumer* consumer,
+ const std::vector<std::shared_ptr<NSkiff::TSkiffSchema>>& skiffSchemas,
+ const TSkiffFormatConfigPtr& config,
+ int tableIndex);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/skiff_writer.cpp b/yt/yt/client/formats/skiff_writer.cpp
new file mode 100644
index 0000000000..1eaf09ce8d
--- /dev/null
+++ b/yt/yt/client/formats/skiff_writer.cpp
@@ -0,0 +1,1112 @@
+#include "skiff_writer.h"
+
+#include "public.h"
+#include "schemaless_writer_adapter.h"
+#include "skiff_yson_converter.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <yt/yt/core/misc/finally.h>
+
+#include <yt/yt/library/skiff_ext/schema_match.h>
+
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/core/yson/writer.h>
+
+#include <library/cpp/skiff/skiff.h>
+#include <library/cpp/skiff/skiff_schema.h>
+
+#include <util/generic/buffer.h>
+
+#include <util/stream/buffer.h>
+
+#include <functional>
+#include <utility>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NSkiff;
+using namespace NSkiffExt;
+using namespace NTableClient;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr int MissingSystemColumn = -1;
+static constexpr int MissingRowRangeIndexTag = 2;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ESkiffWriterColumnType,
+ (Unknown)
+ (Dense)
+ (Sparse)
+ (Skip)
+ (RangeIndex)
+ (RowIndex)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+void ResizeToContainIndex(std::vector<T>* vec, size_t index)
+{
+ if (vec->size() < index + 1) {
+ vec->resize(index + 1);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TIndexedSchemas
+{
+public:
+ explicit TIndexedSchemas(const std::vector<TTableSchemaPtr>& tableSchemas)
+ {
+ for (size_t tableIndex = 0; tableIndex < tableSchemas.size(); ++tableIndex) {
+ const auto& columns = tableSchemas[tableIndex]->Columns();
+ for (const auto& column : columns) {
+ Columns_[std::pair<int,TString>(tableIndex, column.Name())] = column;
+ }
+ }
+ }
+
+ const TColumnSchema* GetColumnSchema(int tableIndex, TStringBuf columnName) const
+ {
+ auto it = Columns_.find(std::pair<int,TString>(tableIndex, columnName));
+ if (it == Columns_.end()) {
+ return nullptr;
+ } else {
+ return &it->second;
+ }
+ }
+
+private:
+ // (TableIndex, ColumnName) -> ColumnSchema
+ THashMap<std::pair<int, TString>, TColumnSchema> Columns_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWriteContext
+{
+ TNameTablePtr NameTable;
+ TUnversionedValueYsonWriter* UnversionedValueYsonConverter = nullptr;
+ TBuffer* TmpBuffer = nullptr;
+};
+
+using TUnversionedValueToSkiffConverter = std::function<void(const TUnversionedValue&, TCheckedInDebugSkiffWriter*, TWriteContext*)>;
+
+template <EWireType wireType>
+constexpr EValueType WireTypeToValueType()
+{
+ if constexpr (wireType == EWireType::Int8 ||
+ wireType == EWireType::Int16 ||
+ wireType == EWireType::Int32 ||
+ wireType == EWireType::Int64)
+ {
+ return EValueType::Int64;
+ } else if constexpr (
+ wireType == EWireType::Uint8 ||
+ wireType == EWireType::Uint16 ||
+ wireType == EWireType::Uint32 ||
+ wireType == EWireType::Uint64)
+ {
+ return EValueType::Uint64;
+ } else if constexpr (wireType == EWireType::Double) {
+ return EValueType::Double;
+ } else if constexpr (wireType == EWireType::Boolean) {
+ return EValueType::Boolean;
+ } else if constexpr (wireType == EWireType::String32) {
+ return EValueType::String;
+ } else if constexpr (wireType == EWireType::Nothing) {
+ return EValueType::Null;
+ } else {
+ // Not compilable.
+ static_assert(wireType == EWireType::Int64, "Bad wireType");
+ }
+}
+
+template<EWireType wireType, bool isOptional>
+void ConvertSimpleValueImpl(const TUnversionedValue& value, TCheckedInDebugSkiffWriter* writer, TWriteContext* context)
+{
+ if constexpr (isOptional) {
+ if (value.Type == EValueType::Null) {
+ writer->WriteVariant8Tag(0);
+ return;
+ } else {
+ writer->WriteVariant8Tag(1);
+ }
+ }
+
+ if constexpr (wireType != EWireType::Yson32) {
+ constexpr auto expectedValueType = WireTypeToValueType<wireType>();
+ if (value.Type != expectedValueType) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow,
+ "Unexpected type of %Qv column: Skiff format expected %Qlv, actual table type %Qlv",
+ context->NameTable->GetName(value.Id),
+ expectedValueType,
+ value.Type);
+ }
+ }
+
+ if constexpr (wireType == EWireType::Int8) {
+ CheckIntSize<wireType>(value.Data.Int64);
+ writer->WriteInt8(value.Data.Int64);
+ } else if constexpr (wireType == EWireType::Int16) {
+ CheckIntSize<wireType>(value.Data.Int64);
+ writer->WriteInt16(value.Data.Int64);
+ } else if constexpr (wireType == EWireType::Int32) {
+ CheckIntSize<wireType>(value.Data.Int64);
+ writer->WriteInt32(value.Data.Int64);
+ } else if constexpr (wireType == EWireType::Int64) {
+ writer->WriteInt64(value.Data.Int64);
+
+ } else if constexpr (wireType == EWireType::Uint8) {
+ CheckIntSize<wireType>(value.Data.Uint64);
+ writer->WriteUint8(value.Data.Uint64);
+ } else if constexpr (wireType == EWireType::Uint16) {
+ CheckIntSize<wireType>(value.Data.Uint64);
+ writer->WriteUint16(value.Data.Uint64);
+ } else if constexpr (wireType == EWireType::Uint32) {
+ CheckIntSize<wireType>(value.Data.Uint64);
+ writer->WriteUint32(value.Data.Uint64);
+ } else if constexpr (wireType == EWireType::Uint64) {
+ writer->WriteUint64(value.Data.Uint64);
+
+ } else if constexpr (wireType == EWireType::Boolean) {
+ writer->WriteBoolean(value.Data.Boolean);
+ } else if constexpr (wireType == EWireType::Double) {
+ writer->WriteDouble(value.Data.Double);
+ } else if constexpr (wireType == EWireType::String32) {
+ writer->WriteString32(value.AsStringBuf());
+ } else if constexpr (wireType == EWireType::Yson32) {
+ context->TmpBuffer->Clear();
+ {
+ TBufferOutput out(*context->TmpBuffer);
+ NYson::TYsonWriter ysonWriter(&out);
+ context->UnversionedValueYsonConverter->WriteValue(value, &ysonWriter);
+ }
+ writer->WriteYson32(TStringBuf(context->TmpBuffer->data(), context->TmpBuffer->size()));
+ } else if constexpr (wireType == EWireType::Nothing) {
+ // Do nothing.
+ } else {
+ // Not compilable.
+ static_assert(wireType == EWireType::Int64, "Bad wireType");
+ }
+}
+
+template <EValueType ExpectedValueType, bool isOptional, typename TFunc>
+class TPrimitiveConverterWrapper
+{
+public:
+ explicit TPrimitiveConverterWrapper(TFunc function)
+ : Function_(function)
+ { }
+
+ void operator()(const TUnversionedValue& value, TCheckedInDebugSkiffWriter* writer, TWriteContext* context)
+ {
+ if constexpr (isOptional) {
+ if (value.Type == EValueType::Null) {
+ writer->WriteVariant8Tag(0);
+ return;
+ } else {
+ writer->WriteVariant8Tag(1);
+ }
+ }
+ if (value.Type != ExpectedValueType) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow,
+ "Unexpected type of %Qv column: Skiff format expected %Qlv, actual table type %Qlv",
+ context->NameTable->GetName(value.Id),
+ ExpectedValueType,
+ value.Type);
+ }
+ if constexpr (ExpectedValueType == EValueType::String) {
+ Function_(value.AsStringBuf(), writer);
+ } else {
+ // TODO(ermolovd) support other types and use this class instead of ConvertSimpleValueImpl
+ // poor man's static assert false
+ static_assert(ExpectedValueType == EValueType::String);
+ }
+ }
+
+private:
+ TFunc Function_;
+};
+
+class TRowAndRangeIndexWriter
+{
+public:
+ template <ERowRangeIndexMode Mode>
+ void WriteRowIndex(const TUnversionedValue& value, TCheckedInDebugSkiffWriter* writer, TWriteContext* /*context*/)
+ {
+ if (value.Type == EValueType::Int64) {
+ const auto currentRowIndex = value.Data.Int64;
+ if (RowIndex_ + 1 == currentRowIndex) {
+ writer->WriteVariant8Tag(0);
+ } else {
+ writer->WriteVariant8Tag(1);
+ writer->WriteInt64(value.Data.Int64);
+ }
+ RowIndex_ = currentRowIndex;
+ } else if (value.Type == EValueType::Null) {
+ if constexpr (Mode == ERowRangeIndexMode::Incremental) {
+ THROW_ERROR_EXCEPTION("Row index requested but reader did not return it");
+ } else {
+ static_assert(Mode == ERowRangeIndexMode::IncrementalWithError);
+ writer->WriteVariant8Tag(MissingRowRangeIndexTag);
+ }
+ RowIndex_ = Undefined;
+ }
+ }
+
+ template <ERowRangeIndexMode Mode>
+ void WriteRangeIndex(const TUnversionedValue& value, TCheckedInDebugSkiffWriter* writer, TWriteContext* /*context*/)
+ {
+ if (value.Type == EValueType::Int64) {
+ const auto currentRangeIndex = value.Data.Int64;
+ if (RangeIndex_ == currentRangeIndex) {
+ writer->WriteVariant8Tag(0);
+ } else {
+ writer->WriteVariant8Tag(1);
+ writer->WriteInt64(currentRangeIndex);
+ }
+ RangeIndex_ = currentRangeIndex;
+ } else if (value.Type == EValueType::Null) {
+ if constexpr (Mode == ERowRangeIndexMode::Incremental) {
+ THROW_ERROR_EXCEPTION("Range index requested but reader did not return it");
+ } else {
+ static_assert(Mode == ERowRangeIndexMode::IncrementalWithError);
+ writer->WriteVariant8Tag(MissingRowRangeIndexTag);
+ }
+ }
+ }
+
+ Y_FORCE_INLINE void PrepareTableIndex(i64 tableIndex)
+ {
+ if (TableIndex_ != tableIndex) {
+ TableIndex_ = tableIndex;
+ RowIndex_ = Undefined;
+ RangeIndex_ = Undefined;
+ }
+ }
+
+ Y_FORCE_INLINE void PrepareRangeIndex(i64 rangeIndex)
+ {
+ if (rangeIndex != RangeIndex_) {
+ RangeIndex_ = Undefined;
+ RowIndex_ = Undefined;
+ }
+ }
+
+ Y_FORCE_INLINE void ResetRangeIndex()
+ {
+ PrepareRangeIndex(Undefined);
+ }
+
+ Y_FORCE_INLINE void ResetRowIndex()
+ {
+ RowIndex_ = Undefined;
+ }
+
+private:
+ static constexpr i64 Undefined = -2;
+
+ i64 TableIndex_ = Undefined;
+ i64 RangeIndex_ = Undefined;
+ i64 RowIndex_ = Undefined;
+};
+
+TUnversionedValueToSkiffConverter CreateMissingCompositeValueConverter(TString name) {
+ return [name=std::move(name)] (const TUnversionedValue& value, TCheckedInDebugSkiffWriter* writer, TWriteContext*) {
+ if (value.Type != EValueType::Null) {
+ THROW_ERROR_EXCEPTION("Cannot represent nonnull value of column %Qv absent in schema as composite Skiff value",
+ name);
+ }
+ writer->WriteVariant8Tag(0);
+ };
+}
+
+template <EValueType ExpectedValueType, typename TFunction>
+TUnversionedValueToSkiffConverter CreatePrimitiveValueConverter(
+ bool required,
+ TFunction function)
+{
+ if (required) {
+ return TPrimitiveConverterWrapper<ExpectedValueType, false, TFunction>(std::move(function));
+ } else {
+ return TPrimitiveConverterWrapper<ExpectedValueType, true, TFunction>(std::move(function));
+ }
+}
+
+TUnversionedValueToSkiffConverter CreatePrimitiveValueConverter(EWireType wireType, bool required)
+{
+ switch (wireType) {
+#define CASE(t) \
+ case t: \
+ return required ? ConvertSimpleValueImpl<t, false> : ConvertSimpleValueImpl<t, true>;
+ CASE(EWireType::Int8)
+ CASE(EWireType::Int16)
+ CASE(EWireType::Int32)
+ CASE(EWireType::Int64)
+ CASE(EWireType::Uint8)
+ CASE(EWireType::Uint16)
+ CASE(EWireType::Uint32)
+ CASE(EWireType::Uint64)
+ CASE(EWireType::Double)
+ CASE(EWireType::Boolean)
+ CASE(EWireType::String32)
+ CASE(EWireType::Yson32)
+#undef CASE
+ case EWireType::Nothing:
+ // TODO(ermolovd): we should use `isOptional` instead of `required` (with corresponding condition inversion).
+ YT_VERIFY(required);
+ return ConvertSimpleValueImpl<EWireType::Nothing, false>;
+
+ default:
+ YT_ABORT();
+ }
+}
+
+TUnversionedValueToSkiffConverter CreateSimpleValueConverter(
+ EWireType wireType,
+ bool required,
+ ESimpleLogicalValueType logicalType)
+{
+ switch (logicalType) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+
+ case ESimpleLogicalValueType::Interval:
+ CheckWireType(wireType, {EWireType::Int8, EWireType::Int16, EWireType::Int32, EWireType::Int64, EWireType::Yson32});
+ return CreatePrimitiveValueConverter(wireType, required);
+
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ CheckWireType(wireType, {EWireType::Uint8, EWireType::Uint16, EWireType::Uint32, EWireType::Uint64, EWireType::Yson32});
+ return CreatePrimitiveValueConverter(wireType, required);
+
+ case ESimpleLogicalValueType::Float:
+ case ESimpleLogicalValueType::Double:
+ CheckWireType(wireType, {EWireType::Double, EWireType::Yson32});
+ return CreatePrimitiveValueConverter(wireType, required);
+
+ case ESimpleLogicalValueType::Boolean:
+ CheckWireType(wireType, {EWireType::Boolean, EWireType::Yson32});
+ return CreatePrimitiveValueConverter(wireType, required);
+
+ case ESimpleLogicalValueType::Utf8:
+ case ESimpleLogicalValueType::Json:
+ case ESimpleLogicalValueType::String:
+ CheckWireType(wireType, {EWireType::String32, EWireType::Yson32});
+ return CreatePrimitiveValueConverter(wireType, required);
+
+ case ESimpleLogicalValueType::Any:
+ CheckWireType(wireType, {
+ EWireType::Int8,
+ EWireType::Int16,
+ EWireType::Int32,
+ EWireType::Int64,
+
+ EWireType::Uint8,
+ EWireType::Uint16,
+ EWireType::Uint32,
+ EWireType::Uint64,
+
+ EWireType::String32,
+ EWireType::Boolean,
+ EWireType::Double,
+ EWireType::Nothing,
+ EWireType::Yson32
+ });
+ return CreatePrimitiveValueConverter(wireType, required);
+
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ CheckWireType(wireType, {EWireType::Nothing, EWireType::Yson32});
+ return CreatePrimitiveValueConverter(wireType, required);
+
+ case ESimpleLogicalValueType::Uuid:
+ CheckWireType(wireType, {EWireType::Uint128, EWireType::String32, EWireType::Yson32});
+ if (wireType == EWireType::Uint128) {
+ return CreatePrimitiveValueConverter<EValueType::String>(required, TUuidWriter());
+ } else {
+ return CreatePrimitiveValueConverter(wireType, required);
+ }
+ }
+}
+
+TUnversionedValueToSkiffConverter CreateComplexValueConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ bool isSparse)
+{
+ TYsonToSkiffConverterConfig config;
+ config.AllowOmitTopLevelOptional = isSparse;
+ auto ysonToSkiff = CreateYsonToSkiffConverter(descriptor, skiffSchema, config);
+ return [ysonToSkiff=ysonToSkiff] (const TUnversionedValue& value, TCheckedInDebugSkiffWriter* skiffWriter, TWriteContext* /*context*/) {
+ TMemoryInput input;
+ if (value.Type == EValueType::Any || value.Type == EValueType::Composite) {
+ // NB. value.Type might be EValueType::Any if user has used override_intermediate_table_schema
+ input.Reset(value.Data.String, value.Length);
+ } else if (value.Type == EValueType::Null) {
+ static const TStringBuf empty = "#";
+ input.Reset(empty.Data(), empty.Size());
+ } else {
+ THROW_ERROR_EXCEPTION("Internal error; unexpected value type: expected %Qlv or %Qlv, actual %Qlv",
+ EValueType::Composite,
+ EValueType::Null,
+ value.Type);
+ }
+ NYson::TYsonPullParser parser(&input, NYson::EYsonType::Node);
+ NYson::TYsonPullParserCursor cursor(&parser);
+ ysonToSkiff(&cursor, skiffWriter);
+ };
+}
+
+TUnversionedValueToSkiffConverter CreateDecimalValueConverter(
+ const TFieldDescription& field,
+ const TDecimalLogicalType& logicalType)
+{
+ bool isRequired = field.IsRequired();
+ int precision = logicalType.GetPrecision();
+ auto wireType = field.ValidatedSimplify();
+ switch (wireType) {
+ case EWireType::Int32:
+ return CreatePrimitiveValueConverter<EValueType::String>(
+ isRequired,
+ TDecimalSkiffWriter<EWireType::Int32>(precision));
+ case EWireType::Int64:
+ return CreatePrimitiveValueConverter<EValueType::String>(
+ isRequired,
+ TDecimalSkiffWriter<EWireType::Int64>(precision));
+ case EWireType::Int128:
+ return CreatePrimitiveValueConverter<EValueType::String>(
+ isRequired,
+ TDecimalSkiffWriter<EWireType::Int128>(precision));
+ case EWireType::Yson32:
+ return CreatePrimitiveValueConverter(wireType, isRequired);
+ default:
+ CheckSkiffWireTypeForDecimal(precision, wireType);
+ YT_ABORT();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSkiffEncodingInfo
+{
+ ESkiffWriterColumnType EncodingPart = ESkiffWriterColumnType::Unknown;
+
+ // Converter is set only for sparse part.
+ TUnversionedValueToSkiffConverter Converter;
+
+ // FieldIndex is index of field inside Skiff tuple for dense part of the row
+ // and variant tag for sparse part of the row.
+ ui32 FieldIndex = 0;
+
+ TSkiffEncodingInfo() = default;
+
+ static TSkiffEncodingInfo Skip()
+ {
+ TSkiffEncodingInfo result;
+ result.EncodingPart = ESkiffWriterColumnType::Skip;
+ return result;
+ }
+
+ static TSkiffEncodingInfo RangeIndex(ui32 fieldIndex)
+ {
+ TSkiffEncodingInfo result;
+ result.EncodingPart = ESkiffWriterColumnType::RangeIndex;
+ result.FieldIndex = fieldIndex;
+ return result;
+ }
+
+ static TSkiffEncodingInfo RowIndex(ui32 fieldIndex)
+ {
+ TSkiffEncodingInfo result;
+ result.EncodingPart = ESkiffWriterColumnType::RowIndex;
+ result.FieldIndex = fieldIndex;
+ return result;
+ }
+
+ static TSkiffEncodingInfo Dense(ui32 fieldIndex)
+ {
+ TSkiffEncodingInfo result;
+ result.EncodingPart = ESkiffWriterColumnType::Dense;
+ result.FieldIndex = fieldIndex;
+ return result;
+ }
+
+ static TSkiffEncodingInfo Sparse(TUnversionedValueToSkiffConverter converter, ui32 fieldIndex)
+ {
+ TSkiffEncodingInfo result;
+ result.EncodingPart = ESkiffWriterColumnType::Sparse;
+ result.Converter = std::move(converter);
+ result.FieldIndex = fieldIndex;
+ return result;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+struct TSparseFieldInfo
+{
+ const TUnversionedValueToSkiffConverter* Converter;
+ ui32 SparseFieldTag;
+ ui32 ValueIndex;
+
+ TSparseFieldInfo(const TUnversionedValueToSkiffConverter* converter, ui32 sparseFieldTag, ui32 valueIndex)
+ : Converter(converter)
+ , SparseFieldTag(sparseFieldTag)
+ , ValueIndex(valueIndex)
+ { }
+};
+
+struct TDenseFieldWriterInfo
+{
+ TUnversionedValueToSkiffConverter Converter;
+ ui16 ColumnId;
+
+ TDenseFieldWriterInfo(TUnversionedValueToSkiffConverter converter, ui16 columnId)
+ : Converter(std::move(converter))
+ , ColumnId(columnId)
+ { }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSkiffWriterTableDescription
+{
+ std::vector<TSkiffEncodingInfo> KnownFields;
+ std::vector<TDenseFieldWriterInfo> DenseFieldInfos;
+ int KeySwitchFieldIndex = -1;
+ int RangeIndexFieldIndex = -1;
+ int RowIndexFieldIndex = -1;
+ ERowRangeIndexMode RangeIndexMode = ERowRangeIndexMode::Incremental;
+ ERowRangeIndexMode RowIndexMode = ERowRangeIndexMode::Incremental;
+ bool HasSparseColumns = false;
+ bool HasOtherColumns = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSkiffWriter
+ : public TSchemalessFormatWriterBase
+{
+public:
+ TSkiffWriter(
+ TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+ : TSchemalessFormatWriterBase(
+ std::move(nameTable),
+ std::move(output),
+ enableContextSaving,
+ std::move(controlAttributesConfig),
+ keyColumnCount)
+ { }
+
+ void Init(const std::vector<TTableSchemaPtr>& schemas, const std::vector<std::shared_ptr<TSkiffSchema>>& tableSkiffSchemas)
+ {
+ GetError().ThrowOnError();
+
+ for (const auto& schema : schemas) {
+ UnversionedValueToYsonConverter_.emplace_back(NameTable_, schema, TYsonConverterConfig());
+ }
+
+ std::shared_ptr<TSkiffSchema> streamSchema;
+ if (ControlAttributesConfig_->EnableEndOfStream) {
+ streamSchema = CreateRepeatedVariant16Schema(tableSkiffSchemas);
+ } else {
+ streamSchema = CreateVariant16Schema(tableSkiffSchemas);
+ }
+ SkiffWriter_.emplace(std::move(streamSchema), GetOutputStream());
+
+ auto indexedSchemas = TIndexedSchemas(schemas);
+
+ auto tableDescriptionList = CreateTableDescriptionList(tableSkiffSchemas, RangeIndexColumnName, RowIndexColumnName);
+ for (const auto& commonTableDescription : tableDescriptionList) {
+ auto tableIndex = TableDescriptionList_.size();
+ TableDescriptionList_.emplace_back();
+ auto& writerTableDescription = TableDescriptionList_.back();
+ writerTableDescription.HasOtherColumns = commonTableDescription.HasOtherColumns;
+ writerTableDescription.HasSparseColumns = !commonTableDescription.SparseFieldDescriptionList.empty();
+ writerTableDescription.KeySwitchFieldIndex = MissingSystemColumn;
+
+ writerTableDescription.RowIndexFieldIndex = MissingSystemColumn;
+ writerTableDescription.RowIndexMode = commonTableDescription.RowIndexMode;
+
+ writerTableDescription.RangeIndexFieldIndex = MissingSystemColumn;
+ writerTableDescription.RangeIndexMode = commonTableDescription.RangeIndexMode;
+
+ auto& knownFields = writerTableDescription.KnownFields;
+
+ const auto& denseFieldDescriptionList = commonTableDescription.DenseFieldDescriptionList;
+
+ auto& denseFieldWriterInfos = writerTableDescription.DenseFieldInfos;
+
+ auto createComplexValueConverter = [&] (const TFieldDescription& skiffField, bool isSparse) -> TUnversionedValueToSkiffConverter {
+ auto columnSchema = indexedSchemas.GetColumnSchema(tableIndex, skiffField.Name());
+
+ // NB: we don't create complex value converter for simple types
+ // (column is missing in schema or has simple type).
+ // 1. Complex value converter expects unversioned values of type ANY
+ // and simple types have other types.
+ // 2. For historical reasons we don't check Skiff schema that strictly for simple types,
+ // e.g we allow column to be optional in table schema and be required in Skiff schema
+ // (runtime check is used in such cases).
+ if (!columnSchema) {
+ if (!skiffField.Simplify() && !skiffField.IsRequired()) {
+ // NB. Special case, column is described in Skiff schema as non required complex field
+ // but is missing in schema.
+ // We expect it to be missing in whole table and return corresponding converter.
+ return CreateMissingCompositeValueConverter(skiffField.Name());
+ }
+ }
+
+ TLogicalTypePtr logicalType = OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any));
+ if (columnSchema) {
+ logicalType = columnSchema->LogicalType();
+ }
+ auto denullifiedLogicalType = DenullifyLogicalType(logicalType);
+ try {
+ switch (denullifiedLogicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return CreateSimpleValueConverter(
+ skiffField.ValidatedSimplify(),
+ skiffField.IsRequired(),
+ denullifiedLogicalType->AsSimpleTypeRef().GetElement());
+ case ELogicalMetatype::Decimal:
+ return CreateDecimalValueConverter(skiffField, denullifiedLogicalType->AsDecimalTypeRef());
+ case ELogicalMetatype::Optional:
+ // NB. It's complex optional because we denullified type
+ case ELogicalMetatype::List:
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantStruct:
+ case ELogicalMetatype::VariantTuple:
+ case ELogicalMetatype::Dict: {
+ auto descriptor = TComplexTypeFieldDescriptor(skiffField.Name(), columnSchema->LogicalType());
+ return CreateComplexValueConverter(std::move(descriptor), skiffField.Schema(), isSparse);
+ }
+ case ELogicalMetatype::Tagged:
+ // Don't expect tagged type in denullified logical type
+ break;
+ }
+ YT_ABORT();
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Cannot create Skiff writer for column %Qv",
+ skiffField.Name())
+ << TErrorAttribute("logical_type", logicalType)
+ << ex;
+ }
+ };
+
+ size_t nextDenseIndex = 0;
+ for (size_t i = 0; i < denseFieldDescriptionList.size(); ++i) {
+ const auto& denseField = denseFieldDescriptionList[i];
+ const auto id = NameTable_->GetIdOrRegisterName(denseField.Name());
+ ResizeToContainIndex(&knownFields, id);
+ YT_VERIFY(knownFields[id].EncodingPart == ESkiffWriterColumnType::Unknown);
+
+ if (denseField.Schema()->GetWireType() == EWireType::Nothing) {
+ knownFields[id] = TSkiffEncodingInfo::Skip();
+ continue;
+ }
+
+ TUnversionedValueToSkiffConverter converter;
+ try {
+ if (denseField.Name() == RowIndexColumnName) {
+ writerTableDescription.RowIndexFieldIndex = nextDenseIndex;
+ knownFields[id] = TSkiffEncodingInfo::RowIndex(nextDenseIndex);
+
+ auto method =
+ commonTableDescription.RowIndexMode == ERowRangeIndexMode::Incremental
+ ? (&TRowAndRangeIndexWriter::WriteRowIndex<ERowRangeIndexMode::Incremental>)
+ : (&TRowAndRangeIndexWriter::WriteRowIndex<ERowRangeIndexMode::IncrementalWithError>);
+
+ converter = std::bind(
+ method,
+ &RowAndRangeIndexWriter_,
+ std::placeholders::_1,
+ std::placeholders::_2,
+ std::placeholders::_3);
+ } else if (denseField.Name() == RangeIndexColumnName) {
+ writerTableDescription.RangeIndexFieldIndex = nextDenseIndex;
+ knownFields[id] = TSkiffEncodingInfo::RangeIndex(nextDenseIndex);
+
+ auto method =
+ commonTableDescription.RangeIndexMode == ERowRangeIndexMode::Incremental
+ ? (&TRowAndRangeIndexWriter::WriteRangeIndex<ERowRangeIndexMode::Incremental>)
+ : (&TRowAndRangeIndexWriter::WriteRangeIndex<ERowRangeIndexMode::IncrementalWithError>);
+
+ converter = std::bind(
+ method,
+ &RowAndRangeIndexWriter_,
+ std::placeholders::_1,
+ std::placeholders::_2,
+ std::placeholders::_3);
+ } else {
+ if (denseField.Name() == KeySwitchColumnName) {
+ writerTableDescription.KeySwitchFieldIndex = nextDenseIndex;
+ }
+ knownFields[id] = TSkiffEncodingInfo::Dense(nextDenseIndex);
+ converter = createComplexValueConverter(denseField, /*sparse*/ false);
+ }
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Cannot create Skiff writer for table #%v",
+ tableIndex)
+ << ex;
+ }
+ denseFieldWriterInfos.emplace_back(converter, id);
+ ++nextDenseIndex;
+ }
+
+ const auto& sparseFieldDescriptionList = commonTableDescription.SparseFieldDescriptionList;
+ for (size_t i = 0; i < sparseFieldDescriptionList.size(); ++i) {
+ const auto& sparseField = sparseFieldDescriptionList[i];
+ auto id = NameTable_->GetIdOrRegisterName(sparseField.Name());
+ ResizeToContainIndex(&knownFields, id);
+ YT_VERIFY(knownFields[id].EncodingPart == ESkiffWriterColumnType::Unknown);
+
+ try {
+ auto converter = createComplexValueConverter(sparseField, /*sparse*/ true);
+ knownFields[id] = TSkiffEncodingInfo::Sparse(converter, i);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Cannot create Skiff writer for table #%v",
+ tableIndex)
+ << ex;
+ }
+ }
+
+ const auto systemColumnMaxId = Max(GetTableIndexColumnId(), GetRangeIndexColumnId(), GetRowIndexColumnId());
+ ResizeToContainIndex(&knownFields, systemColumnMaxId);
+ knownFields[GetTableIndexColumnId()] = TSkiffEncodingInfo::Skip();
+ if (writerTableDescription.RangeIndexFieldIndex == MissingSystemColumn) {
+ knownFields[GetRangeIndexColumnId()] = TSkiffEncodingInfo::Skip();
+ }
+ if (writerTableDescription.RowIndexFieldIndex == MissingSystemColumn) {
+ knownFields[GetRowIndexColumnId()] = TSkiffEncodingInfo::Skip();
+ }
+ }
+ }
+
+private:
+ std::vector<TErrorAttribute> GetRowPositionErrorAttributes() const
+ {
+ if (CurrentRow_ == nullptr) {
+ return {};
+ }
+
+ i64 tableIndex = 0;
+ std::optional<i64> rowIndex;
+
+ // We don't use tableIndex / rowIndex from DoWrite function because sometimes we want
+ // to throw error before DoWrite knows table index / row index.
+ // To keep things simple we always recompute table index / row index by ourselves.
+ for (const auto& value : *CurrentRow_) {
+ if (value.Id == GetTableIndexColumnId()) {
+ YT_VERIFY(value.Type == EValueType::Int64);
+ tableIndex = value.Data.Int64;
+ } else if (value.Id == GetRowIndexColumnId()) {
+ YT_VERIFY(value.Type == EValueType::Int64);
+ rowIndex = value.Data.Int64;
+ }
+ }
+
+ std::vector<TErrorAttribute> result = {
+ TErrorAttribute("table_index", tableIndex),
+ };
+ if (rowIndex) {
+ result.emplace_back("row_index", *rowIndex);
+ }
+ return result;
+ }
+
+ void DoWrite(TRange<TUnversionedRow> rows) override
+ {
+ const auto rowCount = rows.Size();
+ TWriteContext writeContext;
+ writeContext.NameTable = NameTable_;
+ writeContext.TmpBuffer = &YsonBuffer_;
+
+ for (size_t rowIndexInBatch = 0; rowIndexInBatch < rowCount; ++rowIndexInBatch) {
+ auto row = rows[rowIndexInBatch];
+ CurrentRow_ = &row;
+ auto finallyGuard = Finally([&] {
+ CurrentRow_ = nullptr;
+ });
+
+ const auto valueCount = row.GetCount();
+ ui32 tableIndex = 0;
+ for (const auto& value : row) {
+ if (value.Id == GetTableIndexColumnId()) {
+ tableIndex = value.Data.Int64;
+ break;
+ }
+ }
+ if (tableIndex >= TableDescriptionList_.size()) {
+ THROW_ERROR_EXCEPTION("Table #%v is not described by Skiff schema",
+ tableIndex)
+ << GetRowPositionErrorAttributes();
+ }
+ YT_VERIFY(tableIndex < UnversionedValueToYsonConverter_.size());
+ writeContext.UnversionedValueYsonConverter = &UnversionedValueToYsonConverter_[tableIndex];
+
+ const auto& knownFields = TableDescriptionList_[tableIndex].KnownFields;
+ const auto& denseFields = TableDescriptionList_[tableIndex].DenseFieldInfos;
+ const auto hasOtherColumns = TableDescriptionList_[tableIndex].HasOtherColumns;
+ const auto hasSparseColumns = TableDescriptionList_[tableIndex].HasSparseColumns;
+ const auto keySwitchFieldIndex = TableDescriptionList_[tableIndex].KeySwitchFieldIndex;
+ const auto rowIndexFieldIndex = TableDescriptionList_[tableIndex].RowIndexFieldIndex;
+ const auto rangeIndexFieldIndex = TableDescriptionList_[tableIndex].RangeIndexFieldIndex;
+
+ const bool isLastRowInBatch = rowIndexInBatch + 1 == rowCount;
+
+ constexpr ui16 missingColumnPlaceholder = -1;
+ constexpr ui16 keySwitchColumnPlaceholder = -2;
+ DenseIndexes_.assign(denseFields.size(), missingColumnPlaceholder);
+ SparseFields_.clear();
+ OtherValueIndexes_.clear();
+
+ if (keySwitchFieldIndex != MissingSystemColumn) {
+ DenseIndexes_[keySwitchFieldIndex] = keySwitchColumnPlaceholder;
+ }
+
+ ui16 rowIndexValueId = missingColumnPlaceholder;
+ ui16 rangeIndexValueId = missingColumnPlaceholder;
+
+ for (ui32 valueIndex = 0; valueIndex < valueCount; ++valueIndex) {
+ const auto& value = row[valueIndex];
+
+ const auto columnId = value.Id;
+ static const TSkiffEncodingInfo unknownField = TSkiffEncodingInfo();
+ const auto& encodingInfo = columnId < knownFields.size() ? knownFields[columnId] : unknownField;
+ switch (encodingInfo.EncodingPart) {
+ case ESkiffWriterColumnType::Dense:
+ DenseIndexes_[encodingInfo.FieldIndex] = valueIndex;
+ break;
+ case ESkiffWriterColumnType::Sparse:
+ SparseFields_.emplace_back(
+ &encodingInfo.Converter,
+ encodingInfo.FieldIndex,
+ valueIndex);
+ break;
+ case ESkiffWriterColumnType::Skip:
+ break;
+ case ESkiffWriterColumnType::RowIndex:
+ rowIndexValueId = valueIndex;
+ break;
+ case ESkiffWriterColumnType::RangeIndex:
+ rangeIndexValueId = valueIndex;
+ break;
+ case ESkiffWriterColumnType::Unknown:
+ if (!hasOtherColumns) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Column %Qv is not described by Skiff schema and there is no %Qv column",
+ NameTable_->GetName(columnId),
+ OtherColumnsName)
+ << GetRowPositionErrorAttributes();
+ }
+ OtherValueIndexes_.emplace_back(valueIndex);
+ break;
+ default:
+ YT_ABORT();
+ }
+ }
+ if (rowIndexFieldIndex != MissingSystemColumn || rangeIndexFieldIndex != MissingSystemColumn) {
+ RowAndRangeIndexWriter_.PrepareTableIndex(tableIndex);
+ if (rangeIndexFieldIndex != MissingSystemColumn) {
+ DenseIndexes_[rangeIndexFieldIndex] = rangeIndexValueId;
+ }
+ if (rangeIndexValueId != missingColumnPlaceholder) {
+ YT_VERIFY(row[rangeIndexValueId].Type == EValueType::Int64);
+ const auto rangeIndex = row[rangeIndexValueId].Data.Int64;
+ RowAndRangeIndexWriter_.PrepareRangeIndex(rangeIndex);
+ } else if (rangeIndexFieldIndex != MissingSystemColumn) {
+ RowAndRangeIndexWriter_.ResetRangeIndex();
+ }
+
+ if (rowIndexFieldIndex != MissingSystemColumn) {
+ DenseIndexes_[rowIndexFieldIndex] = rowIndexValueId;
+ if (rowIndexValueId == missingColumnPlaceholder) {
+ RowAndRangeIndexWriter_.ResetRowIndex();
+ }
+ }
+ }
+
+ SkiffWriter_->WriteVariant16Tag(tableIndex);
+ for (size_t idx = 0; idx < denseFields.size(); ++idx) {
+ const auto& fieldInfo = denseFields[idx];
+ const auto valueIndex = DenseIndexes_[idx];
+
+ switch (valueIndex) {
+ case missingColumnPlaceholder:
+ fieldInfo.Converter(
+ MakeUnversionedSentinelValue(EValueType::Null, fieldInfo.ColumnId),
+ &*SkiffWriter_,
+ &writeContext);
+ break;
+ case keySwitchColumnPlaceholder:
+ SkiffWriter_->WriteBoolean(CheckKeySwitch(row, isLastRowInBatch));
+ break;
+ default: {
+ const auto& value = row[valueIndex];
+ fieldInfo.Converter(
+ value,
+ &*SkiffWriter_,
+ &writeContext);
+ break;
+ }
+ }
+ }
+
+ if (hasSparseColumns) {
+ for (const auto& fieldInfo : SparseFields_) {
+ const auto& value = row[fieldInfo.ValueIndex];
+ if (value.Type != EValueType::Null) {
+ SkiffWriter_->WriteVariant16Tag(fieldInfo.SparseFieldTag);
+ (*fieldInfo.Converter)(value, &*SkiffWriter_, &writeContext);
+ }
+ }
+ SkiffWriter_->WriteVariant16Tag(EndOfSequenceTag<ui16>());
+ }
+ if (hasOtherColumns) {
+ YsonBuffer_.Clear();
+ TBufferOutput out(YsonBuffer_);
+ NYson::TYsonWriter writer(
+ &out,
+ NYson::EYsonFormat::Binary,
+ NYson::EYsonType::Node,
+ /* enableRaw */ true);
+ writer.OnBeginMap();
+ for (const auto otherValueIndex : OtherValueIndexes_) {
+ const auto& value = row[otherValueIndex];
+ writer.OnKeyedItem(NameTable_->GetName(value.Id));
+ writeContext.UnversionedValueYsonConverter->WriteValue(value, &writer);
+ }
+ writer.OnEndMap();
+ SkiffWriter_->WriteYson32(TStringBuf(YsonBuffer_.Data(), YsonBuffer_.Size()));
+ }
+ SkiffWriter_->Flush();
+ TryFlushBuffer(false);
+ }
+ Flush();
+ }
+
+ TFuture<void> Flush() override
+ {
+ SkiffWriter_->Flush();
+ return TSchemalessFormatWriterBase::Flush();
+ }
+
+ TFuture<void> Close() override
+ {
+ // NB(gritukan): You can't move it into WriteEndOfStream, since it
+ // will be called between SkiffWriter and buffer flushes leading to
+ // their inconsistency.
+ if (ControlAttributesConfig_->EnableEndOfStream) {
+ SkiffWriter_->WriteVariant16Tag(EndOfSequenceTag<ui16>());
+ }
+
+ SkiffWriter_->Flush();
+ return TSchemalessFormatWriterBase::Close();
+ }
+
+private:
+ std::optional<NSkiff::TCheckedInDebugSkiffWriter> SkiffWriter_;
+
+ std::vector<ui16> DenseIndexes_;
+ std::vector<TSparseFieldInfo> SparseFields_;
+ std::vector<ui16> OtherValueIndexes_;
+
+ // Table #i is described by element with index i.
+ std::vector<TSkiffWriterTableDescription> TableDescriptionList_;
+
+ std::vector<TUnversionedValueYsonWriter> UnversionedValueToYsonConverter_;
+
+ // Buffer that we are going to reuse in order to reduce memory allocations.
+ TBuffer YsonBuffer_;
+
+ TRowAndRangeIndexWriter RowAndRangeIndexWriter_;
+
+ const TUnversionedRow* CurrentRow_ = nullptr;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateWriterForSkiff(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TNameTablePtr nameTable,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ try {
+ auto config = NYTree::ConvertTo<TSkiffFormatConfigPtr>(attributes);
+ auto skiffSchemas = ParseSkiffSchemas(config->SkiffSchemaRegistry, config->TableSkiffSchemas);
+
+ auto copySchemas = schemas;
+ if (config->OverrideIntermediateTableSchema) {
+ Y_VERIFY(!schemas.empty());
+ if (!IsTrivialIntermediateSchema(*schemas[0])) {
+ THROW_ERROR_EXCEPTION("Cannot use \"override_intermediate_table_schema\" since input table #0 has nontrivial schema")
+ << TErrorAttribute("schema", *schemas[0]);
+ }
+ copySchemas[0] = New<TTableSchema>(*config->OverrideIntermediateTableSchema);
+ }
+
+ return CreateWriterForSkiff(
+ skiffSchemas,
+ std::move(nameTable),
+ copySchemas,
+ std::move(output),
+ enableContextSaving,
+ std::move(controlAttributesConfig),
+ keyColumnCount);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(EErrorCode::InvalidFormat, "Failed to parse config for Skiff format") << ex;
+ }
+}
+
+ISchemalessFormatWriterPtr CreateWriterForSkiff(
+ const std::vector<std::shared_ptr<TSkiffSchema>>& tableSkiffSchemas,
+ NTableClient::TNameTablePtr nameTable,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ auto result = New<TSkiffWriter>(
+ std::move(nameTable),
+ std::move(output),
+ enableContextSaving,
+ std::move(controlAttributesConfig),
+ keyColumnCount);
+ result->Init(schemas, tableSkiffSchemas);
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/skiff_writer.h b/yt/yt/client/formats/skiff_writer.h
new file mode 100644
index 0000000000..9cd8f66268
--- /dev/null
+++ b/yt/yt/client/formats/skiff_writer.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+#include <yt/yt/library/skiff_ext/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateWriterForSkiff(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TNameTablePtr nameTable,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+ISchemalessFormatWriterPtr CreateWriterForSkiff(
+ const std::vector<std::shared_ptr<NSkiff::TSkiffSchema>>& tableSkiffSchemas,
+ NTableClient::TNameTablePtr nameTable,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormat
diff --git a/yt/yt/client/formats/skiff_yson_converter-inl.h b/yt/yt/client/formats/skiff_yson_converter-inl.h
new file mode 100644
index 0000000000..2c667b35d6
--- /dev/null
+++ b/yt/yt/client/formats/skiff_yson_converter-inl.h
@@ -0,0 +1,157 @@
+#ifndef SKIFF_YSON_CONVERTER_INL_H_
+#error "Direct inclusion of this file is not allowed; include skiff_yson_converter.h"
+#endif
+
+#include <util/system/byteorder.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <NSkiff::EWireType wireType>
+Y_FORCE_INLINE auto TSimpleSkiffParser<wireType>::operator () (NSkiff::TCheckedInDebugSkiffParser* parser) const
+{
+ using namespace NSkiff;
+
+ if constexpr (wireType == EWireType::Int8) {
+ return parser->ParseInt8();
+ } else if constexpr (wireType == EWireType::Int16) {
+ return parser->ParseInt16();
+ } else if constexpr (wireType == EWireType::Int32) {
+ return parser->ParseInt32();
+ } else if constexpr (wireType == EWireType::Int64) {
+ return parser->ParseInt64();
+ } else if constexpr (wireType == EWireType::Uint8) {
+ return parser->ParseUint8();
+ } else if constexpr (wireType == EWireType::Uint16) {
+ return parser->ParseUint16();
+ } else if constexpr (wireType == EWireType::Uint32) {
+ return parser->ParseUint32();
+ } else if constexpr (wireType == EWireType::Uint64) {
+ return parser->ParseUint64();
+ } else if constexpr (wireType == EWireType::Boolean) {
+ return parser->ParseBoolean();
+ } else if constexpr (wireType == EWireType::Double) {
+ return parser->ParseDouble();
+ } else if constexpr (wireType == EWireType::String32) {
+ return parser->ParseString32();
+ } else if constexpr (wireType == EWireType::Nothing) {
+ return nullptr;
+ } else {
+ static_assert(wireType == EWireType::Int64);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <NSkiff::EWireType SkiffWireType>
+TDecimalSkiffParser<SkiffWireType>::TDecimalSkiffParser(int precision)
+ : Precision_(precision)
+{
+ CheckSkiffWireTypeForDecimal(precision, SkiffWireType);
+}
+
+template <NSkiff::EWireType SkiffWireType>
+Y_FORCE_INLINE TStringBuf TDecimalSkiffParser<SkiffWireType>::operator() (NSkiff::TCheckedInDebugSkiffParser* parser) const
+{
+ using namespace NSkiff;
+ using namespace NDecimal;
+
+ if constexpr (SkiffWireType == EWireType::Int32) {
+ auto value = parser->ParseInt32();
+ return TDecimal::WriteBinary32(Precision_, value, Buffer_, sizeof(Buffer_));
+ } else if constexpr (SkiffWireType == EWireType::Int64) {
+ auto value = parser->ParseInt64();
+ return TDecimal::WriteBinary64(Precision_, value, Buffer_, sizeof(Buffer_));
+ } else if constexpr (SkiffWireType == EWireType::Int128) {
+ const auto skiffValue = parser->ParseInt128();
+ return TDecimal::WriteBinary128(
+ Precision_,
+ TDecimal::TValue128{skiffValue.Low, skiffValue.High},
+ Buffer_,
+ sizeof(Buffer_));
+ } else {
+ static_assert(SkiffWireType == EWireType::Int128);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <NSkiff::EWireType SkiffWireType>
+TDecimalSkiffWriter<SkiffWireType>::TDecimalSkiffWriter(int precision)
+ : Precision_(precision)
+{
+ CheckSkiffWireTypeForDecimal(precision, SkiffWireType);
+}
+
+template <NSkiff::EWireType SkiffWireType>
+void TDecimalSkiffWriter<SkiffWireType>::operator()(TStringBuf value, NSkiff::TCheckedInDebugSkiffWriter* writer) const
+{
+ using namespace NSkiff;
+ using namespace NDecimal;
+
+ if constexpr (SkiffWireType == EWireType::Int32) {
+ auto intValue = TDecimal::ParseBinary32(Precision_, value);
+ writer->WriteInt32(intValue);
+ } else if constexpr (SkiffWireType == EWireType::Int64) {
+ auto intValue = TDecimal::ParseBinary64(Precision_, value);
+ writer->WriteInt64(intValue);
+ } else if constexpr (SkiffWireType == EWireType::Int128) {
+ auto intValue = TDecimal::ParseBinary128(Precision_, value);
+ writer->WriteInt128(TInt128{intValue.Low, intValue.High});
+ } else {
+ // poor man's static_assert(false)
+ static_assert(SkiffWireType == EWireType::Int128);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStringBuf TUuidParser::operator()(NSkiff::TCheckedInDebugSkiffParser* parser) const
+{
+ static_assert(sizeof(Buffer_) == 16);
+
+ auto value = parser->ParseUint128();
+ Buffer_[0] = HostToInet(value.High);
+ Buffer_[1] = HostToInet(value.Low);
+ return TStringBuf(reinterpret_cast<const char*>(Buffer_), sizeof(Buffer_));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TUuidWriter::operator()(TStringBuf value, NSkiff::TCheckedInDebugSkiffWriter* writer) const
+{
+ constexpr size_t ExpectedSize = 16;
+ if (value.size() != ExpectedSize) {
+ THROW_ERROR_EXCEPTION("Invalid size of UUID value: expected %v, actual %v",
+ ExpectedSize,
+ value.size());
+ }
+ const ui64* array = reinterpret_cast<const ui64*>(value.Data());
+ writer->WriteUint128(NSkiff::TUint128{InetToHost(array[1]), InetToHost(array[0])});
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <NSkiff::EWireType wireType, typename TValueType>
+void CheckIntSize(TValueType value)
+{
+ using TIntType = typename NSkiff::TUnderlyingIntegerType<wireType>::TValue;
+ bool ok;
+ if constexpr (std::is_same_v<TValueType, i64>) {
+ ok = std::numeric_limits<TIntType>::min() <= value && value <= std::numeric_limits<TIntType>::max();
+ } else if constexpr (std::is_same_v<TValueType, ui64>) {
+ ok = value <= std::numeric_limits<TIntType>::max();
+ } else {
+ static_assert(std::is_same_v<TIntType, i64>, "We expect either i64 or ui64 here");
+ }
+ if (!ok) {
+ THROW_ERROR_EXCEPTION("Value %v is out of range for possible values for skiff type %Qlv",
+ value,
+ wireType);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/skiff_yson_converter.cpp b/yt/yt/client/formats/skiff_yson_converter.cpp
new file mode 100644
index 0000000000..171bfd9a9a
--- /dev/null
+++ b/yt/yt/client/formats/skiff_yson_converter.cpp
@@ -0,0 +1,1899 @@
+#include "skiff_yson_converter.h"
+
+#include <yt/yt/client/complex_types/check_yson_token.h>
+#include <yt/yt/client/table_client/logical_type.h>
+
+#include <yt/yt/library/decimal/decimal.h>
+
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/core/yson/parser.h>
+#include <yt/yt/core/yson/token_writer.h>
+#include <yt/yt/core/yson/writer.h>
+
+#include <library/cpp/skiff/skiff.h>
+#include <library/cpp/skiff/skiff_schema.h>
+
+#include <util/stream/zerocopy.h>
+#include <util/stream/mem.h>
+
+namespace NYT::NFormats {
+
+using namespace NSkiff;
+using namespace NYson;
+using namespace NTableClient;
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TTypePair = std::pair<TComplexTypeFieldDescriptor, std::shared_ptr<TSkiffSchema>>;
+
+struct TConverterCreationContext;
+
+TYsonToSkiffConverter CreateYsonToSkiffConverterImpl(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config);
+
+TSkiffToYsonConverter CreateSkiffToYsonConverterImpl(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TConverterCreationContext
+{
+ int NestingLevel = 0;
+};
+
+std::vector<TErrorAttribute> SkiffYsonErrorAttributes(const TComplexTypeFieldDescriptor& descriptor, const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ return {
+ TErrorAttribute("complex_type_field", descriptor.GetDescription()),
+ TErrorAttribute("logical_type_dbg", ToString(*descriptor.GetType())),
+ TErrorAttribute("skiff_schema_dbg", GetShortDebugString(skiffSchema)),
+ };
+}
+
+std::shared_ptr<TSkiffSchema> GetOptionalChild(const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ if (skiffSchema->GetWireType() != EWireType::Variant8) {
+ return nullptr;
+ }
+ auto children = skiffSchema->GetChildren();
+ if (children.size() != 2) {
+ return nullptr;
+ }
+ if (children[0]->GetWireType() != EWireType::Nothing) {
+ return nullptr;
+ }
+ return children[1];
+}
+
+struct TSkiffStructField
+{
+ TString Name;
+ std::shared_ptr<TSkiffSchema> Type;
+};
+
+template<EWireType wireType>
+constexpr EYsonItemType WireTypeToYsonItemType()
+{
+ if constexpr (
+ wireType == EWireType::Int8 ||
+ wireType == EWireType::Int16 ||
+ wireType == EWireType::Int32 ||
+ wireType == EWireType::Int64)
+ {
+ return EYsonItemType::Int64Value;
+ } else if constexpr (
+ wireType == EWireType::Uint8 ||
+ wireType == EWireType::Uint16 ||
+ wireType == EWireType::Uint32 ||
+ wireType == EWireType::Uint64)
+ {
+ return EYsonItemType::Uint64Value;
+ } else if constexpr (wireType == EWireType::Double) {
+ return EYsonItemType::DoubleValue;
+ } else if constexpr (wireType == EWireType::Boolean) {
+ return EYsonItemType::BooleanValue;
+ } else if constexpr (wireType == EWireType::String32) {
+ return EYsonItemType::StringValue;
+ } else if constexpr (wireType == EWireType::Nothing) {
+ return EYsonItemType::EntityValue;
+ } else {
+ static_assert(wireType == EWireType::Int64);
+ }
+}
+
+struct TOptionalTypesMatch
+{
+ TTypePair InnerTypes;
+ int LogicalNesting = 0;
+ int SkiffNesting = 0;
+};
+
+[[noreturn]] void ThrowBadWireType(EWireType expected, EWireType actual)
+{
+ THROW_ERROR_EXCEPTION("Bad Skiff wire type: expected %Qlv, actual %Qlv",
+ expected,
+ actual);
+}
+
+[[noreturn]] void RethrowCannotMatchField(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const std::exception& ex)
+{
+ THROW_ERROR_EXCEPTION("Cannot match field %Qv to Skiff schema",
+ descriptor.GetDescription())
+ << SkiffYsonErrorAttributes(descriptor, skiffSchema)
+ << ex;
+}
+
+template <typename... Args>
+[[noreturn]] void ThrowYsonToSkiffConversionError(const TComplexTypeFieldDescriptor& descriptor, const Args&... args)
+{
+ THROW_ERROR_EXCEPTION("Yson to Skiff conversion error while converting %Qv field",
+ descriptor.GetDescription())
+ << TError(args...);
+}
+
+[[noreturn]] void ThrowBadYsonToken(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::vector<EYsonItemType>& expected,
+ const EYsonItemType actual)
+{
+ TStringStream expectationString;
+ if (expected.size() > 1) {
+ expectationString << "one of ";
+ bool first = true;
+ for (const auto& itemType : expected) {
+ if (!first) {
+ expectationString << ", ";
+ }
+ first = false;
+ expectationString << Format("%Qlv", itemType);
+ }
+ } else {
+ YT_VERIFY(expected.size() == 1);
+ expectationString << Format("%Qlv", expected[0]);
+ }
+
+ ThrowYsonToSkiffConversionError(descriptor, "Bad yson token type, expected %v actual: %Qlv",
+ expectationString.Str(),
+ actual);
+}
+
+template <typename... Args>
+[[noreturn]] void ThrowSkiffToYsonConversionError(const TComplexTypeFieldDescriptor& descriptor, const Args&... args)
+{
+ THROW_ERROR_EXCEPTION("Skiff to Yson conversion error while converting %Qv field",
+ descriptor.GetDescription())
+ << TError(args...);
+}
+
+TOptionalTypesMatch MatchOptionalTypes(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ bool allowOmitOptional)
+{
+ // NB. Here we have a problem:
+ // variant_tuple<null, T> has exactly the same Skiff representation as optional<T>.
+ // We need to perform nontrivial analysis of schemas in order to align these schemas.
+ //
+ // When reading this code you can keep in mind following examples
+ // 1. logical type: optional<variant_tuple<null, T>>
+ // Skiff type: variant8<nothing, variant8<nothing, T>>
+ // Outer Skiff `variant8` encodes logical outer `optional` type
+ // 2. logical type: optional<variant_tuple<null, T>>
+ // Skiff type: variant8<nothing, T>
+ // Outer `variant8` Skiff type encodes logical inner `variant_tuple` type when allowOmitOptional is true.
+
+ // When logical type is either optional<T> or variant_tuple<null, T> this function returns descriptor of T.
+ // Otherwise it returns std::nullopt.
+ const auto& getLogicalOptionalLikeChild = [] (const TComplexTypeFieldDescriptor& descriptor) -> std::optional<TComplexTypeFieldDescriptor> {
+ const auto& type = descriptor.GetType();
+ switch (type->GetMetatype()) {
+ case ELogicalMetatype::Optional:
+ return descriptor.OptionalElement();
+ case ELogicalMetatype::VariantTuple: {
+ const auto& variantType = type->AsVariantTupleTypeRef();
+ if (variantType.GetElements().size() == 2
+ && *variantType.GetElements()[0] == *SimpleLogicalType(ESimpleLogicalValueType::Null))
+ {
+ return descriptor.VariantTupleElement(1);
+ } else {
+ return {};
+ }
+ }
+ default:
+ return {};
+ }
+ };
+
+ try {
+
+ // First of all we compute strict and relaxed depths of optional chain.
+ // Strict depth is the depth of chain where each element is optional<T>.
+ // Relaxed depth is the depth of chain where each element is optional<T> or variant<null, T>
+
+ int logicalNestingRelaxed = 0;
+ int logicalNestingStrict = 0;
+ {
+ auto innerDescriptor = descriptor;
+ while (auto element = getLogicalOptionalLikeChild(innerDescriptor)) {
+ if (innerDescriptor.GetType()->GetMetatype() == ELogicalMetatype::Optional &&
+ logicalNestingStrict == logicalNestingRelaxed) {
+ ++logicalNestingStrict;
+ }
+ ++logicalNestingRelaxed;
+ innerDescriptor = *element;
+ }
+ }
+ YT_VERIFY(logicalNestingStrict);
+ YT_VERIFY(logicalNestingRelaxed >= logicalNestingStrict);
+
+ // Then we compute depth of corresponding Skiff chain of variant<nothing, T>
+ // This chain should match to logical relaxed chain.
+ int skiffNestingRelaxed = 0;
+ {
+ std::shared_ptr<TSkiffSchema> innerSkiffSchema = skiffSchema;
+ while (auto child = GetOptionalChild(innerSkiffSchema)) {
+ ++skiffNestingRelaxed;
+ innerSkiffSchema = child;
+ }
+ }
+
+ if (logicalNestingRelaxed != skiffNestingRelaxed &&
+ !(allowOmitOptional && logicalNestingRelaxed == skiffNestingRelaxed + 1))
+ {
+ THROW_ERROR_EXCEPTION("Optional nesting mismatch: logical type nesting %v, Skiff nesting %v",
+ logicalNestingRelaxed,
+ skiffNestingRelaxed);
+ }
+
+ // NB. We only allow to omit outer optional of the column so in order to match lengths of inner chains must be
+ // equal. Based on this assertion we compute lengths of Skiff chain corresponding to length of
+ auto skiffNestingStrict = skiffNestingRelaxed - (logicalNestingRelaxed - logicalNestingStrict);
+ YT_VERIFY(skiffNestingRelaxed >= 0);
+
+ // We descend over strict chains once again to get matching inner types.
+ auto innerDescriptor = descriptor;
+ for (int i = 0; i < logicalNestingStrict; ++i) {
+ YT_VERIFY(innerDescriptor.GetType()->GetMetatype() == ELogicalMetatype::Optional);
+ innerDescriptor = innerDescriptor.OptionalElement();
+ }
+ YT_VERIFY(innerDescriptor.GetType()->GetMetatype() != ELogicalMetatype::Optional);
+
+ auto innerSkiffSchema = skiffSchema;
+ for (int i = 0; i < skiffNestingStrict; ++i) {
+ innerSkiffSchema = GetOptionalChild(innerSkiffSchema);
+ YT_VERIFY(innerSkiffSchema);
+ }
+
+ return {{std::move(innerDescriptor), innerSkiffSchema}, logicalNestingStrict, skiffNestingStrict};
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+TTypePair MatchListTypes(const TComplexTypeFieldDescriptor& descriptor, const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ try {
+ if (skiffSchema->GetWireType() != EWireType::RepeatedVariant8) {
+ ThrowBadWireType(EWireType::RepeatedVariant8, skiffSchema->GetWireType());
+ }
+ if (skiffSchema->GetChildren().size() != 1) {
+ THROW_ERROR_EXCEPTION(
+ "%Qlv has too many children: expected %v, actual %v",
+ skiffSchema->GetWireType(),
+ 1,
+ skiffSchema->GetChildren().size());
+ }
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+ return {descriptor.ListElement(), skiffSchema->GetChildren()[0]};
+}
+
+std::vector<std::optional<TTypePair>> MatchStructTypes(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ bool allowUnknownSkiffFields)
+{
+ try {
+ if (skiffSchema->GetWireType() != EWireType::Tuple) {
+ ThrowBadWireType(EWireType::Tuple, skiffSchema->GetWireType());
+ }
+
+ THashMap<TString, int> skiffNameToIndex;
+ std::vector<TSkiffStructField> skiffFields;
+ {
+ const auto& children = skiffSchema->GetChildren();
+ for (size_t i = 0; i < children.size(); ++i) {
+ const auto& child = children[i];
+ const auto& name = child->GetName();
+ if (child->GetName().empty()) {
+ THROW_ERROR_EXCEPTION("%Qv child #%v has empty name",
+ EWireType::Tuple,
+ i);
+ }
+ skiffFields.push_back({child->GetName(), child});
+ if (skiffNameToIndex.find(name) != skiffNameToIndex.end()) {
+ THROW_ERROR_EXCEPTION("%Qv has multiple children with name %Qv",
+ EWireType::Tuple,
+ name);
+ }
+ skiffNameToIndex[name] = i;
+ }
+ }
+
+ std::vector<std::optional<TTypePair>> result;
+ auto addEmptyResult = [&result, &skiffFields, allowUnknownSkiffFields] (int index) {
+ if (!allowUnknownSkiffFields) {
+ THROW_ERROR_EXCEPTION("Skiff %Qv child %Qv is not found in logical type",
+ EWireType::Tuple,
+ skiffFields[index].Name);
+ }
+ if (!GetOptionalChild(skiffFields[index].Type)) {
+ THROW_ERROR_EXCEPTION("Non optional Skiff field %Qv is missing corresponding logical struct field",
+ skiffFields[index].Name);
+ }
+ result.emplace_back(std::nullopt);
+
+ };
+
+ ssize_t nextSkiffFieldIndex = 0;
+ const auto& fields = descriptor.GetType()->AsStructTypeRef().GetFields();
+ for (size_t i = 0; i < fields.size(); ++i) {
+ auto logicalField = fields[i];
+ auto skiffIndexIt = skiffNameToIndex.find(logicalField.Name);
+ if (skiffIndexIt == skiffNameToIndex.end()) {
+ result.emplace_back(TTypePair{descriptor.StructField(i), nullptr});
+ continue;
+ }
+ const auto skiffFieldIndex = skiffIndexIt->second;
+ if (skiffFieldIndex < nextSkiffFieldIndex) {
+ THROW_ERROR_EXCEPTION("%Qv child %Qv is out of order",
+ EWireType::Tuple,
+ logicalField.Name);
+ }
+
+ for (; nextSkiffFieldIndex < skiffFieldIndex; ++nextSkiffFieldIndex) {
+ addEmptyResult(nextSkiffFieldIndex);
+ }
+ auto skiffFieldSchema = skiffFields[nextSkiffFieldIndex].Type;
+ ++nextSkiffFieldIndex;
+
+ result.emplace_back(TTypePair{descriptor.StructField(i), skiffFieldSchema});
+ }
+
+ for (; nextSkiffFieldIndex < std::ssize(skiffFields); ++nextSkiffFieldIndex) {
+ addEmptyResult(nextSkiffFieldIndex);
+ }
+
+ return result;
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+std::vector<TTypePair> MatchTupleTypes(const TComplexTypeFieldDescriptor& descriptor, const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ try {
+ if (skiffSchema->GetWireType() != EWireType::Tuple) {
+ ThrowBadWireType(EWireType::Tuple, skiffSchema->GetWireType());
+ }
+
+ const auto& elements = descriptor.GetType()->AsTupleTypeRef().GetElements();
+ const auto& children = skiffSchema->GetChildren();
+
+ if (children.size() != elements.size()) {
+ THROW_ERROR_EXCEPTION("Tuple element counts do not match: logical type elements %v, Skiff elements %v",
+ elements.size(),
+ children.size());
+ }
+
+ std::vector<TTypePair> result;
+ for (size_t i = 0; i < elements.size(); ++i) {
+ result.emplace_back(descriptor.TupleElement(i), children[i]);
+ }
+
+ return result;
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+std::vector<TTypePair> MatchVariantTupleTypes(const TComplexTypeFieldDescriptor& descriptor, const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ try {
+ if (skiffSchema->GetWireType() != EWireType::Variant8 && skiffSchema->GetWireType() != EWireType::Variant16) {
+ ThrowBadWireType(EWireType::Tuple, skiffSchema->GetWireType());
+ }
+
+ const auto& elements = descriptor.GetType()->AsVariantTupleTypeRef().GetElements();
+ const auto& children = skiffSchema->GetChildren();
+
+ if (children.size() != elements.size()) {
+ THROW_ERROR_EXCEPTION("Variant element counts do not match: logical type elements %v, Skiff elements %v",
+ elements.size(),
+ children.size());
+ }
+
+ std::vector<TTypePair> result;
+ for (size_t i = 0; i < elements.size(); ++i) {
+ result.emplace_back(descriptor.VariantTupleElement(i), children[i]);
+ }
+
+ return result;
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+std::vector<TTypePair> MatchVariantStructTypes(const TComplexTypeFieldDescriptor& descriptor, const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ try {
+ if (skiffSchema->GetWireType() != EWireType::Variant8 && skiffSchema->GetWireType() != EWireType::Variant16) {
+ ThrowBadWireType(EWireType::Variant8, skiffSchema->GetWireType());
+ }
+
+ const auto& fields = descriptor.GetType()->AsVariantStructTypeRef().GetFields();
+ const auto& children = skiffSchema->GetChildren();
+
+ if (children.size() != fields.size()) {
+ THROW_ERROR_EXCEPTION("Variant element counts do not match: logical type elements %v, Skiff elements %v",
+ fields.size(),
+ children.size());
+ }
+
+ std::vector<TTypePair> result;
+ for (size_t i = 0; i < fields.size(); ++i) {
+ if (fields[i].Name != children[i]->GetName()) {
+ THROW_ERROR_EXCEPTION("Skiff %v child #%v expected to be %Qv but %Qv found",
+ skiffSchema->GetWireType(),
+ i,
+ fields[i].Name,
+ children[i]->GetName());
+ }
+ result.emplace_back(descriptor.VariantStructField(i), children[i]);
+ }
+
+ return result;
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+std::pair<TTypePair, TTypePair> MatchDictTypes(const TComplexTypeFieldDescriptor& descriptor, const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ try {
+ if (skiffSchema->GetWireType() != EWireType::RepeatedVariant8) {
+ ThrowBadWireType(EWireType::RepeatedVariant8, skiffSchema->GetWireType());
+ }
+
+ if (skiffSchema->GetChildren().size() != 1) {
+ THROW_ERROR_EXCEPTION("%Qlv has unexpected children count: expected %v, actual %v",
+ EWireType::RepeatedVariant8,
+ 1,
+ skiffSchema->GetChildren().size());
+ }
+
+ auto tupleSchema = skiffSchema->GetChildren()[0];
+ if (tupleSchema->GetWireType() != EWireType::Tuple) {
+ THROW_ERROR_EXCEPTION("%Qlv has unexpected wire type: expected %Qlv, actual %Qlv",
+ EWireType::RepeatedVariant8,
+ EWireType::Tuple,
+ tupleSchema->GetWireType());
+ }
+
+ if (tupleSchema->GetChildren().size() != 2) {
+ THROW_ERROR_EXCEPTION("%Qlv has unexpected children count: expected %v, found %v",
+ EWireType::Tuple,
+ 1,
+ skiffSchema->GetChildren().size());
+ }
+ return {
+ {descriptor.DictKey(), tupleSchema->GetChildren()[0]},
+ {descriptor.DictValue(), tupleSchema->GetChildren()[1]}
+ };
+ } catch (const std::exception & ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <EYsonItemType ExpectedTokenType, typename TFunction>
+class TPrimitiveTypeYsonToSkiffConverter
+{
+public:
+ explicit TPrimitiveTypeYsonToSkiffConverter(TComplexTypeFieldDescriptor descriptor, TFunction function)
+ : Descriptor_(std::move(descriptor))
+ , Function_(std::move(function))
+ { }
+
+ void operator()(TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer)
+ {
+ NComplexTypes::EnsureYsonToken(Descriptor_, *cursor, ExpectedTokenType);
+ if constexpr (ExpectedTokenType == EYsonItemType::StringValue) {
+ const auto& value = cursor->GetCurrent().UncheckedAsString();
+ Function_(value, writer);
+ } else {
+ // poor man's static_assert(false)
+ static_assert(ExpectedTokenType == EYsonItemType::StringValue);
+ }
+ cursor->Next();
+ }
+
+private:
+ const TComplexTypeFieldDescriptor Descriptor_;
+ const TFunction Function_;
+};
+
+
+template <EWireType wireType>
+class TSimpleYsonToSkiffConverter
+{
+public:
+ explicit TSimpleYsonToSkiffConverter(TComplexTypeFieldDescriptor descriptor)
+ : Descriptor_(std::move(descriptor))
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer)
+ {
+ if constexpr (wireType == EWireType::Yson32) {
+ TmpString_.clear();
+ {
+ TStringOutput output(TmpString_);
+ TBufferedBinaryYsonWriter ysonWriter(&output);
+ cursor->TransferComplexValue(&ysonWriter);
+ ysonWriter.Flush();
+ }
+ writer->WriteYson32(TmpString_);
+ } else {
+ constexpr auto expectedValueType = WireTypeToYsonItemType<wireType>();
+ auto ysonItem = cursor->GetCurrent();
+ if (ysonItem.GetType() != expectedValueType) {
+ ThrowYsonToSkiffConversionError(Descriptor_, "Unexpected yson type: expected %Qlv, found %Qlv",
+ expectedValueType,
+ ysonItem.GetType());
+ }
+
+ if constexpr (wireType == EWireType::Int8) {
+ const auto value = ysonItem.UncheckedAsInt64();
+ CheckIntSize<wireType>(value);
+ writer->WriteInt8(value);
+ } else if constexpr (wireType == EWireType::Int16) {
+ const auto value = ysonItem.UncheckedAsInt64();
+ CheckIntSize<wireType>(value);
+ writer->WriteInt16(value);
+ } else if constexpr (wireType == EWireType::Int32) {
+ const auto value = ysonItem.UncheckedAsInt64();
+ CheckIntSize<wireType>(value);
+ writer->WriteInt32(value);
+ } else if constexpr (wireType == EWireType::Int64) {
+ writer->WriteInt64(ysonItem.UncheckedAsInt64());
+
+ } else if constexpr (wireType == EWireType::Uint8) {
+ auto value = ysonItem.UncheckedAsUint64();
+ CheckIntSize<wireType>(value);
+ writer->WriteUint8(value);
+ } else if constexpr (wireType == EWireType::Uint16) {
+ auto value = ysonItem.UncheckedAsUint64();
+ CheckIntSize<wireType>(value);
+ writer->WriteUint16(value);
+ } else if constexpr (wireType == EWireType::Uint32) {
+ auto value = ysonItem.UncheckedAsUint64();
+ CheckIntSize<wireType>(value);
+ writer->WriteUint32(value);
+ } else if constexpr (wireType == EWireType::Uint64) {
+ writer->WriteUint64(ysonItem.UncheckedAsUint64());
+
+ } else if constexpr (wireType == EWireType::Boolean) {
+ writer->WriteBoolean(ysonItem.UncheckedAsBoolean());
+ } else if constexpr (wireType == EWireType::Double) {
+ writer->WriteDouble(ysonItem.UncheckedAsDouble());
+ } else if constexpr (wireType == EWireType::String32) {
+ writer->WriteString32(ysonItem.UncheckedAsString());
+ } else if constexpr (wireType == EWireType::Nothing) {
+ // do nothing
+ } else {
+ static_assert(wireType == EWireType::Int64);
+ }
+ cursor->Next();
+ }
+ }
+
+private:
+ TComplexTypeFieldDescriptor Descriptor_;
+ TString TmpString_;
+};
+
+template <EYsonItemType ExpectedTokenType, typename TFunction>
+inline TPrimitiveTypeYsonToSkiffConverter<ExpectedTokenType, TFunction> CreatePrimitiveTypeYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ TFunction function)
+{
+ return TPrimitiveTypeYsonToSkiffConverter<ExpectedTokenType, TFunction>(descriptor, function);
+}
+
+TYsonToSkiffConverter CreatePrimitiveTypeYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ EWireType wireType)
+{
+ switch (wireType) {
+#define CASE(x) case x: return TSimpleYsonToSkiffConverter<x>(std::move(descriptor));
+ CASE(EWireType::Int8)
+ CASE(EWireType::Int16)
+ CASE(EWireType::Int32)
+ CASE(EWireType::Int64)
+ CASE(EWireType::Uint8)
+ CASE(EWireType::Uint16)
+ CASE(EWireType::Uint32)
+ CASE(EWireType::Uint64)
+ CASE(EWireType::Boolean)
+ CASE(EWireType::Double)
+ CASE(EWireType::String32)
+ CASE(EWireType::Yson32)
+ CASE(EWireType::Nothing)
+#undef CASE
+ default:
+ YT_ABORT();
+ }
+}
+
+TYsonToSkiffConverter CreateSimpleYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ try {
+ const auto wireType = skiffSchema->GetWireType();
+ const auto logicalType = descriptor.GetType()->AsSimpleTypeRef().GetElement();
+ switch (logicalType) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+
+ case ESimpleLogicalValueType::Interval:
+ CheckWireType(wireType, {EWireType::Int8, EWireType::Int16, EWireType::Int32, EWireType::Int64});
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ CheckWireType(wireType, {EWireType::Uint8, EWireType::Uint16, EWireType::Uint32, EWireType::Uint64});
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+
+ case ESimpleLogicalValueType::Float:
+ case ESimpleLogicalValueType::Double:
+ CheckWireType(wireType, {EWireType::Double});
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+
+ case ESimpleLogicalValueType::Boolean:
+ CheckWireType(wireType, {EWireType::Boolean});
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+
+ case ESimpleLogicalValueType::Utf8:
+ case ESimpleLogicalValueType::Json:
+ case ESimpleLogicalValueType::String:
+ CheckWireType(wireType, {EWireType::String32});
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+
+ case ESimpleLogicalValueType::Any:
+ CheckWireType(wireType, {EWireType::Yson32});
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ CheckWireType(wireType, {EWireType::Nothing});
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+
+ case ESimpleLogicalValueType::Uuid:
+ CheckWireType(wireType, {EWireType::Uint128, EWireType::String32});
+ if (wireType == EWireType::Uint128) {
+ return CreatePrimitiveTypeYsonToSkiffConverter<EYsonItemType::StringValue>(
+ std::move(descriptor),
+ TUuidWriter());
+ } else {
+ return CreatePrimitiveTypeYsonToSkiffConverter(std::move(descriptor), wireType);
+ }
+ }
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+TYsonToSkiffConverter CreateDecimalYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ const auto& logicalType = descriptor.GetType();
+ const int precision = logicalType->AsDecimalTypeRef().GetPrecision();
+ const auto wireType = skiffSchema->GetWireType();
+ switch (wireType) {
+ case EWireType::Int32:
+ return CreatePrimitiveTypeYsonToSkiffConverter<EYsonItemType::StringValue>(
+ std::move(descriptor),
+ TDecimalSkiffWriter<EWireType::Int32>(precision));
+ case EWireType::Int64:
+ return CreatePrimitiveTypeYsonToSkiffConverter<EYsonItemType::StringValue>(
+ std::move(descriptor),
+ TDecimalSkiffWriter<EWireType::Int64>(precision));
+ case EWireType::Int128:
+ return CreatePrimitiveTypeYsonToSkiffConverter<EYsonItemType::StringValue>(
+ std::move(descriptor),
+ TDecimalSkiffWriter<EWireType::Int128>(precision));
+ default:
+ CheckSkiffWireTypeForDecimal(precision, wireType);
+ YT_ABORT();
+ }
+}
+
+class TOptionalYsonToSkiffConverterImpl
+{
+public:
+ TOptionalYsonToSkiffConverterImpl(
+ TYsonToSkiffConverter innerConverter,
+ TComplexTypeFieldDescriptor descriptor,
+ int ysonOptionalLevel,
+ int skiffOptionalLevel)
+ : InnerConverter_(std::move(innerConverter))
+ , Descriptor_(std::move(descriptor))
+ , OuterExpectFilledLevel_(ysonOptionalLevel > 1 ? ysonOptionalLevel - skiffOptionalLevel : 0)
+ , OuterTranslateLevel_(ysonOptionalLevel - 1)
+ , InnerOptionalTranslate_(skiffOptionalLevel > 0)
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer)
+ {
+ auto throwValueExpectedToBeNonempty = [&] {
+ ThrowYsonToSkiffConversionError(Descriptor_, "\"#\" found while value expected to be nonempty");
+ };
+
+ int outerOptionalsFound = 0;
+ for (; outerOptionalsFound < OuterExpectFilledLevel_; ++outerOptionalsFound) {
+ if (cursor->GetCurrent().GetType() == EYsonItemType::BeginList) {
+ cursor->Next();
+ } else if (cursor->GetCurrent().GetType() == EYsonItemType::EntityValue) {
+ throwValueExpectedToBeNonempty();
+ } else {
+ ThrowBadYsonToken(
+ Descriptor_,
+ {EYsonItemType::BeginList},
+ cursor->GetCurrent().GetType());
+ }
+ }
+
+ for (; outerOptionalsFound < OuterTranslateLevel_; ++outerOptionalsFound) {
+ if (cursor->GetCurrent().GetType() == EYsonItemType::BeginList) {
+ writer->WriteVariant8Tag(1);
+ cursor->Next();
+ } else if (cursor->GetCurrent().GetType() == EYsonItemType::EntityValue) {
+ writer->WriteVariant8Tag(0);
+ cursor->Next();
+ goto skip_end_list_tokens;
+ } else {
+ ThrowBadYsonToken(
+ Descriptor_,
+ {EYsonItemType::BeginList, EYsonItemType::EntityValue},
+ cursor->GetCurrent().GetType());
+ }
+ }
+
+ if (cursor->GetCurrent().GetType() == EYsonItemType::EntityValue) {
+ if (InnerOptionalTranslate_) {
+ writer->WriteVariant8Tag(0);
+ } else {
+ throwValueExpectedToBeNonempty();
+ }
+ cursor->Next();
+ } else {
+ if (InnerOptionalTranslate_) {
+ writer->WriteVariant8Tag(1);
+ }
+ InnerConverter_(cursor, writer);
+ }
+
+skip_end_list_tokens:
+ for (int i = 0; i < outerOptionalsFound; ++i) {
+ if (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ ThrowBadYsonToken(Descriptor_, {EYsonItemType::EndList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ }
+ }
+
+private:
+ const TYsonToSkiffConverter InnerConverter_;
+ const TComplexTypeFieldDescriptor Descriptor_;
+
+ // Max level of outer yson optional we expect to be filled.
+ const int OuterExpectFilledLevel_;
+ // Max level of outer optional we want to translate to yson.
+ const int OuterTranslateLevel_;
+
+ // If true we translate inner yson optional into Skiff optional
+ // if false we expect inner yson optional to be filled.
+ const bool InnerOptionalTranslate_;
+};
+
+class TOptionalNullYsonToSkiffConverterImpl
+{
+public:
+ TOptionalNullYsonToSkiffConverterImpl(
+ TComplexTypeFieldDescriptor descriptor,
+ int ysonOptionalLevel,
+ int skiffOptionalLevel)
+ : Descriptor_(std::move(descriptor))
+ , OuterExpectFilledLevel_(ysonOptionalLevel - skiffOptionalLevel)
+ , OuterTranslateLevel_(ysonOptionalLevel)
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer)
+ {
+ auto throwValueExpectedToBeNonempty = [&] {
+ ThrowYsonToSkiffConversionError(Descriptor_, "\"#\" found while value expected to be nonempty");
+ };
+
+ int outerOptionalsFound = 0;
+ for (; outerOptionalsFound < OuterExpectFilledLevel_; ++outerOptionalsFound) {
+ if (cursor->GetCurrent().GetType() == EYsonItemType::BeginList) {
+ cursor->Next();
+ } else if (cursor->GetCurrent().GetType() == EYsonItemType::EntityValue) {
+ throwValueExpectedToBeNonempty();
+ } else {
+ ThrowBadYsonToken(
+ Descriptor_,
+ {EYsonItemType::BeginList},
+ cursor->GetCurrent().GetType());
+ }
+ }
+
+ for (; outerOptionalsFound < OuterTranslateLevel_; ++outerOptionalsFound) {
+ if (cursor->GetCurrent().GetType() == EYsonItemType::BeginList) {
+ writer->WriteVariant8Tag(1);
+ cursor->Next();
+ } else if (cursor->GetCurrent().GetType() == EYsonItemType::EntityValue) {
+ writer->WriteVariant8Tag(0);
+ cursor->Next();
+ goto skip_end_list_tokens;
+ } else {
+ ThrowBadYsonToken(
+ Descriptor_,
+ {EYsonItemType::BeginList, EYsonItemType::EntityValue},
+ cursor->GetCurrent().GetType());
+ }
+ }
+
+ if (cursor->GetCurrent().GetType() != EYsonItemType::EntityValue) {
+ ThrowBadYsonToken(
+ Descriptor_,
+ {EYsonItemType::EntityValue},
+ cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+
+skip_end_list_tokens:
+ for (int i = 0; i < outerOptionalsFound; ++i) {
+ if (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ ThrowBadYsonToken(Descriptor_, {EYsonItemType::EndList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ }
+ }
+
+private:
+ const TComplexTypeFieldDescriptor Descriptor_;
+
+ // How many levels of yson optional we expect to be filled.
+ const int OuterExpectFilledLevel_;
+ // How many levels of outer optional we want to translate to yson
+ const int OuterTranslateLevel_;
+};
+
+TYsonToSkiffConverter CreateOptionalYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config)
+{
+ const bool allowOmitOptional = config.AllowOmitTopLevelOptional && context.NestingLevel == 0;
+ auto match = MatchOptionalTypes(
+ descriptor,
+ skiffSchema,
+ allowOmitOptional);
+
+ if (match.InnerTypes.first.GetType()->IsNullable()) {
+ return TOptionalNullYsonToSkiffConverterImpl(
+ descriptor,
+ match.LogicalNesting,
+ match.SkiffNesting);
+ } else {
+ auto innerConverter = CreateYsonToSkiffConverterImpl(
+ std::move(match.InnerTypes.first),
+ match.InnerTypes.second,
+ context,
+ config);
+
+ return TOptionalYsonToSkiffConverterImpl(
+ innerConverter,
+ std::move(descriptor),
+ match.LogicalNesting,
+ match.SkiffNesting);
+ }
+}
+
+TYsonToSkiffConverter CreateListYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config)
+{
+ auto match = MatchListTypes(descriptor, skiffSchema);
+ auto innerConverter = CreateYsonToSkiffConverterImpl(
+ std::move(match.first),
+ match.second,
+ context,
+ config);
+ return [innerConverter = innerConverter, descriptor = std::move(descriptor)] (
+ TYsonPullParserCursor* cursor,
+ TCheckedInDebugSkiffWriter* writer)
+ {
+ if (cursor->GetCurrent().GetType() != EYsonItemType::BeginList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::BeginList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+
+ while (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ writer->WriteVariant8Tag(0);
+ innerConverter(cursor, writer);
+ }
+ writer->WriteVariant8Tag(EndOfSequenceTag<ui8>());
+ cursor->Next();
+ };
+}
+
+class TInfiniteEntity
+{
+public:
+ TInfiniteEntity()
+ : Stream_(TStringBuf("#;#;#;#;#;#;#;#;"))
+ , Parser_(&Stream_, EYsonType::ListFragment)
+ , Cursor_(&Parser_)
+ {
+ YT_VERIFY(Cursor_.TryConsumeFragmentStart());
+ }
+
+ TYsonPullParserCursor* GetCursor()
+ {
+ return &Cursor_;
+ }
+
+private:
+ class TRingBufferStream
+ : public IZeroCopyInput
+ {
+ public:
+ explicit TRingBufferStream(TStringBuf buffer)
+ : Buffer_(buffer)
+ , Pointer_(Buffer_.data())
+ { }
+
+ private:
+ size_t DoNext(const void** ptr, size_t len) override
+ {
+ const auto end = Buffer_.data() + Buffer_.size();
+ auto result = Min<size_t>(len, end - Pointer_);
+ *ptr = Pointer_;
+ Pointer_ += result;
+ if (Pointer_ == end) {
+ Pointer_ = Buffer_.data();
+ }
+ return result;
+ }
+
+ private:
+ const TStringBuf Buffer_;
+ const char* Pointer_;
+ };
+
+private:
+ TRingBufferStream Stream_;
+ TYsonPullParser Parser_;
+ TYsonPullParserCursor Cursor_;
+};
+
+TYsonToSkiffConverter CreateStructYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config)
+{
+ TYsonToSkiffConverter skipYsonValue = [](TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* /*writer*/) {
+ cursor->SkipComplexValue();
+ };
+
+ TYsonToSkiffConverter writeNullUnknownField = [](TYsonPullParserCursor*, TCheckedInDebugSkiffWriter* writer) {
+ writer->WriteVariant8Tag(0);
+ };
+
+ auto fieldMatchList = MatchStructTypes(descriptor, skiffSchema, /*allowUnknownSkiffFields*/ true);
+ std::vector<TYsonToSkiffConverter> converterList;
+ for (const auto& match : fieldMatchList) {
+ if (match) {
+ auto [fieldDescriptor, fieldSkiffSchema] = *match;
+ if (fieldSkiffSchema) {
+ auto converter = CreateYsonToSkiffConverterImpl(fieldDescriptor, fieldSkiffSchema, context, config);
+ converterList.emplace_back(converter);
+ } else {
+ converterList.emplace_back(skipYsonValue);
+ }
+ } else {
+ converterList.emplace_back(writeNullUnknownField);
+ }
+ }
+
+ return [converterList = std::move(converterList), descriptor = std::move(descriptor)]
+ (TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer) {
+ if (cursor->GetCurrent().GetType() != EYsonItemType::BeginList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::BeginList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ for (auto it = converterList.begin(), end = converterList.end(); it != end; ++it) {
+ if (cursor->GetCurrent().GetType() == EYsonItemType::EndList) {
+ TInfiniteEntity infiniteEntity;
+ auto entityCursor = infiniteEntity.GetCursor();
+ do {
+ const auto& converter = *it;
+ converter(entityCursor, writer);
+ ++it;
+ } while (it != end);
+ break;
+ }
+ const auto& converter = *it;
+ converter(cursor, writer);
+ }
+
+ if (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::EndList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ };
+}
+
+TYsonToSkiffConverter CreateTupleYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config)
+{
+ TYsonToSkiffConverter skipYsonValue = [](TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* /*writer*/) {
+ cursor->SkipComplexValue();
+ };
+
+ auto tupleMatch = MatchTupleTypes(descriptor, skiffSchema);
+ const auto& children = skiffSchema->GetChildren();
+ std::vector<TYsonToSkiffConverter> converterList;
+ for (int i = 0; i < std::ssize(tupleMatch); ++i) {
+ if (children[i]->GetWireType() == EWireType::Nothing) {
+ converterList.emplace_back(skipYsonValue);
+ } else {
+ const auto&[descriptor, skiffSchema] = tupleMatch[i];
+ auto converter = CreateYsonToSkiffConverterImpl(descriptor, skiffSchema, context, config);
+ converterList.emplace_back(converter);
+ }
+ }
+
+ return [converterList = std::move(converterList), descriptor = std::move(descriptor)]
+ (TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer) {
+ if (cursor->GetCurrent().GetType() != EYsonItemType::BeginList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::BeginList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ for (const auto& converter : converterList) {
+ converter(cursor, writer);
+ }
+
+ if (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::EndList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ };
+}
+
+template <EWireType wireType>
+class TVariantYsonToSkiffConverterImpl
+{
+public:
+ TVariantYsonToSkiffConverterImpl(std::vector<TYsonToSkiffConverter> converterList, TComplexTypeFieldDescriptor descriptor)
+ : ConverterList_(std::move(converterList))
+ , Descriptor_(std::move(descriptor))
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer)
+ {
+ if (cursor->GetCurrent().GetType() != EYsonItemType::BeginList) {
+ ThrowBadYsonToken(Descriptor_, {EYsonItemType::BeginList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ if (cursor->GetCurrent().GetType() != EYsonItemType::Int64Value) {
+ ThrowBadYsonToken(Descriptor_, {EYsonItemType::Int64Value}, cursor->GetCurrent().GetType());
+ }
+ auto tag = cursor->GetCurrent().UncheckedAsInt64();
+ cursor->Next();
+ if (tag >= std::ssize(ConverterList_)) {
+ ThrowYsonToSkiffConversionError(Descriptor_, "variant tag (%v) exceeds %v children count (%v)",
+ tag,
+ wireType,
+ ConverterList_.size());
+ }
+ if constexpr (wireType == EWireType::Variant8) {
+ writer->WriteVariant8Tag(tag);
+ } else {
+ static_assert(wireType == EWireType::Variant16);
+ writer->WriteVariant16Tag(tag);
+ }
+ ConverterList_[tag](cursor, writer);
+ if (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ ThrowBadYsonToken(Descriptor_, {EYsonItemType::EndList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ }
+
+private:
+ const std::vector<TYsonToSkiffConverter> ConverterList_;
+ const TComplexTypeFieldDescriptor Descriptor_;
+};
+
+TYsonToSkiffConverter CreateVariantYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config)
+{
+ std::vector<TTypePair> variantMatch;
+ if (descriptor.GetType()->GetMetatype() == ELogicalMetatype::VariantStruct) {
+ variantMatch = MatchVariantStructTypes(descriptor, skiffSchema);
+ } else {
+ YT_VERIFY(descriptor.GetType()->GetMetatype() == ELogicalMetatype::VariantTuple);
+ variantMatch = MatchVariantTupleTypes(descriptor, skiffSchema);
+ }
+
+ std::vector<TYsonToSkiffConverter> converterList;
+ for (const auto&[descriptor, skiffSchema] : variantMatch) {
+ auto converter = CreateYsonToSkiffConverterImpl(descriptor, skiffSchema, context, config);
+ converterList.emplace_back(converter);
+ }
+
+ if (skiffSchema->GetWireType() == EWireType::Variant8) {
+ return TVariantYsonToSkiffConverterImpl<EWireType::Variant8>(std::move(converterList), std::move(descriptor));
+ } else if (skiffSchema->GetWireType() == EWireType::Variant16) {
+ return TVariantYsonToSkiffConverterImpl<EWireType::Variant16>(std::move(converterList), std::move(descriptor));
+ }
+ YT_ABORT();
+}
+
+TYsonToSkiffConverter CreateDictYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config)
+{
+ const auto [keyMatch, valueMatch] = MatchDictTypes(descriptor, skiffSchema);
+ auto keyConverter = CreateYsonToSkiffConverterImpl(keyMatch.first, keyMatch.second, context, config);
+ auto valueConverter = CreateYsonToSkiffConverterImpl(valueMatch.first, valueMatch.second, context, config);
+
+ return [
+ keyConverter = std::move(keyConverter),
+ valueConverter = std::move(valueConverter),
+ descriptor = std::move(descriptor)
+ ] (TYsonPullParserCursor* cursor, TCheckedInDebugSkiffWriter* writer) {
+ if (cursor->GetCurrent().GetType() != EYsonItemType::BeginList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::BeginList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ while (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ writer->WriteVariant8Tag(0);
+ if (cursor->GetCurrent().GetType() != EYsonItemType::BeginList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::BeginList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ keyConverter(cursor, writer);
+ valueConverter(cursor, writer);
+ if (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ ThrowBadYsonToken(descriptor, {EYsonItemType::EndList}, cursor->GetCurrent().GetType());
+ }
+ cursor->Next();
+ }
+ writer->WriteVariant8Tag(EndOfSequenceTag<ui8>());
+ cursor->Next(); // Skip EYsonItemType::EndList.
+ };
+}
+
+TYsonToSkiffConverter CreateYsonToSkiffConverterImpl(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TYsonToSkiffConverterConfig& config)
+{
+ TConverterCreationContext innerContext = context;
+ ++innerContext.NestingLevel;
+ const auto& logicalType = descriptor.GetType();
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return CreateSimpleYsonToSkiffConverter(std::move(descriptor), skiffSchema);
+ case ELogicalMetatype::Decimal:
+ return CreateDecimalYsonToSkiffConverter(std::move(descriptor), skiffSchema);
+ case ELogicalMetatype::Optional:
+ return CreateOptionalYsonToSkiffConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::List:
+ return CreateListYsonToSkiffConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::Struct:
+ return CreateStructYsonToSkiffConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::Tuple:
+ return CreateTupleYsonToSkiffConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::VariantTuple:
+ case ELogicalMetatype::VariantStruct:
+ return CreateVariantYsonToSkiffConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::Dict:
+ return CreateDictYsonToSkiffConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::Tagged:
+ // We have detagged our type previously.
+ YT_ABORT();
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TFunction>
+class TPrimitiveTypeSkiffToYsonConverter
+{
+public:
+ explicit TPrimitiveTypeSkiffToYsonConverter(TFunction function = {})
+ : Function_(std::move(function))
+ { }
+
+ void operator() (TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer)
+ {
+ auto value = Function_(parser);
+
+ using TValueType = std::decay_t<decltype(value)>;
+
+ if constexpr (std::is_same_v<TValueType, TStringBuf>) {
+ writer->WriteBinaryString(value);
+ } else if constexpr (
+ std::is_same_v<TValueType, i8> ||
+ std::is_same_v<TValueType, i16> ||
+ std::is_same_v<TValueType, i32> ||
+ std::is_same_v<TValueType, i64>)
+ {
+ writer->WriteBinaryInt64(value);
+ } else if constexpr (
+ std::is_same_v<TValueType, ui8> ||
+ std::is_same_v<TValueType, ui16> ||
+ std::is_same_v<TValueType, ui32> ||
+ std::is_same_v<TValueType, ui64>)
+ {
+ writer->WriteBinaryUint64(value);
+ } else if constexpr (std::is_same_v<TValueType, bool>) {
+ writer->WriteBinaryBoolean(value);
+ } else if constexpr (std::is_same_v<TValueType, double>) {
+ writer->WriteBinaryDouble(value);
+ } else if constexpr (std::is_same_v<TValueType, std::nullptr_t>) {
+ writer->WriteEntity();
+ } else {
+ static_assert(std::is_same_v<TValueType, TStringBuf>);
+ }
+ }
+
+private:
+ TFunction Function_;
+};
+
+class TYson32SkiffToYsonConverter
+{
+public:
+ Y_FORCE_INLINE void operator () (TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer)
+ {
+ TMemoryInput inputStream(parser->ParseYson32());
+ TYsonPullParser pullParser(&inputStream, EYsonType::Node);
+ TYsonPullParserCursor(&pullParser).TransferComplexValue(writer);
+ }
+};
+
+TSkiffToYsonConverter CreatePrimitiveTypeSkiffToYsonConverter(EWireType wireType)
+{
+ switch (wireType) {
+#define CASE(x) \
+ case x: \
+ return TPrimitiveTypeSkiffToYsonConverter(TSimpleSkiffParser<x>());
+ CASE(EWireType::Int8)
+ CASE(EWireType::Int16)
+ CASE(EWireType::Int32)
+ CASE(EWireType::Int64)
+
+ CASE(EWireType::Uint8)
+ CASE(EWireType::Uint16)
+ CASE(EWireType::Uint32)
+ CASE(EWireType::Uint64)
+
+ CASE(EWireType::Boolean)
+ CASE(EWireType::Double)
+ CASE(EWireType::String32)
+ CASE(EWireType::Nothing)
+#undef CASE
+ case EWireType::Yson32:
+ return TYson32SkiffToYsonConverter();
+ default:
+ YT_ABORT();
+ }
+}
+
+TSkiffToYsonConverter CreateSimpleSkiffToYsonConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& /*context*/,
+ const TSkiffToYsonConverterConfig& /*config*/)
+{
+ try {
+ const auto& logicalType = descriptor.GetType()->AsSimpleTypeRef();
+ auto valueType = logicalType.GetElement();
+ auto wireType = skiffSchema->GetWireType();
+ switch (valueType) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+
+ case ESimpleLogicalValueType::Interval:
+ CheckWireType(wireType, {EWireType::Int8, EWireType::Int16, EWireType::Int32, EWireType::Int64});
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ CheckWireType(wireType, {EWireType::Uint8, EWireType::Uint16, EWireType::Uint32, EWireType::Uint64});
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+
+ case ESimpleLogicalValueType::Boolean:
+ CheckWireType(wireType, {EWireType::Boolean});
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+
+ case ESimpleLogicalValueType::Float:
+ case ESimpleLogicalValueType::Double:
+ CheckWireType(wireType, {EWireType::Double});
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Utf8:
+ case ESimpleLogicalValueType::Json:
+ CheckWireType(wireType, {EWireType::String32});
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+
+ case ESimpleLogicalValueType::Any:
+ CheckWireType(wireType, {EWireType::Yson32});
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ CheckWireType(wireType, {EWireType::Nothing});
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+
+ case ESimpleLogicalValueType::Uuid:
+ CheckWireType(wireType, {EWireType::Uint128, EWireType::String32});
+ if (wireType == EWireType::Uint128) {
+ return TPrimitiveTypeSkiffToYsonConverter(TUuidParser());
+ } else {
+ return CreatePrimitiveTypeSkiffToYsonConverter(wireType);
+ }
+ }
+ YT_ABORT();
+ } catch (const std::exception& ex) {
+ RethrowCannotMatchField(descriptor, skiffSchema, ex);
+ }
+}
+
+class TOptionalSkiffToYsonConverterImpl
+{
+public:
+ TOptionalSkiffToYsonConverterImpl(
+ TSkiffToYsonConverter innerConverter,
+ TComplexTypeFieldDescriptor descriptor,
+ int ysonNesting,
+ int skiffNesting)
+ : InnerConverter_(std::move(innerConverter))
+ , Descriptor_(std::move(descriptor))
+ , OuterFill_(ysonNesting > 1 ? ysonNesting - skiffNesting : 0)
+ , OuterTranslate_(ysonNesting - OuterFill_ - 1)
+ , InnerTranslate_(skiffNesting > 0)
+ {
+ YT_VERIFY(skiffNesting >= 0);
+ YT_VERIFY(ysonNesting > 0);
+
+ YT_VERIFY(skiffNesting <= ysonNesting);
+ YT_VERIFY(ysonNesting <= skiffNesting + 1);
+ }
+
+ void operator () (TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer)
+ {
+ for (int i = 0; i < OuterFill_; ++i) {
+ writer->WriteBeginList();
+ }
+
+ int outerOptionalsFilled = 0;
+ for (; outerOptionalsFilled < OuterTranslate_; ++outerOptionalsFilled) {
+ auto tag = parser->ParseVariant8Tag();
+ if (tag == 0) {
+ writer->WriteEntity();
+ goto write_list_ends;
+ } else if (tag == 1) {
+ writer->WriteBeginList();
+ } else {
+ ThrowUnexpectedVariant8Tag(tag);
+ }
+ }
+
+ if (InnerTranslate_) {
+ auto tag = parser->ParseVariant8Tag();
+ if (tag == 0) {
+ writer->WriteEntity();
+ goto write_list_ends;
+ } else if (tag != 1) {
+ ThrowUnexpectedVariant8Tag(tag);
+ }
+ }
+ InnerConverter_(parser, writer);
+
+write_list_ends:
+ const int toClose = outerOptionalsFilled + OuterFill_;
+ for (int i = 0; i < toClose; ++i) {
+ writer->WriteEndList();
+ }
+ }
+
+private:
+ void ThrowUnexpectedVariant8Tag(ui8 tag) const
+ {
+ ThrowSkiffToYsonConversionError(Descriptor_, "Unexpected %lv tag, expected %Qv or %Qv got %Qv",
+ EWireType::Variant8,
+ 0,
+ 1,
+ tag);
+ }
+
+private:
+ const TSkiffToYsonConverter InnerConverter_;
+ const TComplexTypeFieldDescriptor Descriptor_;
+
+ // How many levels of yson optional we set unconditionally.
+ const int OuterFill_;
+
+ // How many levels of Skiff optionals we translate to yson outer optionals (which are encoded as list).
+ const int OuterTranslate_;
+
+ const bool InnerTranslate_;
+};
+
+class TOptionalNullSkiffToYsonConverterImpl
+{
+public:
+ TOptionalNullSkiffToYsonConverterImpl(
+ TComplexTypeFieldDescriptor descriptor,
+ int ysonNesting,
+ int skiffNesting)
+ : Descriptor_(std::move(descriptor))
+ , OuterFill_(ysonNesting > 1 ? ysonNesting - skiffNesting : 0)
+ , OuterTranslate_(ysonNesting - OuterFill_)
+ {
+ YT_VERIFY(skiffNesting >= 0);
+ YT_VERIFY(ysonNesting > 0);
+
+ YT_VERIFY(skiffNesting <= ysonNesting);
+ YT_VERIFY(ysonNesting <= skiffNesting + 1);
+ }
+
+ void operator () (TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer)
+ {
+ for (int i = 0; i < OuterFill_; ++i) {
+ writer->WriteBeginList();
+ }
+
+ int outerOptionalsFilled = 0;
+ for (; outerOptionalsFilled < OuterTranslate_; ++outerOptionalsFilled) {
+ auto tag = parser->ParseVariant8Tag();
+ if (tag == 0) {
+ writer->WriteEntity();
+ goto write_list_ends;
+ } else if (tag == 1) {
+ writer->WriteBeginList();
+ } else {
+ ThrowUnexpectedVariant8Tag(tag);
+ }
+ }
+ writer->WriteEntity();
+
+write_list_ends:
+ const int toClose = outerOptionalsFilled + OuterFill_;
+ for (int i = 0; i < toClose; ++i) {
+ writer->WriteEndList();
+ }
+ }
+
+private:
+ void ThrowUnexpectedVariant8Tag(ui8 tag) const
+ {
+ ThrowSkiffToYsonConversionError(Descriptor_, "Unexpected %lv tag, expected %Qv or %Qv got %Qv",
+ EWireType::Variant8,
+ 0,
+ 1,
+ tag);
+ }
+
+private:
+ const TComplexTypeFieldDescriptor Descriptor_;
+
+ // How many levels of yson optional we set unconditionally.
+ const int OuterFill_;
+
+ // How many levels of Skiff optionals we translate to yson outer optionals (which are encoded as list).
+ const int OuterTranslate_;
+};
+
+TSkiffToYsonConverter CreateOptionalSkiffToYsonConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config)
+{
+ const bool allowOmitOptional = config.AllowOmitTopLevelOptional && context.NestingLevel == 0;
+ auto match = MatchOptionalTypes(descriptor, skiffSchema, allowOmitOptional);
+ if (match.LogicalNesting != match.SkiffNesting) {
+ if (!config.AllowOmitTopLevelOptional || context.NestingLevel > 0) {
+ RethrowCannotMatchField(descriptor, skiffSchema, TErrorException()
+ <<= TError("Optional nesting mismatch"));
+ }
+ }
+
+ if (match.InnerTypes.first.GetType()->IsNullable()) {
+ return TOptionalNullSkiffToYsonConverterImpl(std::move(descriptor), match.LogicalNesting, match.SkiffNesting);
+ } else {
+ auto innerConverter = CreateSkiffToYsonConverterImpl(
+ std::move(match.InnerTypes.first),
+ match.InnerTypes.second,
+ context,
+ config);
+ return TOptionalSkiffToYsonConverterImpl(
+ innerConverter, std::move(descriptor), match.LogicalNesting, match.SkiffNesting);
+ }
+}
+
+TSkiffToYsonConverter CreateListSkiffToYsonConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config)
+{
+ auto match = MatchListTypes(descriptor, skiffSchema);
+ auto innerConverter = CreateSkiffToYsonConverterImpl(std::move(match.first), match.second, context, config);
+
+ return [innerConverter = innerConverter, descriptor=std::move(descriptor)](TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer) {
+ writer->WriteBeginList();
+ while (true) {
+ auto tag = parser->ParseVariant8Tag();
+ if (tag == EndOfSequenceTag<ui8>()) {
+ break;
+ } else if (tag != 0) {
+ ThrowSkiffToYsonConversionError(descriptor, "Unexpected %lv tag, expected %Qv or %Qv got %Qv",
+ EWireType::RepeatedVariant8,
+ 0,
+ EndOfSequenceTag<ui8>(),
+ tag);
+ }
+ innerConverter(parser, writer);
+ writer->WriteItemSeparator();
+ }
+ writer->WriteEndList();
+ };
+}
+
+TSkiffToYsonConverter CreateStructSkiffToYsonConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config)
+{
+ const auto insertEntity = [](TCheckedInDebugSkiffParser* /*parser*/, TCheckedInDebugYsonTokenWriter* writer) {
+ writer->WriteEntity();
+ };
+
+ auto structMatch = MatchStructTypes(descriptor, skiffSchema, /*allowUnknownSkiffFields*/false);
+ std::vector<TSkiffToYsonConverter> converterList;
+ for (const auto& match : structMatch) {
+ const auto& [fieldDescriptor, fieldSkiffSchema] = *match;
+ if (fieldSkiffSchema) {
+ converterList.emplace_back(CreateSkiffToYsonConverterImpl(fieldDescriptor, fieldSkiffSchema, context, config));
+ } else if (fieldDescriptor.GetType()->GetMetatype() == ELogicalMetatype::Optional) {
+ converterList.emplace_back(insertEntity);
+ } else {
+ RethrowCannotMatchField(
+ descriptor,
+ skiffSchema,
+ TErrorException() <<= TError(
+ "Non optional struct field %Qv is missing in Skiff schema",
+ fieldDescriptor.GetDescription()));
+ }
+ }
+
+ return [converterList = converterList](TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer) {
+ writer->WriteBeginList();
+ for (const auto& converter : converterList) {
+ converter(parser, writer);
+ writer->WriteItemSeparator();
+ }
+ writer->WriteEndList();
+ };
+}
+
+TSkiffToYsonConverter CreateTupleSkiffToYsonConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config)
+{
+ auto tupleMatch = MatchTupleTypes(descriptor, skiffSchema);
+ std::vector<TSkiffToYsonConverter> converterList;
+ for (const auto& [fieldDescriptor, fieldSkiffSchema] : tupleMatch) {
+ converterList.emplace_back(CreateSkiffToYsonConverterImpl(fieldDescriptor, fieldSkiffSchema, context, config));
+ }
+ return [converterList = converterList](TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer) {
+ writer->WriteBeginList();
+ for (const auto& converter : converterList) {
+ converter(parser, writer);
+ writer->WriteItemSeparator();
+ }
+ writer->WriteEndList();
+ };
+}
+
+template <EWireType wireType>
+class TVariantSkiffToYsonConverterImpl
+{
+public:
+ TVariantSkiffToYsonConverterImpl(std::vector<TSkiffToYsonConverter> converterList, TComplexTypeFieldDescriptor descriptor)
+ : ConverterList_(std::move(converterList))
+ , Descriptor_(std::move(descriptor))
+ { }
+
+ void operator () (TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer)
+ {
+ int tag;
+ if constexpr (wireType == EWireType::Variant8) {
+ tag = parser->ParseVariant8Tag();
+ } else {
+ static_assert(wireType == EWireType::Variant16);
+ tag = parser->ParseVariant16Tag();
+ }
+
+ if (tag >= std::ssize(ConverterList_)) {
+ ThrowSkiffToYsonConversionError(Descriptor_, "Variant tag (%v) exceeds %v children count (%v)",
+ tag,
+ wireType,
+ ConverterList_.size());
+ }
+ writer->WriteBeginList();
+ writer->WriteBinaryInt64(tag);
+ writer->WriteItemSeparator();
+ ConverterList_[tag](parser, writer);
+ writer->WriteItemSeparator();
+ writer->WriteEndList();
+ }
+
+private:
+ const std::vector<TSkiffToYsonConverter> ConverterList_;
+ const TComplexTypeFieldDescriptor Descriptor_;
+};
+
+TSkiffToYsonConverter CreateVariantSkiffToYsonConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config)
+{
+ std::vector<TTypePair> variantMatch;
+ if (descriptor.GetType()->GetMetatype() == ELogicalMetatype::VariantStruct) {
+ variantMatch = MatchVariantStructTypes(descriptor, skiffSchema);
+ } else {
+ YT_VERIFY(descriptor.GetType()->GetMetatype() == ELogicalMetatype::VariantTuple);
+ variantMatch = MatchVariantTupleTypes(descriptor, skiffSchema);
+ }
+
+ std::vector<TSkiffToYsonConverter> converterList;
+ for (const auto& [fieldDescriptor, fieldSkiffSchema] : variantMatch) {
+ converterList.emplace_back(CreateSkiffToYsonConverterImpl(fieldDescriptor, fieldSkiffSchema, context, config));
+ }
+
+ if (skiffSchema->GetWireType() == EWireType::Variant8) {
+ return TVariantSkiffToYsonConverterImpl<EWireType::Variant8>(std::move(converterList), std::move(descriptor));
+ } else {
+ YT_VERIFY(skiffSchema->GetWireType() == EWireType::Variant16);
+ return TVariantSkiffToYsonConverterImpl<EWireType::Variant16>(std::move(converterList), std::move(descriptor));
+ }
+}
+
+TSkiffToYsonConverter CreateDictSkiffToYsonConverter(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config)
+{
+ auto [keyMatch, valueMatch] = MatchDictTypes(descriptor, skiffSchema);
+ auto keyConverter = CreateSkiffToYsonConverterImpl(std::move(keyMatch.first), keyMatch.second, context, config);
+ auto valueConverter = CreateSkiffToYsonConverterImpl(std::move(valueMatch.first), valueMatch.second, context, config);
+
+ return [
+ keyConverter = std::move(keyConverter),
+ valueConverter = std::move(valueConverter),
+ descriptor=std::move(descriptor)
+ ] (TCheckedInDebugSkiffParser* parser, TCheckedInDebugYsonTokenWriter* writer) {
+ writer->WriteBeginList();
+ while (true) {
+ auto tag = parser->ParseVariant8Tag();
+ if (tag == EndOfSequenceTag<ui8>()) {
+ break;
+ } else if (tag != 0) {
+ ThrowSkiffToYsonConversionError(descriptor, "Unexpected %lv tag, expected %Qv or %Qv got %Qv",
+ EWireType::RepeatedVariant8,
+ 0,
+ EndOfSequenceTag<ui8>(),
+ tag);
+ }
+ writer->WriteBeginList();
+ {
+ keyConverter(parser, writer);
+ writer->WriteItemSeparator();
+ valueConverter(parser, writer);
+ writer->WriteItemSeparator();
+ }
+ writer->WriteEndList();
+ writer->WriteItemSeparator();
+ }
+ writer->WriteEndList();
+ };
+}
+
+TSkiffToYsonConverter CreateDecimalSkiffToYsonConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema)
+{
+ const auto& logicalType = descriptor.GetType();
+ int precision = logicalType->AsDecimalTypeRef().GetPrecision();
+ auto wireType = skiffSchema->GetWireType();
+ switch (wireType) {
+ case EWireType::Int32:
+ return TPrimitiveTypeSkiffToYsonConverter(TDecimalSkiffParser<EWireType::Int32>(precision));
+ case EWireType::Int64:
+ return TPrimitiveTypeSkiffToYsonConverter(TDecimalSkiffParser<EWireType::Int64>(precision));
+ case EWireType::Int128:
+ return TPrimitiveTypeSkiffToYsonConverter(TDecimalSkiffParser<EWireType::Int128>(precision));
+ default:
+ CheckSkiffWireTypeForDecimal(precision, wireType);
+ // Previous call must throw
+ YT_ABORT();
+ }
+}
+
+TSkiffToYsonConverter CreateSkiffToYsonConverterImpl(
+ TComplexTypeFieldDescriptor descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TConverterCreationContext& context,
+ const TSkiffToYsonConverterConfig& config)
+{
+ TConverterCreationContext innerContext = context;
+ ++innerContext.NestingLevel;
+ const auto& logicalType = descriptor.GetType();
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return CreateSimpleSkiffToYsonConverter(descriptor, skiffSchema, innerContext, config);
+ case ELogicalMetatype::Decimal:
+ return CreateDecimalSkiffToYsonConverter(descriptor, skiffSchema);
+ case ELogicalMetatype::Optional:
+ return CreateOptionalSkiffToYsonConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::List:
+ return CreateListSkiffToYsonConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::Struct:
+ return CreateStructSkiffToYsonConverter(descriptor, skiffSchema, innerContext, config);
+ case ELogicalMetatype::Tuple:
+ return CreateTupleSkiffToYsonConverter(descriptor, skiffSchema, innerContext, config);
+ case ELogicalMetatype::VariantStruct:
+ case ELogicalMetatype::VariantTuple:
+ return CreateVariantSkiffToYsonConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::Dict:
+ return CreateDictSkiffToYsonConverter(std::move(descriptor), skiffSchema, innerContext, config);
+ case ELogicalMetatype::Tagged:
+ // We have detagged our type previously.
+ break;
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYsonToSkiffConverter CreateYsonToSkiffConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TYsonToSkiffConverterConfig& config)
+{
+ TConverterCreationContext context;
+ // CreateYsonToSkiffConverterImpl will increment NestingLevel to 0 for the top level element.
+ context.NestingLevel = -1;
+ return CreateYsonToSkiffConverterImpl(descriptor.Detag(), skiffSchema, context, config);
+}
+
+TSkiffToYsonConverter CreateSkiffToYsonConverter(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TSkiffToYsonConverterConfig& config)
+{
+ TConverterCreationContext context;
+ context.NestingLevel = -1;
+ return CreateSkiffToYsonConverterImpl(descriptor.Detag(), skiffSchema, context, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CheckSkiffWireTypeForDecimal(int precision, NSkiff::EWireType wireType)
+{
+ using namespace NSkiff;
+
+ const auto decimalBinarySize = NDecimal::TDecimal::GetValueBinarySize(precision);
+ int skiffBinarySize = 0;
+ if (wireType == NSkiff::EWireType::Int32) {
+ skiffBinarySize = sizeof(i32);
+ } else if (wireType == NSkiff::EWireType::Int64) {
+ skiffBinarySize = sizeof(i64);
+ } else if (wireType == NSkiff::EWireType::Int128) {
+ skiffBinarySize = 2 * sizeof(i64);
+ }
+
+ if (decimalBinarySize != skiffBinarySize) {
+ THROW_ERROR_EXCEPTION("Skiff type %v cannot represent type Decimal<%v, ?>",
+ wireType,
+ precision);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CheckWireType(EWireType wireType, const std::initializer_list<EWireType>& allowed)
+{
+ if (std::find(allowed.begin(), allowed.end(), wireType) == allowed.end()) {
+ THROW_ERROR_EXCEPTION("Unexpected wire type %Qlv",
+ wireType);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/skiff_yson_converter.h b/yt/yt/client/formats/skiff_yson_converter.h
new file mode 100644
index 0000000000..233b106729
--- /dev/null
+++ b/yt/yt/client/formats/skiff_yson_converter.h
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <yt/yt/client/table_client/public.h>
+#include <yt/yt/client/table_client/row_base.h>
+
+#include <yt/yt/library/decimal/decimal.h>
+#include <yt/yt/core/yson/public.h>
+
+#include <library/cpp/skiff/skiff.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TYsonToSkiffConverter = std::function<void(NYson::TYsonPullParserCursor*, NSkiff::TCheckedInDebugSkiffWriter*)>;
+using TSkiffToYsonConverter = std::function<void(NSkiff::TCheckedInDebugSkiffParser*, NYson::TCheckedInDebugYsonTokenWriter*)>;
+
+struct TYsonToSkiffConverterConfig
+{
+ // Usually skiffSchema MUST match descriptor.LogicalType.
+ // But when AllowOmitTopLevelOptional is set to true and descriptor.LogicalType is Optional<SomeInnerType>
+ // skiffSchema CAN match SomeInnerType. In that case returned converter will throw error when it gets empty value.
+ //
+ // Useful for sparse fields.
+ bool AllowOmitTopLevelOptional = false;
+};
+
+TYsonToSkiffConverter CreateYsonToSkiffConverter(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<NSkiff::TSkiffSchema>& skiffSchema,
+ const TYsonToSkiffConverterConfig& config = {});
+
+struct TSkiffToYsonConverterConfig
+{
+ // Similar to TYsonToSkiffConverterConfig::AllowOmitTopLevelOptional.
+ bool AllowOmitTopLevelOptional = false;
+};
+
+TSkiffToYsonConverter CreateSkiffToYsonConverter(
+ const NTableClient::TComplexTypeFieldDescriptor& descriptor,
+ const std::shared_ptr<NSkiff::TSkiffSchema>& skiffSchema,
+ const TSkiffToYsonConverterConfig& config = {});
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <NSkiff::EWireType wireType>
+struct TSimpleSkiffParser
+{
+ Y_FORCE_INLINE auto operator () (NSkiff::TCheckedInDebugSkiffParser* parser) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <NSkiff::EWireType SkiffWireType>
+class TDecimalSkiffParser
+{
+public:
+ explicit TDecimalSkiffParser(int precision);
+ Y_FORCE_INLINE TStringBuf operator() (NSkiff::TCheckedInDebugSkiffParser* parser) const;
+
+private:
+ const int Precision_;
+ mutable char Buffer_[NDecimal::TDecimal::MaxBinarySize];
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <NSkiff::EWireType SkiffWireType>
+class TDecimalSkiffWriter
+{
+public:
+ explicit TDecimalSkiffWriter(int precision);
+
+ void operator() (TStringBuf value, NSkiff::TCheckedInDebugSkiffWriter* writer) const;
+
+private:
+ const int Precision_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUuidParser
+{
+public:
+ Y_FORCE_INLINE TStringBuf operator() (NSkiff::TCheckedInDebugSkiffParser* parser) const;
+
+private:
+ mutable ui64 Buffer_[2];
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUuidWriter
+{
+public:
+ Y_FORCE_INLINE void operator() (TStringBuf value, NSkiff::TCheckedInDebugSkiffWriter* writer) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CheckSkiffWireTypeForDecimal(int precision, NSkiff::EWireType wireType);
+void CheckWireType(NSkiff::EWireType wireType, const std::initializer_list<NSkiff::EWireType>& expected);
+
+template <NSkiff::EWireType wireType, typename TValueType>
+Y_FORCE_INLINE void CheckIntSize(TValueType value);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::Formats
+
+#define SKIFF_YSON_CONVERTER_INL_H_
+#include "skiff_yson_converter-inl.h"
+#undef SKIFF_YSON_CONVERTER_INL_H_
diff --git a/yt/yt/client/formats/unversioned_value_yson_writer.cpp b/yt/yt/client/formats/unversioned_value_yson_writer.cpp
new file mode 100644
index 0000000000..c89dd48373
--- /dev/null
+++ b/yt/yt/client/formats/unversioned_value_yson_writer.cpp
@@ -0,0 +1,84 @@
+#include "unversioned_value_yson_writer.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_value.h>
+
+#include <yt/yt/core/yson/pull_parser.h>
+
+namespace NYT::NFormats {
+
+using namespace NYson;
+using namespace NTableClient;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedValueYsonWriter::TUnversionedValueYsonWriter(
+ const TNameTablePtr& nameTable,
+ const TTableSchemaPtr& tableSchema,
+ const TYsonConverterConfig& config)
+{
+ if (config.ComplexTypeMode == EComplexTypeMode::Positional &&
+ config.DecimalMode == EDecimalMode::Binary &&
+ config.TimeMode == ETimeMode::Binary &&
+ config.UuidMode == EUuidMode::Binary) {
+ return;
+ }
+
+ const auto& columns = tableSchema->Columns();
+ for (size_t i = 0; i != columns.size(); ++i) {
+ const auto& column = columns[i];
+ auto id = nameTable->GetIdOrRegisterName(column.Name());
+ TComplexTypeFieldDescriptor descriptor(column.Name(), column.LogicalType());
+ auto converter = CreateYsonServerToClientConverter(descriptor, config);
+ if (converter) {
+ ColumnConverters_.emplace(id, std::move(converter));
+ }
+ }
+}
+
+void TUnversionedValueYsonWriter::WriteValue(const TUnversionedValue& value, IYsonConsumer* consumer)
+{
+ if (value.Type == EValueType::Null) {
+ consumer->OnEntity();
+ return;
+ }
+ auto it = ColumnConverters_.find(value.Id);
+ if (it != ColumnConverters_.end()) {
+ it->second(value, consumer);
+ return;
+ }
+ switch (value.Type) {
+ case EValueType::Int64:
+ consumer->OnInt64Scalar(value.Data.Int64);
+ return;
+ case EValueType::Uint64:
+ consumer->OnUint64Scalar(value.Data.Uint64);
+ return;
+ case EValueType::Double:
+ consumer->OnDoubleScalar(value.Data.Double);
+ return;
+ case EValueType::Boolean:
+ consumer->OnBooleanScalar(value.Data.Boolean);
+ return;
+ case EValueType::String:
+ consumer->OnStringScalar(value.AsStringBuf());
+ return;
+ case EValueType::Any:
+ case EValueType::Composite:
+ consumer->OnRaw(value.AsStringBuf(), EYsonType::Node);
+ return;
+ case EValueType::Null:
+ case EValueType::Min:
+ case EValueType::TheBottom:
+ case EValueType::Max:
+ break;
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/unversioned_value_yson_writer.h b/yt/yt/client/formats/unversioned_value_yson_writer.h
new file mode 100644
index 0000000000..1b6f671298
--- /dev/null
+++ b/yt/yt/client/formats/unversioned_value_yson_writer.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+#include <yt/yt/client/complex_types/yson_format_conversion.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnversionedValueYsonWriter
+{
+public:
+ TUnversionedValueYsonWriter(
+ const NTableClient::TNameTablePtr& nameTable,
+ const NTableClient::TTableSchemaPtr& tableSchema,
+ const NComplexTypes::TYsonConverterConfig& config);
+
+ void WriteValue(const NTableClient::TUnversionedValue& value, NYson::IYsonConsumer* consumer);
+
+private:
+ THashMap<int, NComplexTypes::TYsonServerToClientConverter> ColumnConverters_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats \ No newline at end of file
diff --git a/yt/yt/client/formats/versioned_writer.cpp b/yt/yt/client/formats/versioned_writer.cpp
new file mode 100644
index 0000000000..056c86d5e1
--- /dev/null
+++ b/yt/yt/client/formats/versioned_writer.cpp
@@ -0,0 +1,138 @@
+#include "versioned_writer.h"
+#include "config.h"
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TVersionedWriter::TVersionedWriter(
+ NConcurrency::IAsyncOutputStreamPtr stream,
+ NTableClient::TTableSchemaPtr schema,
+ const std::function<std::unique_ptr<IFlushableYsonConsumer>(IZeroCopyOutput*)>& consumerBuilder)
+ : Stream_(std::move(stream))
+ , Schema_(std::move(schema))
+ , Consumer_(consumerBuilder(&Buffer_))
+{ }
+
+TFuture<void> TVersionedWriter::Close()
+{
+ return Result_;
+}
+
+bool TVersionedWriter::Write(TRange<TVersionedRow> rows)
+{
+ Buffer_.Clear();
+
+ auto consumeUnversionedData = [&] (const TUnversionedValue& value) {
+ switch (value.Type) {
+ case EValueType::Int64:
+ Consumer_->OnInt64Scalar(value.Data.Int64);
+ return;
+ case EValueType::Uint64:
+ Consumer_->OnUint64Scalar(value.Data.Uint64);
+ return;
+ case EValueType::Double:
+ Consumer_->OnDoubleScalar(value.Data.Double);
+ return;
+ case EValueType::Boolean:
+ Consumer_->OnBooleanScalar(value.Data.Boolean);
+ return;
+ case EValueType::String:
+ Consumer_->OnStringScalar(value.AsStringBuf());
+ return;
+ case EValueType::Null:
+ Consumer_->OnEntity();
+ return;
+ case EValueType::Any:
+ Consumer_->OnRaw(value.AsStringBuf(), EYsonType::Node);
+ return;
+ case EValueType::Composite:
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+ }
+ ThrowUnexpectedValueType(value.Type);
+ };
+
+ for (auto row : rows) {
+ if (!row) {
+ Consumer_->OnEntity();
+ continue;
+ }
+
+ Consumer_->OnBeginAttributes();
+ {
+ Consumer_->OnKeyedItem("write_timestamps");
+ Consumer_->OnBeginList();
+ for (auto timestamp : row.WriteTimestamps()) {
+ Consumer_->OnListItem();
+ Consumer_->OnUint64Scalar(timestamp);
+ }
+ Consumer_->OnEndList();
+ }
+ {
+ Consumer_->OnKeyedItem("delete_timestamps");
+ Consumer_->OnBeginList();
+ for (auto timestamp : row.DeleteTimestamps()) {
+ Consumer_->OnListItem();
+ Consumer_->OnUint64Scalar(timestamp);
+ }
+ Consumer_->OnEndList();
+ }
+ Consumer_->OnEndAttributes();
+
+ Consumer_->OnBeginMap();
+ for (const auto& value : row.Keys()) {
+ const auto& column = Schema_->Columns()[value.Id];
+ Consumer_->OnKeyedItem(column.Name());
+ consumeUnversionedData(value);
+ }
+ for (auto valuesBeginIt = row.BeginValues(), valuesEndIt = row.EndValues(); valuesBeginIt != valuesEndIt; /**/) {
+ auto columnBeginIt = valuesBeginIt;
+ auto columnEndIt = columnBeginIt;
+ while (columnEndIt < valuesEndIt && columnEndIt->Id == columnBeginIt->Id) {
+ ++columnEndIt;
+ }
+
+ const auto& column = Schema_->Columns()[columnBeginIt->Id];
+ Consumer_->OnKeyedItem(column.Name());
+ Consumer_->OnBeginList();
+ while (columnBeginIt != columnEndIt) {
+ Consumer_->OnListItem();
+ Consumer_->OnBeginAttributes();
+ Consumer_->OnKeyedItem("timestamp");
+ Consumer_->OnUint64Scalar(columnBeginIt->Timestamp);
+ Consumer_->OnKeyedItem("aggregate");
+ Consumer_->OnBooleanScalar(Any(columnBeginIt->Flags & EValueFlags::Aggregate));
+ Consumer_->OnEndAttributes();
+ consumeUnversionedData(*columnBeginIt);
+ ++columnBeginIt;
+ }
+ Consumer_->OnEndList();
+
+ valuesBeginIt = columnEndIt;
+ }
+ Consumer_->OnEndMap();
+ }
+
+ Consumer_->Flush();
+ auto buffer = Buffer_.Flush();
+ Result_ = Stream_->Write(buffer);
+ return Result_.IsSet() && Result_.Get().IsOK();
+}
+
+TFuture<void> TVersionedWriter::GetReadyEvent()
+{
+ return Result_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/versioned_writer.h b/yt/yt/client/formats/versioned_writer.h
new file mode 100644
index 0000000000..215f4e6537
--- /dev/null
+++ b/yt/yt/client/formats/versioned_writer.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/versioned_writer.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+
+#include <yt/yt/core/yson/writer.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TVersionedWriter
+ : public NTableClient::IVersionedWriter
+{
+public:
+ TVersionedWriter(
+ NConcurrency::IAsyncOutputStreamPtr stream,
+ NTableClient::TTableSchemaPtr schema,
+ const std::function<std::unique_ptr<NYson::IFlushableYsonConsumer>(IZeroCopyOutput*)>& consumerBuilder);
+
+ virtual TFuture<void> Close() override;
+
+ virtual bool Write(TRange<NTableClient::TVersionedRow> rows) override;
+
+ virtual TFuture<void> GetReadyEvent() override;
+
+private:
+ const NConcurrency::IAsyncOutputStreamPtr Stream_;
+ const NTableClient::TTableSchemaPtr Schema_;
+
+ TBlobOutput Buffer_;
+ TFuture<void> Result_;
+
+ const std::unique_ptr<NYson::IFlushableYsonConsumer> Consumer_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/web_json_writer.cpp b/yt/yt/client/formats/web_json_writer.cpp
new file mode 100644
index 0000000000..d4c7293c85
--- /dev/null
+++ b/yt/yt/client/formats/web_json_writer.cpp
@@ -0,0 +1,780 @@
+#include "web_json_writer.h"
+
+#include "config.h"
+#include "format.h"
+#include "public.h"
+#include "schemaless_writer_adapter.h"
+#include "yql_yson_converter.h"
+
+#include <yt/yt/client/complex_types/yson_format_conversion.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <yt/yt/core/yson/format.h>
+
+#include <yt/yt/core/json/json_writer.h>
+#include <yt/yt/core/json/config.h>
+#include <yt/yt/core/json/helpers.h>
+
+#include <yt/yt/core/misc/utf8_decoder.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <util/generic/buffer.h>
+
+#include <util/stream/length.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NComplexTypes;
+using namespace NYTree;
+using namespace NYson;
+using namespace NJson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr auto ContextBufferCapacity = 1_MB;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWebJsonColumnFilter
+{
+public:
+ TWebJsonColumnFilter(int maxSelectedColumnCount, std::optional<THashSet<TString>> names)
+ : MaxSelectedColumnCount_(maxSelectedColumnCount)
+ , Names_(std::move(names))
+ { }
+
+ bool Accept(ui16 columnId, TStringBuf columnName)
+ {
+ if (Names_) {
+ return AcceptByNames(columnName);
+ }
+ return AcceptByMaxCount(columnId);
+ }
+
+private:
+ const int MaxSelectedColumnCount_;
+ std::optional<THashSet<TString>> Names_;
+
+ THashSet<ui16> AcceptedColumnIds_;
+
+ bool AcceptByNames(TStringBuf columnName)
+ {
+ return Names_->contains(columnName);
+ }
+
+ bool AcceptByMaxCount(ui16 columnId)
+ {
+ if (std::ssize(AcceptedColumnIds_) < MaxSelectedColumnCount_) {
+ AcceptedColumnIds_.insert(columnId);
+ return true;
+ }
+ return AcceptedColumnIds_.contains(columnId);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TWebJsonColumnFilter CreateWebJsonColumnFilter(const TWebJsonFormatConfigPtr& webJsonConfig)
+{
+ std::optional<THashSet<TString>> columnNames;
+ if (webJsonConfig->ColumnNames) {
+ columnNames.emplace();
+ for (const auto& columnName : *webJsonConfig->ColumnNames) {
+ if (!columnNames->insert(columnName).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column name %Qv in \"column_names\" parameter of web_json format config",
+ columnName);
+ }
+ }
+ }
+ return TWebJsonColumnFilter(webJsonConfig->MaxSelectedColumnCount, std::move(columnNames));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStringBuf GetSimpleYqlTypeName(ESimpleLogicalValueType type)
+{
+ switch (type) {
+ case ESimpleLogicalValueType::Float:
+ return TStringBuf("Float");
+ case ESimpleLogicalValueType::Double:
+ return TStringBuf("Double");
+ case ESimpleLogicalValueType::Boolean:
+ return TStringBuf("Boolean");
+ case ESimpleLogicalValueType::String:
+ return TStringBuf("String");
+ case ESimpleLogicalValueType::Utf8:
+ return TStringBuf("Utf8");
+ case ESimpleLogicalValueType::Any:
+ return TStringBuf("Yson");
+ case ESimpleLogicalValueType::Json:
+ return TStringBuf("Json");
+ case ESimpleLogicalValueType::Int8:
+ return TStringBuf("Int8");
+ case ESimpleLogicalValueType::Int16:
+ return TStringBuf("Int16");
+ case ESimpleLogicalValueType::Int32:
+ return TStringBuf("Int32");
+ case ESimpleLogicalValueType::Int64:
+ return TStringBuf("Int64");
+ case ESimpleLogicalValueType::Uint8:
+ return TStringBuf("Uint8");
+ case ESimpleLogicalValueType::Uint16:
+ return TStringBuf("Uint16");
+ case ESimpleLogicalValueType::Uint32:
+ return TStringBuf("Uint32");
+ case ESimpleLogicalValueType::Uint64:
+ return TStringBuf("Uint64");
+ case ESimpleLogicalValueType::Date:
+ return TStringBuf("Date");
+ case ESimpleLogicalValueType::Datetime:
+ return TStringBuf("Datetime");
+ case ESimpleLogicalValueType::Timestamp:
+ return TStringBuf("Timestamp");
+ case ESimpleLogicalValueType::Interval:
+ return TStringBuf("Interval");
+ case ESimpleLogicalValueType::Uuid:
+ return TStringBuf("Uuid");
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ // This case must have been processed earlier.
+ YT_ABORT();
+ }
+ YT_ABORT();
+}
+
+void SerializeAsYqlType(TFluentAny fluent, const TLogicalTypePtr& type)
+{
+ auto serializeStruct = [] (TFluentList fluentList, const TStructLogicalTypeBase& structType) {
+ fluentList
+ .Item().Value("StructType")
+ .Item().DoListFor(structType.GetFields(), [] (TFluentList innerFluentList, const TStructField& field) {
+ innerFluentList
+ .Item()
+ .BeginList()
+ .Item().Value(field.Name)
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, field.Type);
+ })
+ .EndList();
+ });
+ };
+
+ auto serializeTuple = [] (TFluentList fluentList, const TTupleLogicalTypeBase& tupleType) {
+ fluentList
+ .Item().Value("TupleType")
+ .Item().DoListFor(tupleType.GetElements(), [&] (TFluentList innerFluentList, const TLogicalTypePtr& element) {
+ innerFluentList
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, element);
+ });
+ });
+ };
+
+ auto build = [&] (TFluentList fluentList){
+ switch (type->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ if (type->AsSimpleTypeRef().GetElement() == ESimpleLogicalValueType::Null) {
+ fluentList
+ .Item().Value("NullType");
+ } else if (type->AsSimpleTypeRef().GetElement() == ESimpleLogicalValueType::Void) {
+ fluentList
+ .Item().Value("VoidType");
+ } else {
+ fluentList
+ .Item().Value("DataType")
+ .Item().Value(GetSimpleYqlTypeName(type->AsSimpleTypeRef().GetElement()));
+ }
+ return;
+ case ELogicalMetatype::Decimal:
+ fluentList
+ .Item().Value("DataType")
+ .Item().Value("Decimal")
+ .Item().Value(ToString(type->AsDecimalTypeRef().GetPrecision()))
+ .Item().Value(ToString(type->AsDecimalTypeRef().GetScale()));
+ return;
+ case ELogicalMetatype::Optional:
+ fluentList
+ .Item().Value("OptionalType")
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, type->AsOptionalTypeRef().GetElement());
+ });
+ return;
+ case ELogicalMetatype::List:
+ fluentList
+ .Item().Value("ListType")
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, type->AsListTypeRef().GetElement());
+ });
+ return;
+ case ELogicalMetatype::Struct:
+ serializeStruct(fluentList, type->AsStructTypeRef());
+ return;
+ case ELogicalMetatype::Tuple:
+ serializeTuple(fluentList, type->AsTupleTypeRef());
+ return;
+ case ELogicalMetatype::VariantStruct:
+ fluentList
+ .Item().Value("VariantType")
+ .Item().DoList([&] (TFluentList fluentList) {
+ serializeStruct(fluentList, type->AsVariantStructTypeRef());
+ });
+ return;
+ case ELogicalMetatype::VariantTuple:
+ fluentList
+ .Item().Value("VariantType")
+ .Item().DoList([&] (TFluentList fluentList) {
+ serializeTuple(fluentList, type->AsVariantTupleTypeRef());
+ });
+ return;
+ case ELogicalMetatype::Dict:
+ fluentList
+ .Item().Value("DictType")
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, type->AsDictTypeRef().GetKey());
+ })
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, type->AsDictTypeRef().GetValue());
+ });
+ return;
+ case ELogicalMetatype::Tagged:
+ fluentList
+ .Item().Value("TaggedType")
+ .Item().Value(type->AsTaggedTypeRef().GetTag())
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, type->AsTaggedTypeRef().GetElement());
+ });
+ return;
+ }
+ YT_ABORT();
+ };
+
+ fluent.DoList(build);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYqlValueWriter
+{
+public:
+ TYqlValueWriter(
+ const TWebJsonFormatConfigPtr& config,
+ const TNameTablePtr& /*nameTable*/,
+ const std::vector<TTableSchemaPtr>& schemas,
+ IJsonWriter* consumer)
+ : Consumer_(consumer)
+ , TableIndexToColumnIdToTypeIndex_(schemas.size())
+ {
+ YT_VERIFY(config->ValueFormat == EWebJsonValueFormat::Yql);
+ auto converterConfig = CreateYqlJsonConsumerConfig(config);
+
+ for (auto valueType : TEnumTraits<EValueType>::GetDomainValues()) {
+ if (IsValueType(valueType) || valueType == EValueType::Null || valueType == EValueType::Composite) {
+ TLogicalTypePtr logicalType = valueType == EValueType::Composite
+ ? SimpleLogicalType(ESimpleLogicalValueType::Any)
+ : SimpleLogicalType(GetLogicalType(valueType));
+ Types_.push_back(logicalType);
+ Converters_.push_back(CreateUnversionedValueToYqlConverter(Types_.back(), converterConfig, Consumer_));
+ ValueTypeToTypeIndex_[valueType] = static_cast<int>(Types_.size()) - 1;
+ } else {
+ ValueTypeToTypeIndex_[valueType] = UnknownTypeIndex;
+ }
+ }
+
+ for (int tableIndex = 0; tableIndex != static_cast<int>(schemas.size()); ++tableIndex) {
+ const auto& schema = schemas[tableIndex];
+ for (const auto& column : schema->Columns()) {
+ Types_.push_back(column.LogicalType());
+ Converters_.push_back(
+ CreateUnversionedValueToYqlConverter(column.LogicalType(), converterConfig, Consumer_));
+ auto [it, inserted] = TableIndexAndColumnNameToTypeIndex_.emplace(
+ std::make_pair(tableIndex, column.Name()),
+ static_cast<int>(Types_.size()) - 1);
+ YT_VERIFY(inserted);
+ }
+ }
+ }
+
+ void WriteValue(int tableIndex, TStringBuf columnName, TUnversionedValue value)
+ {
+ Consumer_->OnBeginList();
+
+ Consumer_->OnListItem();
+ auto typeIndex = GetTypeIndex(tableIndex, value.Id, columnName, value.Type);
+ Converters_[typeIndex](value);
+
+ Consumer_->OnListItem();
+ Consumer_->OnStringScalar(ToString(typeIndex));
+
+ Consumer_->OnEndList();
+ }
+
+ void WriteMetaInfo()
+ {
+ Consumer_->OnKeyedItem("yql_type_registry");
+ BuildYsonFluently(Consumer_)
+ .DoListFor(Types_, [&] (TFluentList fluentList, const TLogicalTypePtr& type) {
+ fluentList
+ .Item().Do([&] (TFluentAny innerFluent) {
+ SerializeAsYqlType(innerFluent, type);
+ });
+ });
+ }
+
+private:
+ static constexpr int UnknownTypeIndex = -1;
+ static constexpr int UnschematizedTypeIndex = -2;
+
+ IJsonWriter* Consumer_;
+ std::vector<TUnversionedValueToYqlConverter> Converters_;
+ std::vector<TLogicalTypePtr> Types_;
+ std::vector<std::vector<int>> TableIndexToColumnIdToTypeIndex_;
+ THashMap<std::pair<int, TString>, int> TableIndexAndColumnNameToTypeIndex_;
+ TEnumIndexedVector<EValueType, int> ValueTypeToTypeIndex_;
+
+private:
+ int GetTypeIndex(int tableIndex, ui16 columnId, TStringBuf columnName, EValueType valueType)
+ {
+ YT_VERIFY(0 <= tableIndex && tableIndex < static_cast<int>(TableIndexToColumnIdToTypeIndex_.size()));
+ auto& columnIdToTypeIndex = TableIndexToColumnIdToTypeIndex_[tableIndex];
+ if (columnId >= columnIdToTypeIndex.size()) {
+ columnIdToTypeIndex.resize(columnId + 1, UnknownTypeIndex);
+ }
+
+ auto typeIndex = columnIdToTypeIndex[columnId];
+ if (typeIndex == UnschematizedTypeIndex) {
+ typeIndex = ValueTypeToTypeIndex_[valueType];
+ } else if (typeIndex == UnknownTypeIndex) {
+ auto it = TableIndexAndColumnNameToTypeIndex_.find(std::make_pair(tableIndex, columnName));
+ if (it == TableIndexAndColumnNameToTypeIndex_.end()) {
+ typeIndex = ValueTypeToTypeIndex_[valueType];
+ columnIdToTypeIndex[columnId] = UnschematizedTypeIndex;
+ } else {
+ typeIndex = columnIdToTypeIndex[columnId] = it->second;
+ }
+ }
+
+ YT_VERIFY(typeIndex != UnknownTypeIndex && typeIndex != UnschematizedTypeIndex);
+ return typeIndex;
+ }
+
+ static TYqlConverterConfigPtr CreateYqlJsonConsumerConfig(const TWebJsonFormatConfigPtr& formatConfig)
+ {
+ auto config = New<TYqlConverterConfig>();
+ config->FieldWeightLimit = formatConfig->FieldWeightLimit;
+ config->StringWeightLimit = formatConfig->StringWeightLimit;
+ return config;
+ }
+};
+
+class TSchemalessValueWriter
+{
+public:
+ TSchemalessValueWriter(
+ const TWebJsonFormatConfigPtr& config,
+ const TNameTablePtr& nameTable,
+ const std::vector<TTableSchemaPtr>& schemas,
+ IJsonWriter* writer)
+ : FieldWeightLimit_(config->FieldWeightLimit)
+ , BlobYsonWriter_(&TmpBlob_, EYsonType::Node)
+ {
+ YT_VERIFY(config->ValueFormat == EWebJsonValueFormat::Schemaless);
+
+ auto jsonFormatConfig = New<TJsonFormatConfig>();
+ jsonFormatConfig->Stringify = true;
+ jsonFormatConfig->AnnotateWithTypes = true;
+ Consumer_ = CreateJsonConsumer(writer, EYsonType::Node, std::move(jsonFormatConfig));
+
+ for (int tableIndex = 0; tableIndex != std::ssize(schemas); ++tableIndex) {
+ for (const auto& column : schemas[tableIndex]->Columns()) {
+ if (!IsV3Composite(column.LogicalType())) {
+ continue;
+ }
+ auto columnId = nameTable->GetIdOrRegisterName(column.Name());
+ auto descriptor = TComplexTypeFieldDescriptor(column);
+ auto converter = CreateYsonServerToClientConverter(descriptor, { });
+ if (converter) {
+ YsonConverters_.emplace(std::pair{tableIndex, columnId}, std::move(converter));
+ }
+ }
+ }
+ }
+
+ void WriteValue(int tableIndex, TStringBuf /*columnName*/, TUnversionedValue value)
+ {
+ switch (value.Type) {
+ case EValueType::Any:
+ case EValueType::Composite: {
+ const auto data = value.AsStringBuf();
+ auto key = std::pair<int,int>(tableIndex, value.Id);
+ auto it = YsonConverters_.find(key);
+ if (it == YsonConverters_.end()) {
+ Consumer_->OnNodeWeightLimited(data, FieldWeightLimit_);
+ } else {
+ TmpBlob_.Clear();
+ it->second(value, &BlobYsonWriter_);
+ BlobYsonWriter_.Flush();
+
+ Consumer_->OnNodeWeightLimited(TStringBuf(TmpBlob_.Begin(), TmpBlob_.Size()), FieldWeightLimit_);
+ }
+ return;
+ }
+ case EValueType::String:
+ Consumer_->OnStringScalarWeightLimited(
+ value.AsStringBuf(),
+ FieldWeightLimit_);
+ return;
+ case EValueType::Int64:
+ Consumer_->OnInt64Scalar(value.Data.Int64);
+ return;
+ case EValueType::Uint64:
+ Consumer_->OnUint64Scalar(value.Data.Uint64);
+ return;
+ case EValueType::Double:
+ Consumer_->OnDoubleScalar(value.Data.Double);
+ return;
+ case EValueType::Boolean:
+ Consumer_->OnBooleanScalar(value.Data.Boolean);
+ return;
+ case EValueType::Null:
+ Consumer_->OnEntity();
+ return;
+ case EValueType::TheBottom:
+ case EValueType::Min:
+ case EValueType::Max:
+ break;
+ }
+ ThrowUnexpectedValueType(value.Type);
+ }
+
+ void WriteMetaInfo()
+ { }
+
+private:
+ int FieldWeightLimit_;
+ std::unique_ptr<IJsonConsumer> Consumer_;
+
+ // Map <tableIndex,columnId> -> YsonConverter
+ THashMap<std::pair<int, int>, TYsonServerToClientConverter> YsonConverters_;
+ TBlobOutput TmpBlob_;
+ TBufferedBinaryYsonWriter BlobYsonWriter_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TValueWriter>
+class TWriterForWebJson
+ : public ISchemalessFormatWriter
+{
+public:
+ TWriterForWebJson(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ TWebJsonColumnFilter columnFilter,
+ const std::vector<TTableSchemaPtr>& schemas,
+ TWebJsonFormatConfigPtr config);
+
+ bool Write(TRange<TUnversionedRow> rows) override;
+ bool WriteBatch(NTableClient::IUnversionedRowBatchPtr rowBatch) override;
+ TFuture<void> GetReadyEvent() override;
+ TBlob GetContext() const override;
+ i64 GetWrittenSize() const override;
+ TFuture<void> Close() override;
+ TFuture<void> Flush() override;
+
+private:
+ const TWebJsonFormatConfigPtr Config_;
+ const TNameTablePtr NameTable_;
+ const TNameTableReader NameTableReader_;
+
+ std::unique_ptr<IOutputStream> UnderlyingOutput_;
+ TCountingOutput Output_;
+
+ std::unique_ptr<IJsonWriter> ResponseBuilder_;
+
+ TWebJsonColumnFilter ColumnFilter_;
+ THashMap<ui16, TString> AllColumnIdToName_;
+
+ TValueWriter ValueWriter_;
+
+ TUtf8Transcoder Utf8Transcoder_;
+
+ bool IncompleteAllColumnNames_ = false;
+ bool IncompleteColumns_ = false;
+
+ TError Error_;
+ int TableIndexId_ = -1;
+
+private:
+ bool TryRegisterColumn(ui16 columnId, TStringBuf columnName);
+ bool SkipSystemColumn(TStringBuf columnName) const;
+
+ void DoFlush(bool force);
+ void DoWrite(TRange<TUnversionedRow> rows);
+ void DoClose();
+};
+
+template <typename TValueWriter>
+TWriterForWebJson<TValueWriter>::TWriterForWebJson(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ TWebJsonColumnFilter columnFilter,
+ const std::vector<TTableSchemaPtr>& schemas,
+ TWebJsonFormatConfigPtr config)
+ : Config_(std::move(config))
+ , NameTable_(std::move(nameTable))
+ , NameTableReader_(NameTable_)
+ , UnderlyingOutput_(CreateBufferedSyncAdapter(
+ std::move(output),
+ EWaitForStrategy::WaitFor,
+ ContextBufferCapacity))
+ , Output_(UnderlyingOutput_.get())
+ , ResponseBuilder_(CreateJsonWriter(&Output_))
+ , ColumnFilter_(std::move(columnFilter))
+ , ValueWriter_(Config_, NameTable_, schemas, ResponseBuilder_.get())
+{
+ ResponseBuilder_->OnBeginMap();
+ ResponseBuilder_->OnKeyedItem("rows");
+ ResponseBuilder_->OnBeginList();
+ TableIndexId_ = NameTable_->GetIdOrRegisterName(TableIndexColumnName);
+}
+
+template <typename TValueWriter>
+bool TWriterForWebJson<TValueWriter>::Write(TRange<TUnversionedRow> rows)
+{
+ if (!Error_.IsOK()) {
+ return false;
+ }
+
+ try {
+ DoWrite(rows);
+ } catch (const std::exception& ex) {
+ Error_ = TError(ex);
+ return false;
+ }
+
+ return true;
+}
+
+template <typename TValueWriter>
+bool TWriterForWebJson<TValueWriter>::WriteBatch(NTableClient::IUnversionedRowBatchPtr rowBatch)
+{
+ return Write(rowBatch->MaterializeRows());
+}
+
+template <typename TValueWriter>
+TFuture<void> TWriterForWebJson<TValueWriter>::GetReadyEvent()
+{
+ return MakeFuture(Error_);
+}
+
+template <typename TValueWriter>
+TBlob TWriterForWebJson<TValueWriter>::GetContext() const
+{
+ return TBlob();
+}
+
+template <typename TValueWriter>
+i64 TWriterForWebJson<TValueWriter>::GetWrittenSize() const
+{
+ return static_cast<i64>(Output_.Counter());
+}
+
+template <typename TValueWriter>
+TFuture<void> TWriterForWebJson<TValueWriter>::Close()
+{
+ try {
+ DoClose();
+ } catch (const std::exception& exception) {
+ Error_ = TError(exception);
+ }
+
+ return GetReadyEvent();
+}
+
+template <typename TValueWriter>
+TFuture<void> TWriterForWebJson<TValueWriter>::Flush()
+{
+ DoFlush(/*force*/ true);
+
+ return GetReadyEvent();
+}
+
+template <typename TValueWriter>
+bool TWriterForWebJson<TValueWriter>::TryRegisterColumn(ui16 columnId, TStringBuf columnName)
+{
+ if (SkipSystemColumn(columnName)) {
+ return false;
+ }
+
+ if (std::ssize(AllColumnIdToName_) < Config_->MaxAllColumnNamesCount) {
+ AllColumnIdToName_[columnId] = columnName;
+ } else if (!AllColumnIdToName_.contains(columnId)) {
+ IncompleteAllColumnNames_ = true;
+ }
+
+ const auto result = ColumnFilter_.Accept(columnId, columnName);
+ if (!result) {
+ IncompleteColumns_ = true;
+ }
+
+ return result;
+}
+
+template <typename TValueWriter>
+bool TWriterForWebJson<TValueWriter>::SkipSystemColumn(TStringBuf columnName) const
+{
+ if (!columnName.StartsWith(SystemColumnNamePrefix)) {
+ return false;
+ }
+ return Config_->SkipSystemColumns;
+}
+
+template <typename TValueWriter>
+void TWriterForWebJson<TValueWriter>::DoFlush(bool force)
+{
+ ResponseBuilder_->Flush();
+ if (force) {
+ UnderlyingOutput_->Flush();
+ }
+}
+
+template <typename TValueWriter>
+void TWriterForWebJson<TValueWriter>::DoWrite(TRange<TUnversionedRow> rows)
+{
+ for (auto row : rows) {
+ if (!row) {
+ continue;
+ }
+
+ ResponseBuilder_->OnListItem();
+ ResponseBuilder_->OnBeginMap();
+
+ for (auto value : row) {
+ auto columnName = NameTableReader_.FindName(value.Id);
+ if (!columnName) {
+ continue;
+ }
+
+ if (!TryRegisterColumn(value.Id, columnName)) {
+ continue;
+ }
+
+ int tableIndex = 0;
+ for (auto val : row) {
+ if (val.Id == TableIndexId_) {
+ tableIndex = val.Data.Int64;
+ break;
+ }
+ }
+
+ if (IsSpecialJsonKey(columnName)) {
+ ResponseBuilder_->OnKeyedItem(Utf8Transcoder_.Encode(TString("$") + columnName));
+ } else {
+ ResponseBuilder_->OnKeyedItem(Utf8Transcoder_.Encode(columnName));
+ }
+ ValueWriter_.WriteValue(tableIndex, columnName, value);
+ }
+
+ ResponseBuilder_->OnEndMap();
+
+ DoFlush(false);
+ }
+
+ DoFlush(true);
+}
+
+template <typename TValueWriter>
+void TWriterForWebJson<TValueWriter>::DoClose()
+{
+ if (Error_.IsOK()) {
+ ResponseBuilder_->OnEndList();
+
+ ResponseBuilder_->OnKeyedItem("incomplete_columns");
+ // TODO(levysotsky): Maybe we don't need stringification here?
+ ResponseBuilder_->OnStringScalar(IncompleteColumns_ ? "true" : "false");
+
+ ResponseBuilder_->OnKeyedItem("incomplete_all_column_names");
+ // TODO(levysotsky): Maybe we don't need stringification here?
+ ResponseBuilder_->OnStringScalar(IncompleteAllColumnNames_ ? "true" : "false");
+
+ ResponseBuilder_->OnKeyedItem("all_column_names");
+ ResponseBuilder_->OnBeginList();
+
+ std::vector<TStringBuf> allColumnNamesSorted;
+ allColumnNamesSorted.reserve(AllColumnIdToName_.size());
+ for (const auto& columnIdToName : AllColumnIdToName_) {
+ allColumnNamesSorted.push_back(columnIdToName.second);
+ }
+ std::sort(allColumnNamesSorted.begin(), allColumnNamesSorted.end());
+
+ for (const auto columnName : allColumnNamesSorted) {
+ ResponseBuilder_->OnListItem();
+ ResponseBuilder_->OnStringScalar(Utf8Transcoder_.Encode(columnName));
+ }
+
+ ResponseBuilder_->OnEndList();
+
+ ValueWriter_.WriteMetaInfo();
+
+ ResponseBuilder_->OnEndMap();
+
+ DoFlush(true);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateWriterForWebJson(
+ TWebJsonFormatConfigPtr config,
+ TNameTablePtr nameTable,
+ const std::vector<TTableSchemaPtr>& schemas,
+ IAsyncOutputStreamPtr output)
+{
+ switch (config->ValueFormat) {
+ case EWebJsonValueFormat::Schemaless:
+ return New<TWriterForWebJson<TSchemalessValueWriter>>(
+ std::move(nameTable),
+ std::move(output),
+ CreateWebJsonColumnFilter(config),
+ schemas,
+ std::move(config));
+ case EWebJsonValueFormat::Yql:
+ return New<TWriterForWebJson<TYqlValueWriter>>(
+ std::move(nameTable),
+ std::move(output),
+ CreateWebJsonColumnFilter(config),
+ schemas,
+ std::move(config));
+
+ }
+ YT_ABORT();
+}
+
+ISchemalessFormatWriterPtr CreateWriterForWebJson(
+ const NYTree::IAttributeDictionary& attributes,
+ TNameTablePtr nameTable,
+ const std::vector<TTableSchemaPtr>& schemas,
+ IAsyncOutputStreamPtr output)
+{
+ try {
+ return CreateWriterForWebJson(
+ ConvertTo<TWebJsonFormatConfigPtr>(&attributes),
+ std::move(nameTable),
+ schemas,
+ std::move(output));
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(EErrorCode::InvalidFormat, "Failed to parse config for web JSON format") << ex;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/web_json_writer.h b/yt/yt/client/formats/web_json_writer.h
new file mode 100644
index 0000000000..25d476a98f
--- /dev/null
+++ b/yt/yt/client/formats/web_json_writer.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+#include "helpers.h"
+#include "schemaless_writer_adapter.h"
+
+#include <yt/yt/client/table_client/unversioned_writer.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <yt/yt/core/misc/blob.h>
+#include <yt/yt/core/misc/blob_output.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateWriterForWebJson(
+ TWebJsonFormatConfigPtr config,
+ NTableClient::TNameTablePtr nameTable,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NConcurrency::IAsyncOutputStreamPtr output);
+
+ISchemalessFormatWriterPtr CreateWriterForWebJson(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TNameTablePtr nameTable,
+ const std::vector<NTableClient::TTableSchemaPtr>& schemas,
+ NConcurrency::IAsyncOutputStreamPtr output);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/ya.make b/yt/yt/client/formats/ya.make
new file mode 100644
index 0000000000..18eb0e8384
--- /dev/null
+++ b/yt/yt/client/formats/ya.make
@@ -0,0 +1,45 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ config.cpp
+ dsv_parser.cpp
+ dsv_writer.cpp
+ escape.cpp
+ format.cpp
+ helpers.cpp
+ parser.cpp
+ protobuf.cpp
+ protobuf_options.cpp
+ protobuf_parser.cpp
+ protobuf_writer.cpp
+ schemaful_dsv_parser.cpp
+ schemaful_dsv_writer.cpp
+ schemaful_writer.cpp
+ schemaless_writer_adapter.cpp
+ web_json_writer.cpp
+ skiff_parser.cpp
+ skiff_yson_converter.cpp
+ skiff_writer.cpp
+ unversioned_value_yson_writer.cpp
+ versioned_writer.cpp
+ yamr_parser.cpp
+ yamr_parser_base.cpp
+ yamr_writer.cpp
+ yamr_writer_base.cpp
+ yamred_dsv_parser.cpp
+ yamred_dsv_writer.cpp
+ yson_parser.cpp
+ yson_map_to_unversioned_value.cpp
+ yql_yson_converter.cpp
+)
+
+PEERDIR(
+ yt/yt/client
+ yt/yt/library/skiff_ext
+ yt/yt_proto/yt/formats
+ library/cpp/string_utils/base64
+)
+
+END()
diff --git a/yt/yt/client/formats/yamr_parser.cpp b/yt/yt/client/formats/yamr_parser.cpp
new file mode 100644
index 0000000000..0465741743
--- /dev/null
+++ b/yt/yt/client/formats/yamr_parser.cpp
@@ -0,0 +1,98 @@
+#include "yamr_parser.h"
+#include "yamr_parser_base.h"
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+class TYamrParserConsumer
+ : public TYamrConsumerBase
+{
+public:
+ TYamrParserConsumer(NYson::IYsonConsumer* consumer, TYamrFormatConfigPtr config)
+ : TYamrConsumerBase(consumer)
+ , Config(config)
+ { }
+
+ void ConsumeKey(TStringBuf key) override
+ {
+ Consumer->OnListItem();
+ Consumer->OnBeginMap();
+ Consumer->OnKeyedItem(Config->Key);
+ Consumer->OnStringScalar(key);
+ }
+
+ void ConsumeSubkey(TStringBuf subkey) override
+ {
+ Consumer->OnKeyedItem(Config->Subkey);
+ Consumer->OnStringScalar(subkey);
+ }
+
+ void ConsumeValue(TStringBuf value) override
+ {
+ Consumer->OnKeyedItem(Config->Value);
+ Consumer->OnStringScalar(value);
+ Consumer->OnEndMap();
+ }
+
+private:
+ TYamrFormatConfigPtr Config;
+
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForYamr(
+ NYson::IYsonConsumer* consumer,
+ TYamrFormatConfigPtr config)
+{
+ if (!config) {
+ config = New<TYamrFormatConfig>();
+ }
+
+ auto parserConsumer = New<TYamrParserConsumer>(consumer, config);
+
+ return config->Lenval
+ ? std::unique_ptr<IParser>(
+ new TYamrLenvalBaseParser(
+ parserConsumer,
+ config->HasSubkey,
+ config->EnableEom))
+ : std::unique_ptr<IParser>(
+ new TYamrDelimitedBaseParser(
+ parserConsumer,
+ config,
+ config->EnableEscaping /* enableKeyEscaping */,
+ config->EnableEscaping /* enableValueEscaping */));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseYamr(
+ IInputStream* input,
+ NYson::IYsonConsumer* consumer,
+ TYamrFormatConfigPtr config)
+{
+ auto parser = CreateParserForYamr(consumer, config);
+ Parse(input, parser.get());
+}
+
+void ParseYamr(
+ TStringBuf data,
+ NYson::IYsonConsumer* consumer,
+ TYamrFormatConfigPtr config)
+{
+ auto parser = CreateParserForYamr(consumer, config);
+ parser->Read(data);
+ parser->Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamr_parser.h b/yt/yt/client/formats/yamr_parser.h
new file mode 100644
index 0000000000..10b5185d8e
--- /dev/null
+++ b/yt/yt/client/formats/yamr_parser.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForYamr(
+ NYson::IYsonConsumer* consumer,
+ TYamrFormatConfigPtr config = New<TYamrFormatConfig>());
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseYamr(
+ IInputStream* input,
+ NYson::IYsonConsumer* consumer,
+ TYamrFormatConfigPtr config = New<TYamrFormatConfig>());
+
+void ParseYamr(
+ TStringBuf data,
+ NYson::IYsonConsumer* consumer,
+ TYamrFormatConfigPtr config = New<TYamrFormatConfig>());
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamr_parser_base.cpp b/yt/yt/client/formats/yamr_parser_base.cpp
new file mode 100644
index 0000000000..3a7a7d8833
--- /dev/null
+++ b/yt/yt/client/formats/yamr_parser_base.cpp
@@ -0,0 +1,453 @@
+#include "yamr_parser_base.h"
+
+#include "format.h"
+#include "config.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/helpers.h>
+
+#include <util/string/escape.h>
+
+namespace NYT::NFormats {
+
+using namespace NYTree;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYamrConsumerBase::TYamrConsumerBase(NYson::IYsonConsumer* consumer)
+ : Consumer(consumer)
+{ }
+
+void TYamrConsumerBase::SwitchTable(i64 tableIndex)
+{
+ static const TString Key = FormatEnum(EControlAttribute::TableIndex);
+ Consumer->OnListItem();
+ Consumer->OnBeginAttributes();
+ Consumer->OnKeyedItem(Key);
+ Consumer->OnInt64Scalar(tableIndex);
+ Consumer->OnEndAttributes();
+ Consumer->OnEntity();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYamrDelimitedBaseParser::TYamrDelimitedBaseParser(
+ IYamrConsumerPtr consumer,
+ const TYamrFormatConfigBasePtr& config,
+ bool enableKeyEscaping,
+ bool enableValueEscaping)
+ : Consumer(consumer)
+ , Config_(config)
+ , State(EState::InsideKey)
+ , ExpectingEscapedChar(false)
+ , Offset(0)
+ , Record(1)
+ , BufferPosition(0)
+{
+ ConfigureEscapeTables(
+ config,
+ enableKeyEscaping,
+ enableValueEscaping,
+ false /* escapingForWriter */,
+ &KeyEscapeTable_,
+ &ValueEscapeTable_);
+}
+
+void TYamrDelimitedBaseParser::Read(TStringBuf data)
+{
+ auto current = data.begin();
+ auto end = data.end();
+ while (current != end) {
+ current = Consume(current, end);
+ }
+}
+
+void TYamrDelimitedBaseParser::Finish()
+{
+ if (ExpectingEscapedChar) {
+ ThrowIncorrectFormat();
+ }
+ if (State == EState::InsideKey && !CurrentToken.empty()) {
+ ThrowIncorrectFormat();
+ }
+ if (State == EState::InsideSubkey) {
+ ProcessSubkey(CurrentToken);
+ ProcessValue("");
+ }
+ if (State == EState::InsideValue) {
+ ProcessValue(CurrentToken);
+ }
+}
+
+TString TYamrDelimitedBaseParser::GetContext() const
+{
+ TString result;
+ const char* last = ContextBuffer + BufferPosition;
+ if (Offset >= ContextBufferSize) {
+ result.append(last, ContextBuffer + ContextBufferSize);
+ }
+ result.append(ContextBuffer, last);
+ return result;
+}
+
+IAttributeDictionaryPtr TYamrDelimitedBaseParser::GetDebugInfo() const
+{
+ auto result = CreateEphemeralAttributes();
+ result->Set("context", GetContext());
+ result->Set("offset", Offset);
+ result->Set("record", Record);
+ result->Set("state", State);
+ return result;
+}
+
+void TYamrDelimitedBaseParser::ProcessTableSwitch(TStringBuf tableIndex)
+{
+ YT_ASSERT(!ExpectingEscapedChar);
+ YT_ASSERT(State == EState::InsideKey);
+ i64 value;
+ try {
+ value = FromString<i64>(tableIndex);
+ } catch (const std::exception& ex) {
+ TString tableIndexString(tableIndex);
+ if (tableIndex.size() > ContextBufferSize) {
+ tableIndexString = TString(tableIndex.SubStr(0, ContextBufferSize)) + "...truncated...";
+ }
+ THROW_ERROR_EXCEPTION("YAMR line %Qv cannot be parsed as a table switch; did you forget a record separator?",
+ tableIndexString)
+ << *GetDebugInfo();
+ }
+ Consumer->SwitchTable(value);
+}
+
+void TYamrDelimitedBaseParser::ProcessKey(TStringBuf key)
+{
+ YT_ASSERT(!ExpectingEscapedChar);
+ YT_ASSERT(State == EState::InsideKey);
+ Consumer->ConsumeKey(key);
+ State = Config_->HasSubkey ? EState::InsideSubkey : EState::InsideValue;
+}
+
+void TYamrDelimitedBaseParser::ProcessSubkey(TStringBuf subkey)
+{
+ YT_ASSERT(!ExpectingEscapedChar);
+ YT_ASSERT(State == EState::InsideSubkey);
+ Consumer->ConsumeSubkey(subkey);
+ State = EState::InsideValue;
+}
+
+void TYamrDelimitedBaseParser::ProcessSubkeyBadFormat(TStringBuf subkey)
+{
+ YT_ASSERT(!ExpectingEscapedChar);
+ YT_ASSERT(State == EState::InsideSubkey);
+ Consumer->ConsumeSubkey(subkey);
+ Consumer->ConsumeValue("");
+ State = EState::InsideKey;
+}
+
+void TYamrDelimitedBaseParser::ProcessValue(TStringBuf value)
+{
+ YT_ASSERT(!ExpectingEscapedChar);
+ YT_ASSERT(State == EState::InsideValue);
+ Consumer->ConsumeValue(value);
+ State = EState::InsideKey;
+ Record += 1;
+}
+
+const char* TYamrDelimitedBaseParser::ProcessToken(
+ void (TYamrDelimitedBaseParser::*processor)(TStringBuf value),
+ const char* begin,
+ const char* next)
+{
+ if (CurrentToken.empty()) {
+ (this->*processor)(TStringBuf(begin, next));
+ } else {
+ CurrentToken.append(begin, next);
+ (this->*processor)(CurrentToken);
+ CurrentToken.clear();
+ }
+
+ OnRangeConsumed(next, next + 1);
+ return next + 1;
+}
+
+const char* TYamrDelimitedBaseParser::FindNext(const char* begin, const char* end, const TEscapeTable& escapeTable)
+{
+ const char* next = escapeTable.FindNext(begin, end);
+ OnRangeConsumed(begin, next);
+ return next;
+}
+
+const char* TYamrDelimitedBaseParser::Consume(const char* begin, const char* end)
+{
+ if (ExpectingEscapedChar) {
+ // Read and unescape.
+ CurrentToken.append(EscapeBackward[static_cast<ui8>(*begin)]);
+ ExpectingEscapedChar = false;
+ OnRangeConsumed(begin, begin + 1);
+ return begin + 1;
+ }
+
+ YT_ASSERT(!ExpectingEscapedChar);
+
+ const char* next = FindNext(begin, end, State == EState::InsideValue ? ValueEscapeTable_ : KeyEscapeTable_);
+ if (next == end) {
+ CurrentToken.append(begin, next);
+ if (CurrentToken.length() > MaxRowWeightLimit) {
+ THROW_ERROR_EXCEPTION(
+ "YAMR token length limit exceeded: %v > %v",
+ CurrentToken.length(),
+ MaxRowWeightLimit)
+ << *GetDebugInfo();
+ }
+ return end;
+ }
+
+ if (*next == Config_->EscapingSymbol) {
+ CurrentToken.append(begin, next);
+ OnRangeConsumed(next, next + 1);
+ ExpectingEscapedChar = true;
+ return next + 1;
+ }
+
+ switch (State) {
+ case EState::InsideKey:
+ if (*next == Config_->RecordSeparator) {
+ return ProcessToken(&TYamrDelimitedBaseParser::ProcessTableSwitch, begin, next);
+ }
+
+ if (*next == Config_->FieldSeparator) {
+ return ProcessToken(&TYamrDelimitedBaseParser::ProcessKey, begin, next);
+ }
+ break;
+
+ case EState::InsideSubkey:
+ if (*next == Config_->FieldSeparator) {
+ return ProcessToken(&TYamrDelimitedBaseParser::ProcessSubkey, begin, next);
+ }
+
+ if (*next == Config_->RecordSeparator) {
+ // See yamr_parser_ut.cpp: IncompleteRows() for details.
+ return ProcessToken(&TYamrDelimitedBaseParser::ProcessSubkeyBadFormat, begin, next);
+ }
+ break;
+
+ case EState::InsideValue:
+ if (*next == Config_->RecordSeparator) {
+ return ProcessToken(&TYamrDelimitedBaseParser::ProcessValue, begin, next);
+ }
+ break;
+ }
+
+ ThrowIncorrectFormat();
+
+ // To suppress warnings.
+ YT_ABORT();
+}
+
+void TYamrDelimitedBaseParser::ThrowIncorrectFormat() const
+{
+ THROW_ERROR_EXCEPTION("Unexpected symbol in YAMR row: expected %Qv, found %Qv",
+ EscapeC(Config_->FieldSeparator),
+ EscapeC(Config_->RecordSeparator))
+ << *GetDebugInfo();
+}
+
+void TYamrDelimitedBaseParser::OnRangeConsumed(const char* begin, const char* end)
+{
+ Offset += end - begin;
+ auto current = std::max(begin, end - ContextBufferSize);
+ for ( ; current < end; ++current) {
+ AppendToContextBuffer(*current);
+ }
+}
+
+void TYamrDelimitedBaseParser::AppendToContextBuffer(char symbol)
+{
+ ContextBuffer[BufferPosition] = symbol;
+ ++BufferPosition;
+ if (BufferPosition >= ContextBufferSize) {
+ BufferPosition -= ContextBufferSize;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYamrLenvalBaseParser::TYamrLenvalBaseParser(
+ IYamrConsumerPtr consumer,
+ bool enableSubkey,
+ bool enableEom)
+ : Consumer(consumer)
+ , EnableSubkey(enableSubkey)
+ , EnableEom(enableEom)
+{ }
+
+void TYamrLenvalBaseParser::Read(TStringBuf data)
+{
+ auto current = data.begin();
+ while (current != data.end()) {
+ current = Consume(current, data.end());
+ }
+}
+
+void TYamrLenvalBaseParser::Finish()
+{
+ if (EnableEom && !MetEom) {
+ THROW_ERROR_EXCEPTION("Missing EOM marker in the stream");
+ }
+
+ if (State == EState::InsideValue && !ReadingLength && BytesToRead == 0) {
+ Consumer->ConsumeValue(CurrentToken);
+ return;
+ }
+
+ if (!(State == EState::InsideKey && ReadingLength && BytesToRead == 4)) {
+ THROW_ERROR_EXCEPTION("Premature end of stream");
+ }
+}
+
+const char* TYamrLenvalBaseParser::Consume(const char* begin, const char* end)
+{
+ if (MetEom) {
+ THROW_ERROR_EXCEPTION("Garbage after EOM marker");
+ }
+ if (ReadingLength) {
+ return ConsumeLength(begin, end);
+ } else {
+ return ConsumeData(begin, end);
+ }
+}
+
+const char* TYamrLenvalBaseParser::ConsumeInt(const char* begin, const char* end, int length)
+{
+ const char* current = begin;
+ while (BytesToRead != 0 && current != end) {
+ Union.Bytes[length - BytesToRead] = *current;
+ ++current;
+ --BytesToRead;
+ }
+ return current;
+}
+
+const char* TYamrLenvalBaseParser::ConsumeLength(const char* begin, const char* end)
+{
+ YT_ASSERT(ReadingLength);
+ const char* next = ConsumeInt(begin, end, 4);
+
+ if (BytesToRead == 0) {
+ ReadingLength = false;
+ BytesToRead = static_cast<ui32>(Union.Value);
+ }
+
+ if (BytesToRead == static_cast<ui32>(-1)) {
+ if (State == EState::InsideKey) {
+ BytesToRead = 4;
+ State = EState::InsideTableSwitch;
+ } else {
+ THROW_ERROR_EXCEPTION("Unexpected table switch instruction");
+ }
+ } else if (BytesToRead == static_cast<ui32>(-5)) {
+ if (!EnableEom) {
+ THROW_ERROR_EXCEPTION("Unexpected EOM marker");
+ }
+ if (State == EState::InsideKey) {
+ BytesToRead = 8;
+ State = EState::InsideEom;
+ } else {
+ THROW_ERROR_EXCEPTION("Unexpected EOM marker");
+ }
+ }
+
+ if (BytesToRead > MaxRowWeightLimit) {
+ THROW_ERROR_EXCEPTION(
+ "YAMR lenval length limit exceeded: %v > %v",
+ BytesToRead,
+ MaxRowWeightLimit);
+ }
+
+ return next;
+}
+
+const char* TYamrLenvalBaseParser::ConsumeData(const char* begin, const char* end)
+{
+ if (State == EState::InsideTableSwitch) {
+ YT_ASSERT(CurrentToken.empty());
+ const char* next = ConsumeInt(begin, end, 4);
+
+ if (BytesToRead == 0) {
+ Consumer->SwitchTable(static_cast<ui32>(Union.Value));
+ State = EState::InsideKey;
+ ReadingLength = true;
+ BytesToRead = 4;
+ }
+
+ return next;
+ } else if (State == EState::InsideEom) {
+ const char* next = ConsumeInt(begin, end, 8);
+
+ if (BytesToRead == 0) {
+ MetEom = true;
+ if (Union.Value != RowCount) {
+ THROW_ERROR_EXCEPTION("Row count mismatch")
+ << TErrorAttribute("eom_marker_row_count", Union.Value)
+ << TErrorAttribute("actual_row_count", RowCount);
+ }
+ State = EState::InsideKey;
+ ReadingLength = true;
+ BytesToRead = 4;
+ }
+
+ return next;
+ }
+
+ // Consume ordinary string tokens.
+ TStringBuf data;
+ const char* current = begin + BytesToRead;
+
+ if (current > end) {
+ CurrentToken.append(begin, end);
+ BytesToRead -= (end - begin);
+ YT_ASSERT(BytesToRead > 0);
+ return end;
+ }
+
+ if (CurrentToken.empty()) {
+ data = TStringBuf(begin, current);
+ } else {
+ CurrentToken.append(begin, current);
+ data = CurrentToken;
+ }
+
+ switch (State) {
+ case EState::InsideKey:
+ ++RowCount;
+ Consumer->ConsumeKey(data);
+ State = EnableSubkey ? EState::InsideSubkey : EState::InsideValue;
+ break;
+ case EState::InsideSubkey:
+ Consumer->ConsumeSubkey(data);
+ State = EState::InsideValue;
+ break;
+ case EState::InsideValue:
+ Consumer->ConsumeValue(data);
+ State = EState::InsideKey;
+ break;
+ default:
+ YT_ABORT();
+ }
+
+ CurrentToken.clear();
+ ReadingLength = true;
+ BytesToRead = 4;
+
+ return current;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamr_parser_base.h b/yt/yt/client/formats/yamr_parser_base.h
new file mode 100644
index 0000000000..240a2855a8
--- /dev/null
+++ b/yt/yt/client/formats/yamr_parser_base.h
@@ -0,0 +1,160 @@
+#pragma once
+
+#include "parser.h"
+
+#include "escape.h"
+
+#include <yt/yt/core/ytree/attributes.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IYamrConsumer
+ : public virtual TRefCounted
+{
+ virtual void ConsumeKey(TStringBuf key) = 0;
+ virtual void ConsumeSubkey(TStringBuf subkey) = 0;
+ virtual void ConsumeValue(TStringBuf value) = 0;
+ virtual void SwitchTable(i64 tableIndex) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IYamrConsumer)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYamrConsumerBase
+ : public IYamrConsumer
+{
+public:
+ explicit TYamrConsumerBase(NYson::IYsonConsumer* consumer);
+ virtual void SwitchTable(i64 tableIndex) override;
+
+protected:
+ NYson::IYsonConsumer* Consumer;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EYamrDelimitedBaseParserState,
+ (InsideKey)
+ (InsideSubkey)
+ (InsideValue)
+);
+
+class TYamrDelimitedBaseParser
+ : public IParser
+{
+public:
+ TYamrDelimitedBaseParser(
+ IYamrConsumerPtr consumer,
+ const TYamrFormatConfigBasePtr& config,
+ bool enableKeyEscaping,
+ bool enableValueEscaping);
+
+ virtual void Read(TStringBuf data) override;
+ virtual void Finish() override;
+
+private:
+ using EState = EYamrDelimitedBaseParserState;
+
+ const char* Consume(const char* begin, const char* end);
+
+ void ProcessKey(TStringBuf key);
+ void ProcessSubkey(TStringBuf subkey);
+ void ProcessSubkeyBadFormat(TStringBuf subkey);
+ void ProcessValue(TStringBuf value);
+ void ProcessTableSwitch(TStringBuf tableIndex);
+
+ const char* ProcessToken(
+ void (TYamrDelimitedBaseParser::*processor)(TStringBuf value),
+ const char* begin,
+ const char* next);
+
+ const char* FindNext(const char* begin, const char* end, const TEscapeTable& escapeTable);
+
+ void ThrowIncorrectFormat() const;
+
+ void OnRangeConsumed(const char* begin, const char* end);
+ void AppendToContextBuffer(char symbol);
+
+ TString GetContext() const;
+ NYTree::IAttributeDictionaryPtr GetDebugInfo() const;
+
+ IYamrConsumerPtr Consumer;
+
+ TYamrFormatConfigBasePtr Config_;
+
+ EState State;
+
+ bool ExpectingEscapedChar;
+
+ TString CurrentToken;
+
+ // Diagnostic Info
+ i64 Offset;
+ i64 Record;
+ i32 BufferPosition;
+
+ static const int ContextBufferSize = 64;
+ char ContextBuffer[ContextBufferSize];
+
+ TEscapeTable KeyEscapeTable_;
+ TEscapeTable ValueEscapeTable_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EYamrLenvalBaseParserState,
+ (InsideTableSwitch)
+ (InsideKey)
+ (InsideSubkey)
+ (InsideValue)
+ (InsideEom)
+);
+
+class TYamrLenvalBaseParser
+ : public IParser
+{
+public:
+ TYamrLenvalBaseParser(
+ IYamrConsumerPtr consumer,
+ bool enableSubkey,
+ bool enableEom);
+
+ virtual void Read(TStringBuf data) override;
+ virtual void Finish() override;
+
+private:
+ using EState = EYamrLenvalBaseParserState;
+
+ const char* Consume(const char* begin, const char* end);
+ const char* ConsumeInt(const char* begin, const char* end, int length);
+ const char* ConsumeLength(const char* begin, const char* end);
+ const char* ConsumeData(const char* begin, const char* end);
+
+ IYamrConsumerPtr Consumer;
+
+ bool EnableSubkey;
+
+ TString CurrentToken;
+
+ union {
+ ui64 Value;
+ char Bytes[8];
+ } Union;
+
+ bool ReadingLength = true;
+ ui32 BytesToRead = 4;
+
+ EState State = EState::InsideKey;
+
+ bool EnableEom;
+ bool MetEom = false;
+ ui64 RowCount = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamr_writer.cpp b/yt/yt/client/formats/yamr_writer.cpp
new file mode 100644
index 0000000000..4408b35568
--- /dev/null
+++ b/yt/yt/client/formats/yamr_writer.cpp
@@ -0,0 +1,211 @@
+#include "yamr_writer.h"
+
+#include "escape.h"
+#include "helpers.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/format.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYTree;
+using namespace NYson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessWriterForYamr
+ : public TSchemalessWriterForYamrBase
+{
+public:
+ TSchemalessWriterForYamr(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount,
+ TYamrFormatConfigPtr config = New<TYamrFormatConfig>())
+ : TSchemalessWriterForYamrBase(
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount,
+ config)
+ {
+ ConfigureEscapeTables(
+ config,
+ config->EnableEscaping /* enableKeyEscaping */,
+ config->EnableEscaping /* enableValueEscaping */,
+ true /* escapingForWriter */,
+ &KeyEscapeTable_,
+ &ValueEscapeTable_);
+
+ try {
+ KeyId_ = nameTable->GetIdOrRegisterName(config->Key);
+ SubkeyId_ = Config_->HasSubkey ? nameTable->GetIdOrRegisterName(config->Subkey) : -1;
+ ValueId_ = nameTable->GetIdOrRegisterName(config->Value);
+ } catch (const std::exception& ex) {
+ auto error = TError("Failed to add columns to name table for YAMR format")
+ << ex;
+ RegisterError(error);
+ }
+ }
+
+private:
+ TEscapeTable KeyEscapeTable_;
+ TEscapeTable ValueEscapeTable_;
+
+ int KeyId_;
+ int SubkeyId_;
+ int ValueId_;
+
+ // ISchemalessFormatWriter override.
+ void DoWrite(TRange<TUnversionedRow> rows) override
+ {
+ TableIndexWasWritten_ = false;
+
+ auto* stream = GetOutputStream();
+ // This nasty line is needed to use Config as TYamrFormatConfigPtr
+ // without extra serializing/deserializing.
+ TYamrFormatConfigPtr config(static_cast<TYamrFormatConfig*>(Config_.Get()));
+
+ int rowCount = static_cast<int>(rows.Size());
+ for (int index = 0; index < rowCount; index++) {
+ auto row = rows[index];
+ if (CheckKeySwitch(row, index + 1 == rowCount /* isLastRow */)) {
+ YT_VERIFY(config->Lenval);
+ WritePod(*stream, static_cast<ui32>(-2));
+ }
+
+ WriteControlAttributes(row);
+
+ std::optional<TStringBuf> key;
+ std::optional<TStringBuf> subkey;
+ std::optional<TStringBuf> value;
+
+ for (const auto* item = row.Begin(); item != row.End(); ++item) {
+ if (item->Id == KeyId_) {
+ ValidateColumnType(item, TStringBuf("key"));
+ key = item->AsStringBuf();
+ } else if (item->Id == SubkeyId_) {
+ if (item->Type != EValueType::Null) {
+ ValidateColumnType(item, TStringBuf("subkey"));
+ subkey = item->AsStringBuf();
+ }
+ } else if (item->Id == ValueId_) {
+ ValidateColumnType(item, TStringBuf("value"));
+ value = item->AsStringBuf();
+ } else {
+ // Ignore unknown columns.
+ continue;
+ }
+ }
+
+ if (!key) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Missing key column %Qv in YAMR record",
+ config->Key);
+ }
+
+ if (!subkey) {
+ subkey = "";
+ }
+
+ if (!value) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Missing value column %Qv in YAMR record",
+ config->Value);
+ }
+
+ if (!config->Lenval) {
+ EscapeAndWrite(*key, stream, KeyEscapeTable_);
+ stream->Write(config->FieldSeparator);
+ if (config->HasSubkey) {
+ EscapeAndWrite(*subkey, stream, KeyEscapeTable_);
+ stream->Write(config->FieldSeparator);
+ }
+ EscapeAndWrite(*value, stream, ValueEscapeTable_);
+ stream->Write(config->RecordSeparator);
+ } else {
+ WriteInLenvalMode(*key);
+ if (config->HasSubkey) {
+ WriteInLenvalMode(*subkey);
+ }
+ WriteInLenvalMode(*value);
+ }
+
+ TryFlushBuffer(false);
+ }
+
+ TryFlushBuffer(true);
+ }
+
+ void ValidateColumnType(const TUnversionedValue* value, TStringBuf columnName)
+ {
+ if (value->Type != EValueType::String) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Wrong type %Qlv of column %Qv in YAMR record",
+ value->Type,
+ columnName);
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamr(
+ TYamrFormatConfigPtr config,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ if (controlAttributesConfig->EnableKeySwitch && !config->Lenval) {
+ THROW_ERROR_EXCEPTION("Key switches are not supported in text YAMR format");
+ }
+
+ if (controlAttributesConfig->EnableRangeIndex && !config->Lenval) {
+ THROW_ERROR_EXCEPTION("Range indices are not supported in text YAMR format");
+ }
+
+ if (controlAttributesConfig->EnableEndOfStream) {
+ THROW_ERROR_EXCEPTION("End of stream control attribute is not supported in YAMR format");
+ }
+
+ return New<TSchemalessWriterForYamr>(
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount,
+ config);
+}
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamr(
+ const IAttributeDictionary& attributes,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ try {
+ auto config = ConvertTo<TYamrFormatConfigPtr>(&attributes);
+ return CreateSchemalessWriterForYamr(
+ config,
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ } catch (const std::exception& exc) {
+ THROW_ERROR_EXCEPTION(EErrorCode::InvalidFormat, "Failed to parse config for YAMR format") << exc;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamr_writer.h b/yt/yt/client/formats/yamr_writer.h
new file mode 100644
index 0000000000..d381b1d4bd
--- /dev/null
+++ b/yt/yt/client/formats/yamr_writer.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+#include "helpers.h"
+#include "yamr_writer_base.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamr(
+ TYamrFormatConfigPtr config,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamr(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamr_writer_base.cpp b/yt/yt/client/formats/yamr_writer_base.cpp
new file mode 100644
index 0000000000..1dc73de0d8
--- /dev/null
+++ b/yt/yt/client/formats/yamr_writer_base.cpp
@@ -0,0 +1,90 @@
+#include "yamr_writer_base.h"
+#include "yamr_writer.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/format.h>
+
+namespace NYT::NFormats {
+
+using namespace NConcurrency;
+using namespace NYTree;
+using namespace NYson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSchemalessWriterForYamrBase::TSchemalessWriterForYamrBase(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount,
+ TYamrFormatConfigBasePtr config)
+ : TSchemalessFormatWriterBase(
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount)
+ , Config_(config)
+{ }
+
+void TSchemalessWriterForYamrBase::WriteInLenvalMode(TStringBuf value)
+{
+ auto* stream = GetOutputStream();
+ WritePod(*stream, static_cast<ui32>(value.size()));
+ stream->Write(value);
+}
+
+void TSchemalessWriterForYamrBase::WriteTableIndex(i64 tableIndex)
+{
+ TableIndexWasWritten_ = true;
+ CurrentTableIndex_ = tableIndex;
+
+ auto* stream = GetOutputStream();
+
+ if (!Config_->EnableTableIndex) {
+ // Silently ignore table switches.
+ return;
+ }
+
+ if (Config_->Lenval) {
+ WritePod(*stream, static_cast<ui32>(-1));
+ WritePod(*stream, static_cast<ui32>(tableIndex));
+ } else {
+ stream->Write(ToString(tableIndex));
+ stream->Write(Config_->RecordSeparator);
+ }
+}
+
+void TSchemalessWriterForYamrBase::WriteRangeIndex(i64 rangeIndex)
+{
+ YT_VERIFY(Config_->Lenval);
+
+ auto* stream = GetOutputStream();
+ WritePod(*stream, static_cast<ui32>(-3));
+ WritePod(*stream, static_cast<ui32>(rangeIndex));
+}
+
+void TSchemalessWriterForYamrBase::WriteRowIndex(i64 rowIndex)
+{
+ auto* stream = GetOutputStream();
+ if (Config_->Lenval) {
+ WritePod(*stream, static_cast<ui32>(-4));
+ WritePod(*stream, static_cast<ui64>(rowIndex));
+ } else {
+ if (!TableIndexWasWritten_) {
+ stream->Write(ToString(CurrentTableIndex_));
+ stream->Write(Config_->RecordSeparator);
+ }
+ stream->Write(ToString(rowIndex));
+ stream->Write(Config_->RecordSeparator);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamr_writer_base.h b/yt/yt/client/formats/yamr_writer_base.h
new file mode 100644
index 0000000000..d8483f6636
--- /dev/null
+++ b/yt/yt/client/formats/yamr_writer_base.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+#include "helpers.h"
+#include "escape.h"
+#include "schemaless_writer_adapter.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessWriterForYamrBase
+ : public TSchemalessFormatWriterBase
+{
+public:
+ TSchemalessWriterForYamrBase(
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount,
+ TYamrFormatConfigBasePtr config);
+
+protected:
+ TYamrFormatConfigBasePtr Config_;
+ bool TableIndexWasWritten_ = false;
+ int CurrentTableIndex_ = 0;
+
+ void WriteInLenvalMode(TStringBuf value);
+
+ void WriteTableIndex(i64 tableIndex) override;
+ void WriteRangeIndex(i64 rangeIndex) override;
+ void WriteRowIndex(i64 rowIndex) override;
+};
+
+DEFINE_REFCOUNTED_TYPE(TSchemalessWriterForYamrBase)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yamred_dsv_parser.cpp b/yt/yt/client/formats/yamred_dsv_parser.cpp
new file mode 100644
index 0000000000..476e760ea6
--- /dev/null
+++ b/yt/yt/client/formats/yamred_dsv_parser.cpp
@@ -0,0 +1,130 @@
+#include "yamred_dsv_parser.h"
+#include "dsv_parser.h"
+#include "yamr_parser_base.h"
+
+namespace NYT::NFormats {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+class TYamredDsvParserConsumer
+ : public TYamrConsumerBase
+{
+public:
+ TYamredDsvParserConsumer(IYsonConsumer* consumer, TYamredDsvFormatConfigPtr config)
+ : TYamrConsumerBase(consumer)
+ , Config(config)
+ , DsvParser(CreateParserForDsv(consumer, ConvertTo<TDsvFormatConfigPtr>(Config), /*wrapWithMap*/ false))
+ { }
+
+ void ConsumeKey(TStringBuf key) override
+ {
+ Consumer->OnListItem();
+ Consumer->OnBeginMap();
+ ConsumeFields(Config->KeyColumnNames, key);
+ }
+
+ void ConsumeSubkey(TStringBuf subkey) override
+ {
+ ConsumeFields(Config->SubkeyColumnNames, subkey);
+ }
+
+ void ConsumeValue(TStringBuf value) override
+ {
+ DsvParser->Read(value);
+ DsvParser->Finish();
+ Consumer->OnEndMap();
+ }
+
+private:
+ TYamredDsvFormatConfigPtr Config;
+ std::unique_ptr<IParser> DsvParser;
+
+ void ConsumeFields(
+ const std::vector<TString>& fieldNames,
+ TStringBuf wholeField)
+ {
+ static const char* emptyString = "";
+ char delimiter = Config->YamrKeysSeparator;
+
+ std::vector<TStringBuf> fields;
+ if (wholeField.length() == 0) {
+ fields = std::vector<TStringBuf>(1, TStringBuf(emptyString));
+ } else {
+ size_t position = 0;
+ while (position != TStringBuf::npos) {
+ size_t newPosition = (fields.size() + 1 == fieldNames.size())
+ ? TStringBuf::npos
+ : wholeField.find(delimiter, position);
+ fields.push_back(wholeField.substr(position, newPosition));
+ position = (newPosition == TStringBuf::npos) ? TStringBuf::npos : newPosition + 1;
+ }
+ }
+
+ if (fields.size() != fieldNames.size()) {
+ THROW_ERROR_EXCEPTION("Invalid number of key fields in YAMRed DSV: expected %v, actual %v",
+ fieldNames.size(),
+ fields.size());
+ }
+
+ for (int i = 0; i < static_cast<int>(fields.size()); ++i) {
+ Consumer->OnKeyedItem(fieldNames[i]);
+ Consumer->OnStringScalar(fields[i]);
+ }
+ }
+
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForYamredDsv(
+ IYsonConsumer* consumer,
+ TYamredDsvFormatConfigPtr config)
+{
+ auto parserConsumer = New<TYamredDsvParserConsumer>(consumer, config);
+
+ return config->Lenval
+ ? std::unique_ptr<IParser>(
+ new TYamrLenvalBaseParser(
+ parserConsumer,
+ config->HasSubkey,
+ config->EnableEom))
+ : std::unique_ptr<IParser>(
+ new TYamrDelimitedBaseParser(
+ parserConsumer,
+ config,
+ config->EnableEscaping /* enableKeyEscaping */,
+ false /* enableValueEscaping */));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseYamredDsv(
+ IInputStream* input,
+ IYsonConsumer* consumer,
+ TYamredDsvFormatConfigPtr config)
+{
+ auto parser = CreateParserForYamredDsv(consumer, config);
+ Parse(input, parser.get());
+}
+
+void ParseYamredDsv(
+ TStringBuf data,
+ IYsonConsumer* consumer,
+ TYamredDsvFormatConfigPtr config)
+{
+ auto parser = CreateParserForYamredDsv(consumer, config);
+ parser->Read(data);
+ parser->Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/yamred_dsv_parser.h b/yt/yt/client/formats/yamred_dsv_parser.h
new file mode 100644
index 0000000000..e260ee6b30
--- /dev/null
+++ b/yt/yt/client/formats/yamred_dsv_parser.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForYamredDsv(
+ NYson::IYsonConsumer* consumer,
+ TYamredDsvFormatConfigPtr config = nullptr);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ParseYamredDsv(
+ IInputStream* input,
+ NYson::IYsonConsumer* consumer,
+ TYamredDsvFormatConfigPtr config = nullptr);
+
+void ParseYamredDsv(
+ TStringBuf data,
+ NYson::IYsonConsumer* consumer,
+ TYamredDsvFormatConfigPtr config = nullptr);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/yamred_dsv_writer.cpp b/yt/yt/client/formats/yamred_dsv_writer.cpp
new file mode 100644
index 0000000000..956e771732
--- /dev/null
+++ b/yt/yt/client/formats/yamred_dsv_writer.cpp
@@ -0,0 +1,251 @@
+#include "yamred_dsv_writer.h"
+
+#include "escape.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+
+namespace NYT::NFormats {
+
+using namespace NYTree;
+using namespace NTableClient;
+using namespace NYson;
+using namespace NTableClient;
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessWriterForYamredDsv
+ : public TSchemalessWriterForYamrBase
+{
+public:
+ TSchemalessWriterForYamredDsv(
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount,
+ TYamredDsvFormatConfigPtr config = New<TYamredDsvFormatConfig>())
+ : TSchemalessWriterForYamrBase(
+ nameTable,
+ std::move(output),
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount,
+ config)
+ , Config_(config)
+ {
+ ConfigureEscapeTables(config, true /* addCarriageReturn */, &KeyEscapeTable_, &ValueEscapeTable_);
+ try {
+ // We register column names in order to have correct size of NameTable_ in DoWrite method.
+ for (const auto& columnName : config->KeyColumnNames) {
+ KeyColumnIds_.push_back(nameTable->GetIdOrRegisterName(columnName));
+ }
+
+ for (const auto& columnName : config->SubkeyColumnNames) {
+ SubkeyColumnIds_.push_back(nameTable->GetIdOrRegisterName(columnName));
+ }
+ } catch (const std::exception& ex) {
+ auto error = TError("Failed to add columns to name table for YAMRed DSV format")
+ << ex;
+ RegisterError(error);
+ }
+
+ UpdateEscapedColumnNames();
+ }
+
+private:
+ const TYamredDsvFormatConfigPtr Config_;
+
+ std::vector<const TUnversionedValue*> RowValues_;
+
+ std::vector<int> KeyColumnIds_;
+ std::vector<int> SubkeyColumnIds_;
+ std::vector<TString> EscapedColumnNames_;
+
+ // In lenval mode key, subkey and value are first written into
+ // this buffer in order to calculate their length.
+ TBlobOutput LenvalBuffer_;
+
+ // We capture size of name table at the beginning of #WriteRows
+ // and refer to the captured value, since name table may change asynchronously.
+ int NameTableSize_ = 0;
+
+ TEscapeTable KeyEscapeTable_;
+ TEscapeTable ValueEscapeTable_;
+
+
+ // ISchemalessFormatWriter implementation
+ void DoWrite(TRange<TUnversionedRow> rows) override
+ {
+ TableIndexWasWritten_ = false;
+
+ auto* stream = GetOutputStream();
+
+ UpdateEscapedColumnNames();
+ RowValues_.resize(NameTableSize_);
+ // Invariant: at the beginning of each loop iteration RowValues contains
+ // nullptr in each element.
+ int rowCount = static_cast<int>(rows.Size());
+ for (int index = 0; index < rowCount; index++) {
+ auto row = rows[index];
+ if (CheckKeySwitch(row, index + 1 == rowCount /* isLastRow */)) {
+ YT_VERIFY(!Config_->Lenval);
+ WritePod(*stream, static_cast<ui32>(-2));
+ }
+
+ WriteControlAttributes(row);
+
+ for (const auto* item = row.Begin(); item != row.End(); ++item) {
+ if (IsSystemColumnId(item->Id) || item->Type == EValueType::Null) {
+ // Ignore null values and system columns.
+ continue;
+ }
+ YT_VERIFY(item->Id < NameTableSize_);
+ RowValues_[item->Id] = item;
+ }
+
+ WriteYamrKey(KeyColumnIds_);
+ if (Config_->HasSubkey) {
+ WriteYamrKey(SubkeyColumnIds_);
+ } else {
+ // Due to YAMRed DSV format logic, when there is no subkey, but still some
+ // columns are marked as subkey columns, we should explicitly remove them
+ // from the row (i. e. don't print as a rest of values in YAMR value column).
+ for (int id : SubkeyColumnIds_)
+ RowValues_[id] = nullptr;
+ }
+ WriteYamrValue();
+ TryFlushBuffer(false);
+ }
+ TryFlushBuffer(true);
+ }
+
+ void WriteYamrKey(const std::vector<int>& columnIds)
+ {
+ auto* stream = Config_->Lenval ? &LenvalBuffer_ : GetOutputStream();
+
+ bool firstColumn = true;
+ for (int id : columnIds) {
+ if (!firstColumn) {
+ stream->Write(Config_->YamrKeysSeparator);
+ } else {
+ firstColumn = false;
+ }
+ if (!RowValues_[id]) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::FormatCannotRepresentRow, "Key column %Qv is missing",
+ NameTableReader_->GetName(id));
+ }
+ WriteUnversionedValue(*RowValues_[id], stream, ValueEscapeTable_);
+ RowValues_[id] = nullptr;
+ }
+
+ if (Config_->Lenval) {
+ WritePod(*GetOutputStream(), static_cast<ui32>(LenvalBuffer_.Size()));
+ GetOutputStream()->Write(LenvalBuffer_.Begin(), LenvalBuffer_.Size());
+ LenvalBuffer_.Clear();
+ } else {
+ GetOutputStream()->Write(Config_->FieldSeparator);
+ }
+ }
+
+ void WriteYamrValue()
+ {
+ auto* stream = Config_->Lenval ? &LenvalBuffer_ : GetOutputStream();
+
+ bool firstColumn = true;
+ for (int id = 0; id < NameTableSize_; ++id) {
+ const auto* value = RowValues_[id];
+ if (!value) {
+ continue;
+ }
+ bool skip = Config_->SkipUnsupportedTypesInValue && IsAnyOrComposite(value->Type);
+ if (!skip) {
+ if (!firstColumn) {
+ stream->Write(Config_->FieldSeparator);
+ } else {
+ firstColumn = false;
+ }
+ stream->Write(EscapedColumnNames_[id]);
+ stream->Write(Config_->KeyValueSeparator);
+ WriteUnversionedValue(*RowValues_[id], stream, ValueEscapeTable_);
+ }
+ RowValues_[id] = nullptr;
+ }
+
+ if (Config_->Lenval) {
+ WritePod(*GetOutputStream(), static_cast<ui32>(LenvalBuffer_.Size()));
+ GetOutputStream()->Write(LenvalBuffer_.Begin(), LenvalBuffer_.Size());
+ LenvalBuffer_.Clear();
+ } else {
+ GetOutputStream()->Write(Config_->RecordSeparator);
+ }
+ }
+
+ void UpdateEscapedColumnNames()
+ {
+ // We store escaped column names in order to not re-escape them each time we write a column name.
+ NameTableSize_ = NameTableReader_->GetSize();
+ EscapedColumnNames_.reserve(NameTableSize_);
+ for (int columnIndex = EscapedColumnNames_.size(); columnIndex < NameTableSize_; ++columnIndex) {
+ EscapedColumnNames_.emplace_back(Escape(NameTableReader_->GetName(columnIndex), KeyEscapeTable_));
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamredDsv(
+ TYamredDsvFormatConfigPtr config,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ if (controlAttributesConfig->EnableKeySwitch && !config->Lenval) {
+ THROW_ERROR_EXCEPTION("Key switches are not supported in text YAMRed DSV format");
+ }
+
+ if (controlAttributesConfig->EnableRangeIndex && !config->Lenval) {
+ THROW_ERROR_EXCEPTION("Range indices are not supported in text YAMRed DSV format");
+ }
+
+ if (controlAttributesConfig->EnableEndOfStream) {
+ THROW_ERROR_EXCEPTION("End of stream control attribute is not supported in YAMRed DSV format");
+ }
+
+ return New<TSchemalessWriterForYamredDsv>(
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount,
+ config);
+}
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamredDsv(
+ const IAttributeDictionary& attributes,
+ TNameTablePtr nameTable,
+ IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount)
+{
+ try {
+ auto config = ConvertTo<TYamredDsvFormatConfigPtr>(&attributes);
+ return CreateSchemalessWriterForYamredDsv(
+ config,
+ nameTable,
+ output,
+ enableContextSaving,
+ controlAttributesConfig,
+ keyColumnCount);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(EErrorCode::InvalidFormat, "Failed to parse config for YAMRed DSV format") << ex;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/yamred_dsv_writer.h b/yt/yt/client/formats/yamred_dsv_writer.h
new file mode 100644
index 0000000000..6d9050abe3
--- /dev/null
+++ b/yt/yt/client/formats/yamred_dsv_writer.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+#include "helpers.h"
+#include "yamr_writer_base.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamredDsv(
+ TYamredDsvFormatConfigPtr config,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+ISchemalessFormatWriterPtr CreateSchemalessWriterForYamredDsv(
+ const NYTree::IAttributeDictionary& attributes,
+ NTableClient::TNameTablePtr nameTable,
+ NConcurrency::IAsyncOutputStreamPtr output,
+ bool enableContextSaving,
+ TControlAttributesConfigPtr controlAttributesConfig,
+ int keyColumnCount);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
+
diff --git a/yt/yt/client/formats/yql_yson_converter.cpp b/yt/yt/client/formats/yql_yson_converter.cpp
new file mode 100644
index 0000000000..dc57a1a69a
--- /dev/null
+++ b/yt/yt/client/formats/yql_yson_converter.cpp
@@ -0,0 +1,855 @@
+#include "yql_yson_converter.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/helpers.h>
+
+#include <yt/yt/library/decimal/decimal.h>
+
+#include <yt/yt/core/json/json_writer.h>
+
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/core/yson/token_writer.h>
+
+#include <library/cpp/string_utils/base64/base64.h>
+
+#include <util/charset/utf8.h>
+
+#include <util/string/cast.h>
+
+#include <util/stream/buffer.h>
+
+namespace NYT::NFormats {
+
+using namespace NJson;
+using namespace NTableClient;
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TStringBuf TruncateUtf8(TStringBuf string, i64 limit)
+{
+ auto begin = reinterpret_cast<const unsigned char*>(string.cbegin());
+ auto end = reinterpret_cast<const unsigned char*>(string.cend());
+ auto it = begin + std::max<i64>(0, limit);
+ if (it < end) {
+ wchar32 rune;
+ size_t runeLength;
+ while (it >= begin && SafeReadUTF8Char(rune, runeLength, it, end) != RECODE_OK) {
+ --it;
+ }
+ }
+ return string.Trunc(it - begin);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYqlJsonWriter
+ : public TRefCounted
+{
+public:
+ explicit TYqlJsonWriter(NJson::IJsonWriter* underlying);
+
+ void OnInt64Scalar(i64 value);
+ void OnUint64Scalar(ui64 value);
+ void OnDoubleScalar(double value);
+ void OnBooleanScalar(bool value);
+ void OnEntity();
+
+ void OnBeginList();
+ void OnListItem();
+ void OnEndList();
+
+ void OnBeginMap();
+ void OnKeyedItem(TStringBuf key);
+ void OnEndMap();
+
+ void OnStringScalarNoWeightLimit(TStringBuf value);
+ void OnStringScalarWeightLimited(TStringBuf value, i64 limit);
+ void TransferYsonWeightLimited(const std::function<void(NYson::TCheckedInDebugYsonTokenWriter*)>& callback, i64 limit);
+
+ ui64 GetWrittenByteCount() const;
+
+private:
+ NJson::IJsonWriter* const Underlying_;
+ TBuffer Buffer_;
+ std::unique_ptr<NJson::IJsonConsumer> AnyWriter_;
+
+public:
+ static constexpr TStringBuf KeyValue = "val";
+ static constexpr TStringBuf KeyIncomplete = "inc";
+ static constexpr TStringBuf KeyBase64 = "b64";
+
+private:
+ void OnStringScalarImpl(TStringBuf value, bool incomplete = false, bool base64 = false);
+ template <typename TFun>
+ void WriteWithWrapping(TFun fun, bool incomplete, bool base64, bool forceMap = false);
+};
+
+DECLARE_REFCOUNTED_CLASS(TYqlJsonWriter)
+DEFINE_REFCOUNTED_TYPE(TYqlJsonWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYqlJsonWriter::TYqlJsonWriter(IJsonWriter* underlying)
+ : Underlying_(underlying)
+{
+ auto config = New<TJsonFormatConfig>();
+ config->Stringify = true;
+ config->AnnotateWithTypes = true;
+ config->EncodeUtf8 = true;
+ AnyWriter_ = CreateJsonConsumer(Underlying_, EYsonType::Node, std::move(config));
+}
+
+void TYqlJsonWriter::OnInt64Scalar(i64 value)
+{
+ Underlying_->OnStringScalar(ToString(value));
+}
+
+void TYqlJsonWriter::OnUint64Scalar(ui64 value)
+{
+ Underlying_->OnStringScalar(ToString(value));
+}
+
+void TYqlJsonWriter::OnDoubleScalar(double value)
+{
+ Underlying_->OnStringScalar(::FloatToString(value));
+}
+
+void TYqlJsonWriter::OnBooleanScalar(bool value)
+{
+ Underlying_->OnBooleanScalar(value);
+}
+
+void TYqlJsonWriter::TYqlJsonWriter::OnEntity()
+{
+ Underlying_->OnEntity();
+}
+
+void TYqlJsonWriter::OnBeginList()
+{
+ Underlying_->OnBeginList();
+}
+
+void TYqlJsonWriter::OnListItem()
+{
+ Underlying_->OnListItem();
+}
+
+void TYqlJsonWriter::OnEndList()
+{
+ Underlying_->OnEndList();
+}
+
+void TYqlJsonWriter::OnBeginMap()
+{
+ Underlying_->OnBeginMap();
+}
+
+void TYqlJsonWriter::OnKeyedItem(TStringBuf key)
+{
+ Underlying_->OnKeyedItem(key);
+}
+
+void TYqlJsonWriter::OnEndMap()
+{
+ Underlying_->OnEndMap();
+}
+
+void TYqlJsonWriter::OnStringScalarNoWeightLimit(TStringBuf value)
+{
+ bool base64 = !IsUtf(value);
+ OnStringScalarImpl(value, false, base64);
+}
+
+void TYqlJsonWriter::OnStringScalarWeightLimited(TStringBuf value, i64 limit)
+{
+ TStringBuf valueToWrite = value;
+ auto incomplete = false;
+ auto base64 = false;
+ if (IsUtf(valueToWrite)) {
+ if (static_cast<i64>(valueToWrite.Size()) > limit) {
+ valueToWrite = TruncateUtf8(valueToWrite, limit);
+ incomplete = true;
+ }
+ } else {
+ base64 = true;
+ auto maxEncodedSize = Base64EncodeBufSize(valueToWrite.Size());
+ if (static_cast<i64>(maxEncodedSize) > limit) {
+ auto truncatedLen = (limit - 1) / 4 * 3;
+ incomplete = (truncatedLen < static_cast<i64>(valueToWrite.Size()));
+ valueToWrite.Trunc(truncatedLen);
+ }
+ Buffer_.Resize(Base64EncodeBufSize(valueToWrite.Size()));
+ valueToWrite = Base64Encode(valueToWrite, Buffer_.Begin());
+ }
+ OnStringScalarImpl(valueToWrite, incomplete, base64);
+}
+
+void TYqlJsonWriter::TransferYsonWeightLimited(
+ const std::function<void(TCheckedInDebugYsonTokenWriter*)>& callback,
+ i64 limit)
+{
+ Buffer_.Clear();
+ {
+ TBufferOutput output(Buffer_);
+ TCheckedInDebugYsonTokenWriter writer(&output);
+ callback(&writer);
+ }
+ auto yson = TStringBuf(Buffer_.Begin(), Buffer_.End());
+ if (static_cast<i64>(yson.Size()) > limit) {
+ OnStringScalarImpl("", /* incomplete */ true, /* base64 */ false);
+ } else {
+ WriteWithWrapping(
+ [&] {
+ AnyWriter_->OnRaw(yson, EYsonType::Node);
+ },
+ /* incomplete */ false,
+ /* base64 */ false,
+ /* forceMap */ true);
+ }
+}
+
+void TYqlJsonWriter::OnStringScalarImpl(
+ TStringBuf value,
+ bool incomplete,
+ bool base64)
+{
+ WriteWithWrapping(
+ [&] {
+ Underlying_->OnStringScalar(value);
+ },
+ incomplete,
+ base64);
+}
+
+template <typename TFun>
+void TYqlJsonWriter::WriteWithWrapping(
+ TFun fun,
+ bool incomplete,
+ bool base64,
+ bool forceMap)
+{
+ auto needMap = (incomplete || base64 || forceMap);
+ if (needMap) {
+ Underlying_->OnBeginMap();
+ }
+ if (incomplete) {
+ Underlying_->OnKeyedItem(KeyIncomplete);
+ Underlying_->OnBooleanScalar(incomplete);
+ }
+ if (base64) {
+ Underlying_->OnKeyedItem(KeyBase64);
+ Underlying_->OnBooleanScalar(base64);
+ }
+
+ if (needMap) {
+ Underlying_->OnKeyedItem(KeyValue);
+ }
+ fun();
+
+ if (needMap) {
+ Underlying_->OnEndMap();
+ }
+}
+
+ui64 TYqlJsonWriter::GetWrittenByteCount() const
+{
+ return Underlying_->GetWrittenByteCount();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TWeightLimitedYsonToYqlConverter = std::function<
+ void(NYson::TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 totalLimit)>;
+using TWeightLimitedUnversionedValueToYqlConverter = std::function<
+ void(TUnversionedValue value, TYqlJsonWriter* consumer, i64 totalLimit)>;
+
+static TWeightLimitedYsonToYqlConverter CreateWeightLimitedYsonToYqlConverter(
+ const TLogicalTypePtr& logicalType,
+ TYqlConverterConfigPtr config);
+
+static void EnsureYsonItemTypeEqual(const TYsonItem& item, EYsonItemType type)
+{
+ if (Y_UNLIKELY(item.GetType() != type)) {
+ THROW_ERROR_EXCEPTION("YSON item type mismatch: expected %Qlv, got %Qlv",
+ type,
+ item.GetType());
+ }
+}
+
+static void EnsureYsonItemTypeNotEqual(const TYsonItem& item, EYsonItemType type)
+{
+ if (Y_UNLIKELY(item.GetType() == type)) {
+ THROW_ERROR_EXCEPTION("Unexpected YSON item type %Qlv",
+ type);
+ }
+}
+
+template <EValueType PhysicalType>
+class TSimpleYsonToYqlConverter
+{
+public:
+ explicit TSimpleYsonToYqlConverter(TYqlConverterConfigPtr config)
+ : Config_(std::move(config))
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ auto getStringWeightLimit = [&] {
+ auto bytesLeft = totalLimit - static_cast<i64>(consumer->GetWrittenByteCount());
+ if (Config_->StringWeightLimit) {
+ bytesLeft = std::min(bytesLeft, *Config_->StringWeightLimit);
+ }
+ return bytesLeft;
+ };
+
+ const auto& item = cursor->GetCurrent();
+ if constexpr (PhysicalType == EValueType::Int64) {
+ EnsureYsonItemTypeEqual(item, EYsonItemType::Int64Value);
+ consumer->OnInt64Scalar(item.UncheckedAsInt64());
+ cursor->Next();
+ } else if constexpr (PhysicalType == EValueType::Uint64) {
+ EnsureYsonItemTypeEqual(item, EYsonItemType::Uint64Value);
+ consumer->OnUint64Scalar(item.UncheckedAsUint64());
+ cursor->Next();
+ } else if constexpr (PhysicalType == EValueType::String) {
+ EnsureYsonItemTypeEqual(item, EYsonItemType::StringValue);
+ consumer->OnStringScalarWeightLimited(
+ item.UncheckedAsString(),
+ getStringWeightLimit());
+ cursor->Next();
+ } else if constexpr (PhysicalType == EValueType::Double) {
+ EnsureYsonItemTypeEqual(item, EYsonItemType::DoubleValue);
+ consumer->OnDoubleScalar(item.UncheckedAsDouble());
+ cursor->Next();
+ } else if constexpr (PhysicalType == EValueType::Boolean) {
+ EnsureYsonItemTypeEqual(item, EYsonItemType::BooleanValue);
+ consumer->OnBooleanScalar(item.UncheckedAsBoolean());
+ cursor->Next();
+ } else if constexpr (PhysicalType == EValueType::Null) {
+ EnsureYsonItemTypeEqual(item, EYsonItemType::EntityValue);
+ consumer->OnEntity();
+ cursor->Next();
+ } else if constexpr (PhysicalType == EValueType::Any || PhysicalType == EValueType::Composite) {
+ consumer->TransferYsonWeightLimited(
+ [cursor] (TCheckedInDebugYsonTokenWriter* writer) {
+ cursor->TransferComplexValue(writer);
+ },
+ getStringWeightLimit());
+ } else {
+ // Silly assert instead of forbidden |static_assert(false)|.
+ static_assert(PhysicalType == EValueType::Int64, "Unexpected physical type");
+ }
+ }
+
+private:
+ TYqlConverterConfigPtr Config_;
+};
+
+TWeightLimitedYsonToYqlConverter CreateSimpleTypeYsonToYqlConverter(
+ EValueType physicalType,
+ TYqlConverterConfigPtr config)
+{
+ switch (physicalType) {
+ case EValueType::Int64:
+ return TSimpleYsonToYqlConverter<EValueType::Int64>(std::move(config));
+ case EValueType::Uint64:
+ return TSimpleYsonToYqlConverter<EValueType::Uint64>(std::move(config));
+ case EValueType::String:
+ return TSimpleYsonToYqlConverter<EValueType::String>(std::move(config));
+ case EValueType::Double:
+ return TSimpleYsonToYqlConverter<EValueType::Double>(std::move(config));
+ case EValueType::Boolean:
+ return TSimpleYsonToYqlConverter<EValueType::Boolean>(std::move(config));
+ case EValueType::Null:
+ return TSimpleYsonToYqlConverter<EValueType::Null>(std::move(config));
+ case EValueType::Any:
+ return TSimpleYsonToYqlConverter<EValueType::Any>(std::move(config));
+ case EValueType::Composite:
+ return TSimpleYsonToYqlConverter<EValueType::Composite>(std::move(config));
+
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+ }
+ ThrowUnexpectedValueType(physicalType);
+}
+
+class TDecimalYsonToYqlConverter
+{
+public:
+ TDecimalYsonToYqlConverter(int precision, int scale)
+ : Precision_(precision)
+ , Scale_(scale)
+ { }
+
+ void operator () (TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 /*totalLimit*/) const
+ {
+ const auto& item = cursor->GetCurrent();
+ EnsureYsonItemTypeEqual(item, EYsonItemType::StringValue);
+
+ char buffer[NDecimal::TDecimal::MaxTextSize];
+ const auto binaryDecimal = item.UncheckedAsString();
+ const auto textDecimal = NDecimal::TDecimal::BinaryToText(binaryDecimal, Precision_, Scale_, buffer, sizeof(buffer));
+ consumer->OnStringScalarNoWeightLimit(textDecimal);
+
+ cursor->Next();
+ }
+
+private:
+ const int Precision_;
+ const int Scale_;
+};
+
+class TListYsonToYqlConverter
+{
+public:
+ TListYsonToYqlConverter(const TListLogicalType& type, TYqlConverterConfigPtr config)
+ : ElementConverter_(CreateWeightLimitedYsonToYqlConverter(type.GetElement(), std::move(config)))
+ { }
+
+ void operator() (TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::BeginList);
+ cursor->Next();
+
+ consumer->OnBeginMap();
+ consumer->OnKeyedItem(TYqlJsonWriter::KeyValue);
+ consumer->OnBeginList();
+ auto incomplete = false;
+ while (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ if (static_cast<i64>(consumer->GetWrittenByteCount()) >= totalLimit) {
+ incomplete = true;
+ break;
+ }
+ consumer->OnListItem();
+ ElementConverter_(cursor, consumer, totalLimit);
+ }
+ if (incomplete) {
+ while (cursor->GetCurrent().GetType() != EYsonItemType::EndList) {
+ cursor->SkipComplexValue();
+ }
+ }
+ consumer->OnEndList();
+ if (incomplete) {
+ consumer->OnKeyedItem(TYqlJsonWriter::KeyIncomplete);
+ consumer->OnBooleanScalar(true);
+ }
+ consumer->OnEndMap();
+ cursor->Next();
+ }
+
+private:
+ const TWeightLimitedYsonToYqlConverter ElementConverter_;
+};
+
+void ConvertSequence(
+ TYsonPullParserCursor* cursor,
+ TYqlJsonWriter* consumer,
+ const std::vector<TWeightLimitedYsonToYqlConverter>& converters,
+ i64 totalLimit)
+{
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::BeginList);
+ cursor->Next();
+
+ consumer->OnBeginList();
+ for (const auto& converter : converters) {
+ EnsureYsonItemTypeNotEqual(cursor->GetCurrent(), EYsonItemType::EndList);
+ converter(cursor, consumer, totalLimit);
+ }
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::EndList);
+ consumer->OnEndList();
+
+ cursor->Next();
+}
+
+class TStructYsonToYqlConverter
+{
+public:
+ explicit TStructYsonToYqlConverter(const TStructLogicalType& type, TYqlConverterConfigPtr config)
+ {
+ for (const auto& field : type.GetFields()) {
+ FieldConverters_.push_back(CreateWeightLimitedYsonToYqlConverter(field.Type, config));
+ }
+ }
+
+ void operator() (TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ ConvertSequence(cursor, consumer, FieldConverters_, totalLimit);
+ }
+
+private:
+ std::vector<TWeightLimitedYsonToYqlConverter> FieldConverters_;
+};
+
+class TTupleYsonToYqlConverter
+{
+public:
+ TTupleYsonToYqlConverter(const TTupleLogicalType& type, TYqlConverterConfigPtr config)
+ {
+ for (const auto& element : type.GetElements()) {
+ ElementConverters_.push_back(CreateWeightLimitedYsonToYqlConverter(element, config));
+ }
+ }
+
+ void operator() (TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ ConvertSequence(cursor, consumer, ElementConverters_, totalLimit);
+ }
+
+private:
+ std::vector<TWeightLimitedYsonToYqlConverter> ElementConverters_;
+};
+
+class TOptionalYsonToYqlConverter
+{
+public:
+ TOptionalYsonToYqlConverter(const TOptionalLogicalType& type, TYqlConverterConfigPtr config)
+ : IsElementNullable_(type.GetElement()->IsNullable())
+ , ElementConverter_(CreateWeightLimitedYsonToYqlConverter(type.GetElement(), std::move(config)))
+ { }
+
+ void operator() (TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ if (cursor->GetCurrent().GetType() == EYsonItemType::EntityValue) {
+ consumer->OnEntity();
+ cursor->Next();
+ return;
+ }
+
+ consumer->OnBeginList();
+ consumer->OnListItem();
+ if (IsElementNullable_) {
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::BeginList);
+ cursor->Next();
+ EnsureYsonItemTypeNotEqual(cursor->GetCurrent(), EYsonItemType::EndList);
+ ElementConverter_(cursor, consumer, totalLimit);
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::EndList);
+ cursor->Next();
+ } else {
+ ElementConverter_(cursor, consumer, totalLimit);
+ }
+ consumer->OnEndList();
+ }
+
+private:
+ const bool IsElementNullable_;
+ const TWeightLimitedYsonToYqlConverter ElementConverter_;
+};
+
+class TVariantYsonToYqlConverter
+{
+public:
+ TVariantYsonToYqlConverter(const TVariantTupleLogicalType& type, TYqlConverterConfigPtr config)
+ {
+ for (const auto& element : type.GetElements()) {
+ ElementConverters_.push_back(CreateWeightLimitedYsonToYqlConverter(element, config));
+ }
+ }
+
+ TVariantYsonToYqlConverter(const TVariantStructLogicalType& type, TYqlConverterConfigPtr config)
+ {
+ for (const auto& field : type.GetFields()) {
+ ElementConverters_.push_back(CreateWeightLimitedYsonToYqlConverter(field.Type, config));
+ }
+ }
+
+ void operator() (TYsonPullParserCursor* cursor, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::BeginList);
+ cursor->Next();
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::Int64Value);
+ const auto alternativeIndex = cursor->GetCurrent().UncheckedAsInt64();
+ if (Y_UNLIKELY(!(0 <= alternativeIndex && alternativeIndex < static_cast<int>(ElementConverters_.size())))) {
+ THROW_ERROR_EXCEPTION("Alternative index is out of bounds: expected it to be in [%v, %v), got %v",
+ 0,
+ ElementConverters_.size(),
+ alternativeIndex);
+ }
+ cursor->Next();
+ consumer->OnBeginList();
+
+ consumer->OnListItem();
+ consumer->OnInt64Scalar(alternativeIndex);
+
+ consumer->OnListItem();
+ ElementConverters_[alternativeIndex](cursor, consumer, totalLimit);
+
+ EnsureYsonItemTypeEqual(cursor->GetCurrent(), EYsonItemType::EndList);
+ consumer->OnEndList();
+
+ cursor->Next();
+ }
+
+private:
+ std::vector<TWeightLimitedYsonToYqlConverter> ElementConverters_;
+};
+
+static TWeightLimitedYsonToYqlConverter CreateWeightLimitedYsonToYqlConverter(
+ const TLogicalTypePtr& logicalType,
+ TYqlConverterConfigPtr config)
+{
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return CreateSimpleTypeYsonToYqlConverter(
+ GetPhysicalType(logicalType->AsSimpleTypeRef().GetElement()),
+ std::move(config));
+ case ELogicalMetatype::Decimal:
+ return TDecimalYsonToYqlConverter(
+ logicalType->AsDecimalTypeRef().GetPrecision(),
+ logicalType->AsDecimalTypeRef().GetScale());
+ case ELogicalMetatype::List:
+ return TListYsonToYqlConverter(logicalType->AsListTypeRef(), std::move(config));
+ case ELogicalMetatype::Struct:
+ return TStructYsonToYqlConverter(logicalType->AsStructTypeRef(), std::move(config));
+ case ELogicalMetatype::Optional:
+ return TOptionalYsonToYqlConverter(logicalType->AsOptionalTypeRef(), std::move(config));
+ case ELogicalMetatype::Tuple:
+ return TTupleYsonToYqlConverter(logicalType->AsTupleTypeRef(), std::move(config));
+ case ELogicalMetatype::VariantStruct:
+ return TVariantYsonToYqlConverter(logicalType->AsVariantStructTypeRef(), std::move(config));
+ case ELogicalMetatype::VariantTuple:
+ return TVariantYsonToYqlConverter(logicalType->AsVariantTupleTypeRef(), std::move(config));
+ case ELogicalMetatype::Dict: {
+ // Converter is identical to list<tuple<Key, Value>>.
+ auto listOfTuples = ListLogicalType(
+ TupleLogicalType({
+ logicalType->AsDictTypeRef().GetKey(),
+ logicalType->AsDictTypeRef().GetValue()}));
+ return TListYsonToYqlConverter(
+ listOfTuples->AsListTypeRef(),
+ std::move(config));
+ }
+ case ELogicalMetatype::Tagged:
+ return CreateWeightLimitedYsonToYqlConverter(
+ logicalType->AsTaggedTypeRef().GetElement(),
+ std::move(config));
+ }
+ YT_ABORT();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <EValueType Type, bool Required>
+class TSimpleUnversionedValueToYqlConverter
+{
+public:
+ void operator () (TUnversionedValue value, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ if constexpr (!Required) {
+ if (value.Type == EValueType::Null) {
+ consumer->OnEntity();
+ return;
+ }
+ consumer->OnBeginList();
+ }
+
+ if (Y_UNLIKELY(Type != EValueType::Any && value.Type != Type)) {
+ THROW_ERROR_EXCEPTION("Bad value type: expected %Qlv, got %Qlv",
+ Type,
+ value.Type);
+ }
+ if constexpr (Type == EValueType::Int64) {
+ consumer->OnInt64Scalar(value.Data.Int64);
+ } else if constexpr (Type == EValueType::Uint64) {
+ consumer->OnUint64Scalar(value.Data.Uint64);
+ } else if constexpr (Type == EValueType::String) {
+ auto bytesLeft = totalLimit - static_cast<i64>(consumer->GetWrittenByteCount());
+ consumer->OnStringScalarWeightLimited(
+ value.AsStringBuf(),
+ bytesLeft);
+ } else if constexpr (Type == EValueType::Double) {
+ consumer->OnDoubleScalar(value.Data.Double);
+ } else if constexpr (Type == EValueType::Boolean) {
+ consumer->OnBooleanScalar(value.Data.Boolean);
+ } else if constexpr (Type == EValueType::Any || Type == EValueType::Composite) {
+ auto bytesLeft = totalLimit - static_cast<i64>(consumer->GetWrittenByteCount());
+ consumer->TransferYsonWeightLimited(
+ [value] (TCheckedInDebugYsonTokenWriter* tokenWriter) {
+ UnversionedValueToYson(value, tokenWriter);
+ },
+ bytesLeft);
+ } else if constexpr (Type == EValueType::Null) {
+ consumer->OnEntity();
+ } else {
+ // Silly assert instead of uncompilable |static_assert(false)|.
+ static_assert(Type == EValueType::Int64, "Unexpected value type");
+ }
+
+ if constexpr (!Required) {
+ consumer->OnEndList();
+ }
+ }
+};
+
+class TDecimalUnversionedValueToYqlConverter
+{
+public:
+ TDecimalUnversionedValueToYqlConverter(int precision, int scale, bool isNullable)
+ : Precision_(precision)
+ , Scale_(scale)
+ , IsNullable_(isNullable)
+ { }
+
+ void operator () (TUnversionedValue value, TYqlJsonWriter* consumer, i64 /*totalLimit*/) const
+ {
+ if (IsNullable_) {
+ if (value.Type == EValueType::Null) {
+ consumer->OnEntity();
+ return;
+ }
+ consumer->OnBeginList();
+ }
+
+ char buffer[NDecimal::TDecimal::MaxTextSize];
+ const auto binaryDecimal = value.AsStringBuf();
+ const auto textDecimal = NDecimal::TDecimal::BinaryToText(binaryDecimal, Precision_, Scale_, buffer, sizeof(buffer));
+ consumer->OnStringScalarNoWeightLimit(textDecimal);
+
+ if (IsNullable_) {
+ consumer->OnEndList();
+ }
+ }
+
+private:
+ const int Precision_;
+ const int Scale_;
+ const bool IsNullable_;
+};
+
+class TComplexUnversionedValueToYqlConverter
+{
+public:
+ TComplexUnversionedValueToYqlConverter(const TLogicalTypePtr& logicalType, TYqlConverterConfigPtr config)
+ : Type_(logicalType)
+ , Converter_(CreateWeightLimitedYsonToYqlConverter(logicalType, std::move(config)))
+ , IsNullable_(logicalType->IsNullable())
+ { }
+
+ void operator () (TUnversionedValue value, TYqlJsonWriter* consumer, i64 totalLimit)
+ {
+ if (value.Type == EValueType::Null) {
+ if (Y_UNLIKELY(!IsNullable_)) {
+ THROW_ERROR_EXCEPTION("Unexpected value type %Qlv for non-nullable type %Qv",
+ EValueType::Null,
+ NTableClient::ToString(*Type_));
+ }
+ consumer->OnEntity();
+ return;
+ }
+ if (Y_UNLIKELY(value.Type != EValueType::Composite)) {
+ THROW_ERROR_EXCEPTION("Bad value type: expected %Qlv, got %Qlv",
+ EValueType::Composite,
+ value.Type);
+ }
+ TMemoryInput input(value.Data.String, value.Length);
+ TYsonPullParser parser(&input, EYsonType::Node);
+ TYsonPullParserCursor cursor(&parser);
+ Converter_(&cursor, consumer, totalLimit);
+ }
+
+private:
+ const TLogicalTypePtr Type_;
+ const TWeightLimitedYsonToYqlConverter Converter_;
+ const bool IsNullable_;
+};
+
+static TWeightLimitedUnversionedValueToYqlConverter CreateSimpleUnversionedValueToYqlConverter(
+ EValueType type,
+ bool isRequired)
+{
+ switch (type) {
+#define CASE(type) \
+ case type: \
+ if (isRequired) { \
+ return TSimpleUnversionedValueToYqlConverter<type, true>{}; \
+ } else { \
+ return TSimpleUnversionedValueToYqlConverter<type, false>{}; \
+ }
+
+ CASE(EValueType::Int64)
+ CASE(EValueType::Uint64)
+ CASE(EValueType::String)
+ CASE(EValueType::Double)
+ CASE(EValueType::Boolean)
+ CASE(EValueType::Any)
+ CASE(EValueType::Composite)
+ CASE(EValueType::Null)
+#undef CASE
+
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+ }
+ ThrowUnexpectedValueType(type);
+}
+
+static TWeightLimitedUnversionedValueToYqlConverter CreateWeightLimitedUnversionedValueToYqlConverter(
+ const TLogicalTypePtr& logicalType,
+ TYqlConverterConfigPtr config)
+{
+ auto denullifiedLogicalType = DenullifyLogicalType(logicalType);
+ switch (denullifiedLogicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple: {
+ auto[simpleType, isRequired] = CastToV1Type(logicalType);
+ auto physicalType = GetPhysicalType(simpleType);
+ return CreateSimpleUnversionedValueToYqlConverter(physicalType, isRequired);
+ }
+ case ELogicalMetatype::Decimal: {
+ const int precision = denullifiedLogicalType->AsDecimalTypeRef().GetPrecision();
+ const int scale = denullifiedLogicalType->AsDecimalTypeRef().GetScale();
+ const int isNullable = logicalType->IsNullable();
+ return TDecimalUnversionedValueToYqlConverter(precision, scale, isNullable);
+ }
+ case ELogicalMetatype::Optional:
+ // NB. It's complex optional because we called DenullifyLogicalType
+ case ELogicalMetatype::List:
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantTuple:
+ case ELogicalMetatype::VariantStruct:
+ case ELogicalMetatype::Dict:
+ return TComplexUnversionedValueToYqlConverter(logicalType, std::move(config));
+ case ELogicalMetatype::Tagged:
+ // DenullifyLogicalType should have cleaned type of tagged types.
+ Y_FAIL();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYsonToYqlConverter CreateYsonToYqlConverter(
+ const TLogicalTypePtr& logicalType,
+ TYqlConverterConfigPtr config,
+ IJsonWriter* consumer)
+{
+ auto weightLimitedConverter = CreateWeightLimitedYsonToYqlConverter(logicalType, config);
+ return [=, consumer = New<TYqlJsonWriter>(consumer)] (TYsonPullParserCursor* cursor) mutable {
+ auto totalLimit = config->FieldWeightLimit
+ ? static_cast<i64>(consumer->GetWrittenByteCount()) + *config->FieldWeightLimit
+ : std::numeric_limits<i64>::max();
+ weightLimitedConverter(cursor, consumer.Get(), totalLimit);
+ };
+}
+
+TUnversionedValueToYqlConverter CreateUnversionedValueToYqlConverter(
+ const TLogicalTypePtr& logicalType,
+ TYqlConverterConfigPtr config,
+ IJsonWriter* consumer)
+{
+ auto weightLimitedConverter = CreateWeightLimitedUnversionedValueToYqlConverter(logicalType, config);
+ return [=, consumer = New<TYqlJsonWriter>(consumer)] (TUnversionedValue value) mutable {
+ auto totalLimit = config->FieldWeightLimit
+ ? static_cast<i64>(consumer->GetWrittenByteCount()) + *config->FieldWeightLimit
+ : std::numeric_limits<i64>::max();
+ weightLimitedConverter(value, consumer.Get(), totalLimit);
+ };
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yql_yson_converter.h b/yt/yt/client/formats/yql_yson_converter.h
new file mode 100644
index 0000000000..5caafea963
--- /dev/null
+++ b/yt/yt/client/formats/yql_yson_converter.h
@@ -0,0 +1,41 @@
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/json/public.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <util/generic/buffer.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TYqlConverterConfig
+ : public TRefCounted
+{
+ std::optional<i64> StringWeightLimit;
+ std::optional<i64> FieldWeightLimit;
+};
+DECLARE_REFCOUNTED_STRUCT(TYqlConverterConfig)
+DEFINE_REFCOUNTED_TYPE(TYqlConverterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TYsonToYqlConverter = std::function<void(NYson::TYsonPullParserCursor*)>;
+using TUnversionedValueToYqlConverter = std::function<void(NTableClient::TUnversionedValue)>;
+
+// Created converters throw exceptions on schema incompliance.
+TYsonToYqlConverter CreateYsonToYqlConverter(
+ const NTableClient::TLogicalTypePtr& logicalType,
+ TYqlConverterConfigPtr config,
+ NJson::IJsonWriter* writer);
+TUnversionedValueToYqlConverter CreateUnversionedValueToYqlConverter(
+ const NTableClient::TLogicalTypePtr& logicalType,
+ TYqlConverterConfigPtr config,
+ NJson::IJsonWriter* writer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yson_map_to_unversioned_value.cpp b/yt/yt/client/formats/yson_map_to_unversioned_value.cpp
new file mode 100644
index 0000000000..fced3477f5
--- /dev/null
+++ b/yt/yt/client/formats/yson_map_to_unversioned_value.cpp
@@ -0,0 +1,181 @@
+#include "yson_map_to_unversioned_value.h"
+
+namespace NYT::NFormats {
+
+using namespace NTableClient;
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYsonMapToUnversionedValueConverter::TYsonMapToUnversionedValueConverter(
+ const NComplexTypes::TYsonConverterConfig& config,
+ IValueConsumer* consumer)
+ : Consumer_(consumer)
+ , AllowUnknownColumns_(consumer->GetAllowUnknownColumns())
+ , NameTable_(consumer->GetNameTable())
+ , ColumnConsumer_(config, this)
+{ }
+
+void TYsonMapToUnversionedValueConverter::Reset()
+{
+ YT_VERIFY(!InsideValue_);
+}
+
+void TYsonMapToUnversionedValueConverter::OnStringScalar(TStringBuf value)
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnStringScalar(value);
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnInt64Scalar(i64 value)
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnInt64Scalar(value);
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnUint64Scalar(ui64 value)
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnUint64Scalar(value);
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnDoubleScalar(double value)
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnDoubleScalar(value);
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnBooleanScalar(bool value)
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnBooleanScalar(value);
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnEntity()
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnEntity();
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnBeginList()
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnBeginList();
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnListItem()
+{
+ YT_VERIFY(InsideValue_); // Should crash on BeginList()
+ ColumnConsumer_.OnListItem();
+}
+
+void TYsonMapToUnversionedValueConverter::OnBeginMap()
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnBeginMap();
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnKeyedItem(TStringBuf name)
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnKeyedItem(name);
+ } else {
+ InsideValue_ = true;
+ if (AllowUnknownColumns_) {
+ ColumnConsumer_.SetColumnIndex(NameTable_->GetIdOrRegisterName(name));
+ } else {
+ auto id = NameTable_->FindId(name);
+ if (!id) {
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::SchemaViolation, "No column %Qv in table schema",
+ name);
+ }
+ ColumnConsumer_.SetColumnIndex(*id);
+ }
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnEndMap()
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnEndMap();
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnBeginAttributes()
+{
+ if (Y_LIKELY(InsideValue_)) {
+ ColumnConsumer_.OnBeginAttributes();
+ } else {
+ THROW_ERROR_EXCEPTION("YSON map without attributes expected");
+ }
+}
+
+void TYsonMapToUnversionedValueConverter::OnEndList()
+{
+ YT_VERIFY(InsideValue_); // Should throw on BeginList().
+ ColumnConsumer_.OnEndList();
+}
+
+void TYsonMapToUnversionedValueConverter::OnEndAttributes()
+{
+ YT_VERIFY(InsideValue_); // Should throw on BeginAttributes()
+ ColumnConsumer_.OnEndAttributes();
+}
+
+const TNameTablePtr& TYsonMapToUnversionedValueConverter::GetNameTable() const
+{
+ return Consumer_->GetNameTable();
+}
+
+bool TYsonMapToUnversionedValueConverter::GetAllowUnknownColumns() const
+{
+ YT_ABORT();
+}
+
+void TYsonMapToUnversionedValueConverter::OnBeginRow()
+{
+ YT_ABORT();
+}
+
+void TYsonMapToUnversionedValueConverter::OnValue(const TUnversionedValue& value)
+{
+ InsideValue_ = false;
+ Consumer_->OnValue(value);
+}
+
+void TYsonMapToUnversionedValueConverter::OnEndRow()
+{
+ YT_ABORT();
+}
+
+const NTableClient::TTableSchemaPtr& TYsonMapToUnversionedValueConverter::GetSchema() const
+{
+ return Consumer_->GetSchema();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yson_map_to_unversioned_value.h b/yt/yt/client/formats/yson_map_to_unversioned_value.h
new file mode 100644
index 0000000000..1e53ad6c88
--- /dev/null
+++ b/yt/yt/client/formats/yson_map_to_unversioned_value.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/public.h>
+#include <yt/yt/client/table_client/table_consumer.h>
+#include <yt/yt/client/table_client/value_consumer.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYsonMapToUnversionedValueConverter
+ : public NYson::TYsonConsumerBase
+ , private NTableClient::IValueConsumer
+{
+public:
+ TYsonMapToUnversionedValueConverter(
+ const NComplexTypes::TYsonConverterConfig& config,
+ NTableClient::IValueConsumer* valueConsumer);
+
+ void Reset();
+ virtual void OnStringScalar(TStringBuf value) override;
+ virtual void OnInt64Scalar(i64 value) override;
+ virtual void OnUint64Scalar(ui64 value) override;
+ virtual void OnDoubleScalar(double value) override;
+ virtual void OnBooleanScalar(bool value) override;
+ virtual void OnEntity() override;
+ virtual void OnBeginList() override;
+ virtual void OnListItem() override;
+ virtual void OnBeginMap() override;
+ virtual void OnKeyedItem(TStringBuf name) override;
+ virtual void OnEndMap() override;
+ virtual void OnBeginAttributes() override;
+ virtual void OnEndList() override;
+ virtual void OnEndAttributes() override;
+
+private:
+ virtual const NTableClient::TNameTablePtr& GetNameTable() const override;
+ virtual bool GetAllowUnknownColumns() const override;
+ virtual void OnBeginRow() override;
+ virtual void OnValue(const NTableClient::TUnversionedValue& value) override;
+ virtual void OnEndRow() override;
+ virtual const NTableClient::TTableSchemaPtr& GetSchema() const override;
+
+private:
+ NTableClient::IValueConsumer* const Consumer_;
+ const bool AllowUnknownColumns_;
+ const NTableClient::TNameTablePtr NameTable_;
+
+ NTableClient::TYsonToUnversionedValueConverter ColumnConsumer_;
+ bool InsideValue_ = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yson_parser.cpp b/yt/yt/client/formats/yson_parser.cpp
new file mode 100644
index 0000000000..193b7caf31
--- /dev/null
+++ b/yt/yt/client/formats/yson_parser.cpp
@@ -0,0 +1,56 @@
+#include "yson_parser.h"
+#include "parser.h"
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/core/yson/parser.h>
+
+namespace NYT::NFormats {
+
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Wrapper around YSON parser that implements IParser interface.
+class TYsonParserAdapter
+ : public IParser
+{
+public:
+ TYsonParserAdapter(
+ IYsonConsumer* consumer,
+ EYsonType type,
+ bool enableLinePositionInfo)
+ : Parser(consumer, type, {
+ .EnableLinePositionInfo=enableLinePositionInfo,
+ .MemoryLimit=NTableClient::MaxRowWeightLimit
+ })
+ { }
+
+ void Read(TStringBuf data) override
+ {
+ Parser.Read(data);
+ }
+
+ void Finish() override
+ {
+ Parser.Finish();
+ }
+
+private:
+ TYsonParser Parser;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForYson(
+ IYsonConsumer* consumer,
+ EYsonType type,
+ bool enableLinePositionInfo)
+{
+ return std::unique_ptr<IParser>(new TYsonParserAdapter(consumer, type, enableLinePositionInfo));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/formats/yson_parser.h b/yt/yt/client/formats/yson_parser.h
new file mode 100644
index 0000000000..a6e4880b30
--- /dev/null
+++ b/yt/yt/client/formats/yson_parser.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/public.h>
+
+namespace NYT::NFormats {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IParser> CreateParserForYson(
+ NYson::IYsonConsumer* consumer,
+ NYson::EYsonType type = NYson::EYsonType::Node,
+ bool enableLinePositionInfo = false);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/hedging/cache.cpp b/yt/yt/client/hedging/cache.cpp
new file mode 100644
index 0000000000..cf725dbf1f
--- /dev/null
+++ b/yt/yt/client/hedging/cache.cpp
@@ -0,0 +1,93 @@
+#include "cache.h"
+#include "options.h"
+#include "rpc.h"
+
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <library/cpp/yt/threading/rw_spin_lock.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TConfig MakeClusterConfig(const TClustersConfig& clustersConfig, TStringBuf clusterUrl)
+{
+ auto [cluster, proxyRole] = ExtractClusterAndProxyRole(clusterUrl);
+ auto it = clustersConfig.GetClusterConfigs().find(cluster);
+ TConfig config = (it != clustersConfig.GetClusterConfigs().end()) ?
+ it->second : clustersConfig.GetDefaultConfig();
+ config.SetClusterName(ToString(cluster));
+ if (!proxyRole.empty()) {
+ config.SetProxyRole(ToString(proxyRole));
+ }
+ return config;
+}
+
+namespace {
+
+class TClientsCache
+ : public IClientsCache
+{
+public:
+ TClientsCache(const TClustersConfig& config, const NApi::TClientOptions& options)
+ : ClustersConfig_(config)
+ , Options_(options)
+ {}
+
+ NApi::IClientPtr GetClient(TStringBuf clusterUrl) override
+ {
+ {
+ auto guard = ReaderGuard(Lock_);
+ auto clientIt = Clients_.find(clusterUrl);
+ if (clientIt != Clients_.end()) {
+ return clientIt->second;
+ }
+ }
+
+ auto client = CreateClient(MakeClusterConfig(ClustersConfig_, clusterUrl), Options_);
+
+ auto guard = WriterGuard(Lock_);
+ return Clients_.try_emplace(clusterUrl, client).first->second;
+ }
+
+private:
+ TClustersConfig ClustersConfig_;
+ NApi::TClientOptions Options_;
+ NThreading::TReaderWriterSpinLock Lock_;
+ THashMap<TString, NApi::IClientPtr> Clients_;
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+IClientsCachePtr CreateClientsCache(const TClustersConfig& config, const NApi::TClientOptions& options)
+{
+ return New<TClientsCache>(config, options);
+}
+
+IClientsCachePtr CreateClientsCache(const TConfig& config, const NApi::TClientOptions& options)
+{
+ TClustersConfig clustersConfig;
+ *clustersConfig.MutableDefaultConfig() = config;
+ return CreateClientsCache(clustersConfig, options);
+}
+
+IClientsCachePtr CreateClientsCache(const TConfig& config)
+{
+ return CreateClientsCache(config, GetClientOpsFromEnvStatic());
+}
+
+IClientsCachePtr CreateClientsCache(const NApi::TClientOptions& options)
+{
+ return CreateClientsCache(TClustersConfig{}, options);
+}
+
+IClientsCachePtr CreateClientsCache()
+{
+ return CreateClientsCache(GetClientOpsFromEnvStatic());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/cache.h b/yt/yt/client/hedging/cache.h
new file mode 100644
index 0000000000..e957cecc7e
--- /dev/null
+++ b/yt/yt/client/hedging/cache.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <util/generic/strbuf.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+class TConfig;
+class TClustersConfig;
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Cache of clients per cluster.
+class IClientsCache
+ : public TRefCounted
+{
+public:
+ virtual NApi::IClientPtr GetClient(TStringBuf clusterUrl) = 0;
+};
+
+DECLARE_REFCOUNTED_TYPE(IClientsCache)
+DEFINE_REFCOUNTED_TYPE(IClientsCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Creates clients cache which explicitly given config. Server name is always overwritten with requested.
+IClientsCachePtr CreateClientsCache(const TClustersConfig& config, const NApi::TClientOptions& options);
+
+//! Creates clients cache which shares same config (except server name).
+IClientsCachePtr CreateClientsCache(const TConfig& config, const NApi::TClientOptions& options);
+
+//! Shortcut to use client options from env.
+IClientsCachePtr CreateClientsCache(const TConfig& config);
+
+//! Shortcut to create cache with custom options and proxy role.
+IClientsCachePtr CreateClientsCache(const NApi::TClientOptions& options);
+
+//! Shortcut to create cache with default config.
+IClientsCachePtr CreateClientsCache();
+
+
+//! Helper function to create one cluster config from cluster url and clusters config
+TConfig MakeClusterConfig(const TClustersConfig& config, TStringBuf clusterUrl);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/config.cpp b/yt/yt/client/hedging/config.cpp
new file mode 100644
index 0000000000..a0b009f1b3
--- /dev/null
+++ b/yt/yt/client/hedging/config.cpp
@@ -0,0 +1,67 @@
+#include "config.h"
+
+#include "counter.h"
+#include "rpc.h"
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TClientConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("connection", &TThis::Connection);
+ registrar.Parameter("initial_penalty", &TThis::InitialPenalty);
+}
+
+THedgingClientOptions::TClientOptions::TClientOptions(
+ NApi::IClientPtr client,
+ TString clusterName,
+ TDuration initialPenalty,
+ TCounterPtr counter)
+ : Client(std::move(client))
+ , ClusterName(std::move(clusterName))
+ , InitialPenalty(initialPenalty)
+ , Counter(std::move(counter))
+{ }
+
+THedgingClientOptions::TClientOptions::TClientOptions(
+ NApi::IClientPtr client,
+ TDuration initialPenalty,
+ TCounterPtr counter)
+ : TClientOptions(client, "default", initialPenalty, counter)
+{ }
+
+void THedgingClientOptions::Register(TRegistrar registrar)
+{
+ registrar.Parameter("client_configs", &TThis::ClientConfigs)
+ .Default();
+ registrar.Parameter("ban_penalty", &TThis::BanPenalty)
+ .Default(TDuration::MilliSeconds(1));
+ registrar.Parameter("ban_duration", &TThis::BanDuration)
+ .Default(TDuration::MilliSeconds(50));
+ registrar.Parameter("tags", &TThis::Tags)
+ .Default();
+
+ registrar.Postprocessor([] (TThis* config) {
+ NProfiling::TTagSet counterTagSet;
+
+ for (const auto& [tagName, tagValue] : config->Tags) {
+ counterTagSet.AddTag(NProfiling::TTag(tagName, tagValue));
+ }
+
+ config->Clients.reserve(config->Clients.size());
+ for (const auto& client : config->ClientConfigs) {
+ THROW_ERROR_EXCEPTION_UNLESS(client->Connection->ClusterUrl, "\"cluster_url\" must be set");
+ auto clusterUrl = client->Connection->ClusterUrl.value();
+ config->Clients.emplace_back(
+ CreateClient(client->Connection),
+ clusterUrl,
+ client->InitialPenalty,
+ New<TCounter>(counterTagSet.WithTag(NProfiling::TTag("yt_cluster", clusterUrl))));
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/config.h b/yt/yt/client/hedging/config.h
new file mode 100644
index 0000000000..b8e7c5dc2f
--- /dev/null
+++ b/yt/yt/client/hedging/config.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/rpc_proxy/config.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <util/generic/vector.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TClientConfig
+ : public virtual NYTree::TYsonStruct
+{
+ NApi::NRpcProxy::TConnectionConfigPtr Connection;
+ TDuration InitialPenalty;
+
+ REGISTER_YSON_STRUCT(TClientConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TClientConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! The options for hedging client.
+struct THedgingClientOptions
+ : public virtual NYTree::TYsonStructLite
+{
+ std::vector<TClientConfigPtr> ClientConfigs;
+
+ struct TClientOptions
+ {
+ TClientOptions(
+ NApi::IClientPtr client,
+ TString clusterName,
+ TDuration initialPenalty,
+ TCounterPtr counter = {});
+
+ TClientOptions(
+ NApi::IClientPtr client,
+ TDuration initialPenalty,
+ TCounterPtr counter = {});
+
+ NApi::IClientPtr Client;
+ TString ClusterName;
+ TDuration InitialPenalty;
+ TCounterPtr Counter;
+ };
+
+ TDuration BanPenalty;
+ TDuration BanDuration;
+ THashMap<TString, TString> Tags;
+
+ // This parameter is set on postprocessor.
+ TVector<TClientOptions> Clients;
+
+ REGISTER_YSON_STRUCT(THedgingClientOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/counter.cpp b/yt/yt/client/hedging/counter.cpp
new file mode 100644
index 0000000000..f8a88038f4
--- /dev/null
+++ b/yt/yt/client/hedging/counter.cpp
@@ -0,0 +1,51 @@
+#include "counter.h"
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto HedgingClientProfiler = NProfiling::TRegistry{"/hedging_client"}.WithHot();
+static const auto LagPenaltyProviderProfiler = NProfiling::TRegistry{"/lag_penalty_provider"}.WithHot();
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCounter::TCounter(const NProfiling::TRegistry& registry)
+ : SuccessRequestCount(registry.Counter("/requests_success"))
+ , CancelRequestCount(registry.Counter("/requests_cancel"))
+ , ErrorRequestCount(registry.Counter("/requests_error"))
+ , EffectivePenalty(registry.TimeGauge("/effective_penalty"))
+ , ExternalPenalty(registry.TimeGauge("/external_penalty"))
+ , RequestDuration(registry.TimeHistogram("/request_duration", TDuration::MilliSeconds(1), TDuration::MilliSeconds(70)))
+{
+}
+
+TCounter::TCounter(const TString& clusterName)
+ : TCounter(HedgingClientProfiler.WithTag("yt_cluster", clusterName))
+{
+}
+
+TCounter::TCounter(const NProfiling::TTagSet& tagSet)
+ : TCounter(HedgingClientProfiler.WithTags(tagSet))
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLagPenaltyProviderCounters::TLagPenaltyProviderCounters(const NProfiling::TRegistry& registry, const TVector<TString>& clusters)
+ : SuccessRequestCount(registry.Counter("/update_success"))
+ , ErrorRequestCount(registry.Counter("/update_error"))
+ , TotalTabletsCount(registry.Gauge("/tablets_total"))
+{
+ for (const auto& cluster : clusters) {
+ LagTabletsCount.emplace(cluster, registry.WithTag("yt_cluster", cluster).Gauge("/tablets_with_lag"));
+ }
+}
+
+TLagPenaltyProviderCounters::TLagPenaltyProviderCounters(const TString& tablePath, const TVector<TString>& clusterNames)
+ : TLagPenaltyProviderCounters(LagPenaltyProviderProfiler.WithTag("table", tablePath), clusterNames)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/counter.h b/yt/yt/client/hedging/counter.h
new file mode 100644
index 0000000000..1502df2475
--- /dev/null
+++ b/yt/yt/client/hedging/counter.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <yt/yt/core/misc/ref_counted.h>
+
+#include <yt/yt/library/profiling/sensor.h>
+
+#include <util/generic/hash.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// ! Counters which will be collected from yt-client.
+struct TCounter final
+{
+ explicit TCounter(const TString& clusterName);
+ explicit TCounter(const NProfiling::TTagSet& tagSet);
+ explicit TCounter(const NProfiling::TRegistry& registry);
+
+ NProfiling::TCounter SuccessRequestCount;
+ NProfiling::TCounter CancelRequestCount;
+ NProfiling::TCounter ErrorRequestCount;
+ NProfiling::TTimeGauge EffectivePenalty;
+ NProfiling::TTimeGauge ExternalPenalty;
+ NProfiling::TEventTimer RequestDuration;
+};
+
+DEFINE_REFCOUNTED_TYPE(TCounter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+// ! Counters for TReplicationLagPenaltyProvider.
+struct TLagPenaltyProviderCounters final
+{
+ TLagPenaltyProviderCounters(const NProfiling::TRegistry& registry, const TVector<TString>& clusters);
+ TLagPenaltyProviderCounters(const TString& tablePath, const TVector<TString>& replicaClusters);
+
+ NProfiling::TCounter SuccessRequestCount;
+ NProfiling::TCounter ErrorRequestCount;
+ THashMap<TString, NProfiling::TGauge> LagTabletsCount; // cluster -> # of tablets
+ NProfiling::TGauge TotalTabletsCount;
+};
+
+DEFINE_REFCOUNTED_TYPE(TLagPenaltyProviderCounters)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/hedging.cpp b/yt/yt/client/hedging/hedging.cpp
new file mode 100644
index 0000000000..acfd23bc63
--- /dev/null
+++ b/yt/yt/client/hedging/hedging.cpp
@@ -0,0 +1,284 @@
+#include "hedging.h"
+
+#include "cache.h"
+#include "counter.h"
+#include "logger.h"
+#include "rpc.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/client/misc/method_helpers.h>
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/core/logging/log.h>
+
+#include <yt/yt/core/profiling/timing.h>
+
+#include <yt/yt/core/rpc/dispatcher.h>
+
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <library/cpp/iterator/enumerate.h>
+#include <library/cpp/iterator/zip.h>
+
+#include <util/datetime/base.h>
+
+#include <util/generic/va_args.h>
+
+#include <util/system/compiler.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+using namespace NYT;
+using namespace NApi;
+using namespace NProfiling;
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TClientBuilder = std::function<NApi::IClientPtr(const TConfig&)>;
+
+#define RETRYABLE_METHOD(ReturnType, MethodName, Args) \
+ ReturnType MethodName(Y_METHOD_USED_ARGS_DECLARATION(Args)) override { \
+ return Executor_->DoWithHedging<typename ReturnType::TValueType>(BIND([=] (IClientPtr client) { \
+ return client->MethodName(Y_PASS_METHOD_USED_ARGS(Args)); \
+ })); \
+ } Y_SEMICOLON_GUARD
+
+////////////////////////////////////////////////////////////////////////////////
+
+class THedgingClient
+ : public IClient
+{
+public:
+ THedgingClient(const THedgingClientOptions& options, const IPenaltyProviderPtr& penaltyProvider)
+ : Executor_(New<THedgingExecutor>(options, penaltyProvider))
+ { }
+
+ // IClientBase methods.
+ // Supported methods.
+ IConnectionPtr GetConnection() override
+ {
+ return Executor_->GetConnection();
+ }
+
+ std::optional<TStringBuf> GetClusterName(bool fetchIfNull = true) override
+ {
+ Y_UNUSED(fetchIfNull);
+ return {};
+ }
+
+ RETRYABLE_METHOD(TFuture<IUnversionedRowsetPtr>, LookupRows, (const NYPath::TYPath&, NTableClient::TNameTablePtr, const TSharedRange<NTableClient::TUnversionedRow>&, const TLookupRowsOptions&));
+ RETRYABLE_METHOD(TFuture<IVersionedRowsetPtr>, VersionedLookupRows, (const NYPath::TYPath&, NTableClient::TNameTablePtr, const TSharedRange<NTableClient::TUnversionedRow>&, const TVersionedLookupRowsOptions&));
+ RETRYABLE_METHOD(TFuture<TSelectRowsResult>, SelectRows, (const TString&, const TSelectRowsOptions&));
+ RETRYABLE_METHOD(TFuture<NQueueClient::IQueueRowsetPtr>, PullQueue, (const NYPath::TRichYPath&, i64, int, const NQueueClient::TQueueRowBatchReadOptions&, const TPullQueueOptions&));
+ RETRYABLE_METHOD(TFuture<NQueueClient::IQueueRowsetPtr>, PullConsumer, (const NYPath::TRichYPath&, const NYPath::TRichYPath&, i64, int, const NQueueClient::TQueueRowBatchReadOptions&, const TPullConsumerOptions&));
+ RETRYABLE_METHOD(TFuture<void>, RegisterQueueConsumer, (const NYPath::TRichYPath&, const NYPath::TRichYPath&, bool, const TRegisterQueueConsumerOptions&));
+ RETRYABLE_METHOD(TFuture<void>, UnregisterQueueConsumer, (const NYPath::TRichYPath&, const NYPath::TRichYPath&, const TUnregisterQueueConsumerOptions&));
+ RETRYABLE_METHOD(TFuture<std::vector<TListQueueConsumerRegistrationsResult>>, ListQueueConsumerRegistrations, (const std::optional<NYPath::TRichYPath>&, const std::optional<NYPath::TRichYPath>&, const TListQueueConsumerRegistrationsOptions&));
+ RETRYABLE_METHOD(TFuture<NYson::TYsonString>, ExplainQuery, (const TString&, const TExplainQueryOptions&));
+ RETRYABLE_METHOD(TFuture<ITableReaderPtr>, CreateTableReader, (const NYPath::TRichYPath&, const TTableReaderOptions&));
+ RETRYABLE_METHOD(TFuture<NYson::TYsonString>, GetNode, (const NYPath::TYPath&, const TGetNodeOptions&));
+ RETRYABLE_METHOD(TFuture<NYson::TYsonString>, ListNode, (const NYPath::TYPath&, const TListNodeOptions&));
+ RETRYABLE_METHOD(TFuture<bool>, NodeExists, (const NYPath::TYPath&, const TNodeExistsOptions&));
+ RETRYABLE_METHOD(TFuture<IFileReaderPtr>, CreateFileReader, (const NYPath::TYPath&, const TFileReaderOptions&));
+ RETRYABLE_METHOD(TFuture<std::vector<IUnversionedRowsetPtr>>, MultiLookup, (const std::vector<TMultiLookupSubrequest>&, const TMultiLookupOptions&));
+
+ // Unsupported methods.
+ UNSUPPORTED_METHOD(TFuture<ITransactionPtr>, StartTransaction, (NTransactionClient::ETransactionType, const TTransactionStartOptions&));
+ UNSUPPORTED_METHOD(TFuture<ITableWriterPtr>, CreateTableWriter, (const NYPath::TRichYPath&, const TTableWriterOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, SetNode, (const NYPath::TYPath&, const NYson::TYsonString&, const TSetNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, MultisetAttributesNode, (const NYPath::TYPath&, const NYTree::IMapNodePtr&, const TMultisetAttributesNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, RemoveNode, (const NYPath::TYPath&, const TRemoveNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<NCypressClient::TNodeId>, CreateNode, (const NYPath::TYPath&, NObjectClient::EObjectType, const TCreateNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<TLockNodeResult>, LockNode, (const NYPath::TYPath&, NCypressClient::ELockMode, const TLockNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, UnlockNode, (const NYPath::TYPath&, const TUnlockNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<NCypressClient::TNodeId>, CopyNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TCopyNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<NCypressClient::TNodeId>, MoveNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TMoveNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<NCypressClient::TNodeId>, LinkNode, (const NYPath::TYPath&, const NYPath::TYPath&, const TLinkNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ConcatenateNodes, (const std::vector<NYPath::TRichYPath>&, const NYPath::TRichYPath&, const TConcatenateNodesOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ExternalizeNode, (const NYPath::TYPath&, NObjectClient::TCellTag, const TExternalizeNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, InternalizeNode, (const NYPath::TYPath&, const TInternalizeNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<NObjectClient::TObjectId>, CreateObject, (NObjectClient::EObjectType, const TCreateObjectOptions&));
+ UNSUPPORTED_METHOD(IFileWriterPtr, CreateFileWriter, (const NYPath::TRichYPath&, const TFileWriterOptions&));
+ UNSUPPORTED_METHOD(IJournalReaderPtr, CreateJournalReader, (const NYPath::TYPath&, const TJournalReaderOptions&));
+ UNSUPPORTED_METHOD(IJournalWriterPtr, CreateJournalWriter, (const NYPath::TYPath&, const TJournalWriterOptions&));
+
+ // IClient methods.
+ // Unsupported methods.
+ UNSUPPORTED_METHOD(void, Terminate, ());
+ UNSUPPORTED_METHOD(const NTabletClient::ITableMountCachePtr&, GetTableMountCache, ());
+ UNSUPPORTED_METHOD(const NChaosClient::IReplicationCardCachePtr&, GetReplicationCardCache, ());
+ UNSUPPORTED_METHOD(const NTransactionClient::ITimestampProviderPtr&, GetTimestampProvider, ());
+ UNSUPPORTED_METHOD(ITransactionPtr, AttachTransaction, (NTransactionClient::TTransactionId, const TTransactionAttachOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, MountTable, (const NYPath::TYPath&, const TMountTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, UnmountTable, (const NYPath::TYPath&, const TUnmountTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, RemountTable, (const NYPath::TYPath&, const TRemountTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, FreezeTable, (const NYPath::TYPath&, const TFreezeTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, UnfreezeTable, (const NYPath::TYPath&, const TUnfreezeTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ReshardTable, (const NYPath::TYPath&, const std::vector<NTableClient::TUnversionedOwningRow>&, const TReshardTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ReshardTable, (const NYPath::TYPath&, int, const TReshardTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<std::vector<NTabletClient::TTabletActionId>>, ReshardTableAutomatic, (const NYPath::TYPath&, const TReshardTableAutomaticOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, TrimTable, (const NYPath::TYPath&, int, i64, const TTrimTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AlterTable, (const NYPath::TYPath&, const TAlterTableOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AlterTableReplica, (NTabletClient::TTableReplicaId, const TAlterTableReplicaOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AlterReplicationCard, (NChaosClient::TReplicationCardId, const TAlterReplicationCardOptions&));
+ UNSUPPORTED_METHOD(TFuture<std::vector<NTabletClient::TTableReplicaId>>, GetInSyncReplicas, (const NYPath::TYPath&, const NTableClient::TNameTablePtr&, const TSharedRange<NTableClient::TUnversionedRow>&, const TGetInSyncReplicasOptions&));
+ UNSUPPORTED_METHOD(TFuture<std::vector<NTabletClient::TTableReplicaId>>, GetInSyncReplicas, (const NYPath::TYPath&, const TGetInSyncReplicasOptions&));
+ UNSUPPORTED_METHOD(TFuture<std::vector<TTabletInfo>>, GetTabletInfos, (const NYPath::TYPath&, const std::vector<int>&, const TGetTabletInfosOptions&));
+ UNSUPPORTED_METHOD(TFuture<TGetTabletErrorsResult>, GetTabletErrors, (const NYPath::TYPath&, const TGetTabletErrorsOptions&));
+ UNSUPPORTED_METHOD(TFuture<std::vector<NTabletClient::TTabletActionId>>, BalanceTabletCells, (const TString&, const std::vector<NYPath::TYPath>&, const TBalanceTabletCellsOptions&));
+ UNSUPPORTED_METHOD(TFuture<TSkynetSharePartsLocationsPtr>, LocateSkynetShare, (const NYPath::TRichYPath&, const TLocateSkynetShareOptions&));
+ UNSUPPORTED_METHOD(TFuture<std::vector<NTableClient::TColumnarStatistics>>, GetColumnarStatistics, (const std::vector<NYPath::TRichYPath>&, const TGetColumnarStatisticsOptions&));
+ UNSUPPORTED_METHOD(TFuture<TMultiTablePartitions>, PartitionTables, (const std::vector<NYPath::TRichYPath>&, const TPartitionTablesOptions&));
+ UNSUPPORTED_METHOD(TFuture<NYson::TYsonString>, GetTablePivotKeys, (const NYPath::TYPath&, const TGetTablePivotKeysOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, CreateTableBackup, (const TBackupManifestPtr&, const TCreateTableBackupOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, RestoreTableBackup, (const TBackupManifestPtr&, const TRestoreTableBackupOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, TruncateJournal, (const NYPath::TYPath&, i64, const TTruncateJournalOptions&));
+ UNSUPPORTED_METHOD(TFuture<TGetFileFromCacheResult>, GetFileFromCache, (const TString&, const TGetFileFromCacheOptions&));
+ UNSUPPORTED_METHOD(TFuture<TPutFileToCacheResult>, PutFileToCache, (const NYPath::TYPath&, const TString&, const TPutFileToCacheOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AddMember, (const TString&, const TString&, const TAddMemberOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, RemoveMember, (const TString&, const TString&, const TRemoveMemberOptions&));
+ UNSUPPORTED_METHOD(TFuture<TCheckPermissionResponse>, CheckPermission, (const TString&, const NYPath::TYPath&, NYTree::EPermission, const TCheckPermissionOptions&));
+ UNSUPPORTED_METHOD(TFuture<TCheckPermissionByAclResult>, CheckPermissionByAcl, (const std::optional<TString>&, NYTree::EPermission, NYTree::INodePtr, const TCheckPermissionByAclOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, TransferAccountResources, (const TString&, const TString&, NYTree::INodePtr, const TTransferAccountResourcesOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, TransferPoolResources, (const TString&, const TString&, const TString&, NYTree::INodePtr, const TTransferPoolResourcesOptions&));
+ UNSUPPORTED_METHOD(TFuture<NScheduler::TOperationId>, StartOperation, (NScheduler::EOperationType, const NYson::TYsonString&, const TStartOperationOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AbortOperation, (const NScheduler::TOperationIdOrAlias&, const TAbortOperationOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, SuspendOperation, (const NScheduler::TOperationIdOrAlias&, const TSuspendOperationOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ResumeOperation, (const NScheduler::TOperationIdOrAlias&, const TResumeOperationOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, CompleteOperation, (const NScheduler::TOperationIdOrAlias&, const TCompleteOperationOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, UpdateOperationParameters, (const NScheduler::TOperationIdOrAlias&, const NYson::TYsonString&, const TUpdateOperationParametersOptions&));
+ UNSUPPORTED_METHOD(TFuture<TOperation>, GetOperation, (const NScheduler::TOperationIdOrAlias&, const TGetOperationOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, DumpJobContext, (NJobTrackerClient::TJobId, const NYPath::TYPath&, const TDumpJobContextOptions&));
+ UNSUPPORTED_METHOD(TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr>, GetJobInput, (NJobTrackerClient::TJobId, const TGetJobInputOptions&));
+ UNSUPPORTED_METHOD(TFuture<NYson::TYsonString>, GetJobInputPaths, (NJobTrackerClient::TJobId, const TGetJobInputPathsOptions&));
+ UNSUPPORTED_METHOD(TFuture<NYson::TYsonString>, GetJobSpec, (NJobTrackerClient::TJobId, const TGetJobSpecOptions&));
+ UNSUPPORTED_METHOD(TFuture<TSharedRef>, GetJobStderr, (const NScheduler::TOperationIdOrAlias&, NJobTrackerClient::TJobId, const TGetJobStderrOptions&));
+ UNSUPPORTED_METHOD(TFuture<TSharedRef>, GetJobFailContext, (const NScheduler::TOperationIdOrAlias&, NJobTrackerClient::TJobId, const TGetJobFailContextOptions&));
+ UNSUPPORTED_METHOD(TFuture<TListOperationsResult>, ListOperations, (const TListOperationsOptions&));
+ UNSUPPORTED_METHOD(TFuture<TListJobsResult>, ListJobs, (const NScheduler::TOperationIdOrAlias&, const TListJobsOptions&));
+ UNSUPPORTED_METHOD(TFuture<NYson::TYsonString>, GetJob, (const NScheduler::TOperationIdOrAlias&, NJobTrackerClient::TJobId, const TGetJobOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AbandonJob, (NJobTrackerClient::TJobId, const TAbandonJobOptions&));
+ UNSUPPORTED_METHOD(TFuture<TPollJobShellResponse>, PollJobShell, (NJobTrackerClient::TJobId, const std::optional<TString>&, const NYson::TYsonString&, const TPollJobShellOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AbortJob, (NJobTrackerClient::TJobId, const TAbortJobOptions&));
+ UNSUPPORTED_METHOD(TFuture<TClusterMeta>, GetClusterMeta, (const TGetClusterMetaOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, CheckClusterLiveness, (const TCheckClusterLivenessOptions&));
+ UNSUPPORTED_METHOD(TFuture<int>, BuildSnapshot, (const TBuildSnapshotOptions&));
+ UNSUPPORTED_METHOD(TFuture<TCellIdToSnapshotIdMap>, BuildMasterSnapshots, (const TBuildMasterSnapshotsOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, SwitchLeader, (NObjectClient::TCellId, const TString&, const TSwitchLeaderOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ResetStateHash, (NObjectClient::TCellId, const TResetStateHashOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, GCCollect, (const TGCCollectOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, KillProcess, (const TString&, const TKillProcessOptions&));
+ UNSUPPORTED_METHOD(TFuture<TString>, WriteCoreDump, (const TString&, const TWriteCoreDumpOptions&));
+ UNSUPPORTED_METHOD(TFuture<TGuid>, WriteLogBarrier, (const TString&, const TWriteLogBarrierOptions&));
+ UNSUPPORTED_METHOD(TFuture<TString>, WriteOperationControllerCoreDump, (NJobTrackerClient::TOperationId, const TWriteOperationControllerCoreDumpOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, HealExecNode, (const TString&, const THealExecNodeOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, SuspendCoordinator, (NObjectClient::TCellId, const TSuspendCoordinatorOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ResumeCoordinator, (NObjectClient::TCellId, const TResumeCoordinatorOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, MigrateReplicationCards, (NObjectClient::TCellId, const TMigrateReplicationCardsOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, SuspendChaosCells, (const std::vector<NObjectClient::TCellId>&, const TSuspendChaosCellsOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ResumeChaosCells, (const std::vector<NObjectClient::TCellId>&, const TResumeChaosCellsOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, SuspendTabletCells, (const std::vector<NObjectClient::TCellId>&, const TSuspendTabletCellsOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, ResumeTabletCells, (const std::vector<NObjectClient::TCellId>&, const TResumeTabletCellsOptions&));
+ UNSUPPORTED_METHOD(TFuture<NChaosClient::TReplicationCardPtr>, GetReplicationCard, (NChaosClient::TReplicationCardId, const TGetReplicationCardOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, UpdateChaosTableReplicaProgress, (NChaosClient::TReplicaId, const TUpdateChaosTableReplicaProgressOptions&));
+ UNSUPPORTED_METHOD(TFuture<TMaintenanceId>, AddMaintenance, (EMaintenanceComponent, const TString&, EMaintenanceType, const TString&, const TAddMaintenanceOptions&));
+ UNSUPPORTED_METHOD(TFuture<TMaintenanceCounts>, RemoveMaintenance, (EMaintenanceComponent, const TString&, const TMaintenanceFilter&, const TRemoveMaintenanceOptions&));
+ UNSUPPORTED_METHOD(TFuture<TDisableChunkLocationsResult>, DisableChunkLocations, (const TString&, const std::vector<TGuid>&, const TDisableChunkLocationsOptions&));
+ UNSUPPORTED_METHOD(TFuture<TDestroyChunkLocationsResult>, DestroyChunkLocations, (const TString&, const std::vector<TGuid>&, const TDestroyChunkLocationsOptions&));
+ UNSUPPORTED_METHOD(TFuture<TResurrectChunkLocationsResult>, ResurrectChunkLocations, (const TString&, const std::vector<TGuid>&, const TResurrectChunkLocationsOptions&));
+ UNSUPPORTED_METHOD(TFuture<TRequestRebootResult>, RequestReboot, (const TString&, const TRequestRebootOptions&));
+ UNSUPPORTED_METHOD(TFuture<TPullRowsResult>, PullRows, (const NYPath::TYPath&, const TPullRowsOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, SetUserPassword, (const TString&, const TString&, const TString&, const TSetUserPasswordOptions&));
+ UNSUPPORTED_METHOD(TFuture<TIssueTokenResult>, IssueToken, (const TString&, const TString&, const TIssueTokenOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, RevokeToken, (const TString&, const TString&, const TString&, const TRevokeTokenOptions&));
+ UNSUPPORTED_METHOD(TFuture<TListUserTokensResult>, ListUserTokens, (const TString&, const TString&, const TListUserTokensOptions&));
+ UNSUPPORTED_METHOD(TFuture<NQueryTrackerClient::TQueryId>, StartQuery, (NQueryTrackerClient::EQueryEngine, const TString&, const TStartQueryOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AbortQuery, (NQueryTrackerClient::TQueryId, const TAbortQueryOptions&));
+ UNSUPPORTED_METHOD(TFuture<TQueryResult>, GetQueryResult, (NQueryTrackerClient::TQueryId, i64, const TGetQueryResultOptions&));
+ UNSUPPORTED_METHOD(TFuture<IUnversionedRowsetPtr>, ReadQueryResult, (NQueryTrackerClient::TQueryId, i64, const TReadQueryResultOptions&));
+ UNSUPPORTED_METHOD(TFuture<TQuery>, GetQuery, (NQueryTrackerClient::TQueryId, const TGetQueryOptions&));
+ UNSUPPORTED_METHOD(TFuture<TListQueriesResult>, ListQueries, (const TListQueriesOptions&));
+ UNSUPPORTED_METHOD(TFuture<void>, AlterQuery, (NQueryTrackerClient::TQueryId, const TAlterQueryOptions&));
+
+private:
+ THedgingExecutorPtr Executor_;
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::IClientPtr CreateHedgingClient(const THedgingClientOptions& options)
+{
+ return New<THedgingClient>(options, CreateDummyPenaltyProvider());
+}
+
+NApi::IClientPtr CreateHedgingClient(const THedgingClientOptions& options,
+ const IPenaltyProviderPtr& penaltyProvider)
+{
+ return New<THedgingClient>(options, penaltyProvider);
+}
+
+NApi::IClientPtr CreateHedgingClient(const THedgingClientConfig& config)
+{
+ return CreateHedgingClient(GetHedgingClientOptions(config));
+}
+
+NApi::IClientPtr CreateHedgingClient(const THedgingClientConfig& config, const IClientsCachePtr& clientsCache)
+{
+ return CreateHedgingClient(GetHedgingClientOptions(config, clientsCache));
+}
+
+NApi::IClientPtr CreateHedgingClient(const THedgingClientConfig& config,
+ const IClientsCachePtr& clientsCache,
+ const IPenaltyProviderPtr& penaltyProvider)
+{
+ return CreateHedgingClient(GetHedgingClientOptions(config, clientsCache), penaltyProvider);
+}
+
+THedgingClientOptions GetHedgingClientOptions(const THedgingClientConfig& config, TClientBuilder clientBuilder)
+{
+ THedgingClientOptions options;
+ options.BanPenalty = TDuration::MilliSeconds(config.GetBanPenalty());
+ options.BanDuration = TDuration::MilliSeconds(config.GetBanDuration());
+
+ NProfiling::TTagSet counterTagSet;
+
+ for (const auto& [tagName, tagValue] : config.GetTags()) {
+ counterTagSet.AddTag(NProfiling::TTag(tagName, tagValue));
+ }
+
+ options.Clients.reserve(config.GetClients().size());
+ for (const auto& client : config.GetClients()) {
+ options.Clients.emplace_back(
+ clientBuilder(client.GetClientConfig()),
+ client.GetClientConfig().GetClusterName(),
+ TDuration::MilliSeconds(client.GetInitialPenalty()),
+ New<TCounter>(counterTagSet.WithTag(NProfiling::TTag("yt_cluster", client.GetClientConfig().GetClusterName()))));
+ }
+ return options;
+}
+
+THedgingClientOptions GetHedgingClientOptions(const THedgingClientConfig& config)
+{
+ return GetHedgingClientOptions(config, [] (const auto& clientConfig) {
+ return CreateClient(clientConfig);
+ });
+}
+
+THedgingClientOptions GetHedgingClientOptions(const THedgingClientConfig& config, const IClientsCachePtr& clientsCache)
+{
+ return GetHedgingClientOptions(config, [clientsCache] (const auto& clientConfig) {
+ return clientsCache->GetClient(clientConfig.GetClusterName());
+ });
+}
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/hedging.h b/yt/yt/client/hedging/hedging.h
new file mode 100644
index 0000000000..3c515ce8b9
--- /dev/null
+++ b/yt/yt/client/hedging/hedging.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "public.h"
+
+#include "hedging_executor.h"
+#include "penalty_provider.h"
+
+#include <yt/yt/client/api/client.h>
+
+// @brief HedgingClient is a wrapper for several YT-clients with ability
+// to retry asynchronously the same request with different underlying-clients.
+// HedgingClient implements IClient interface and supports methods that do not change state of data on YT.
+// Currently supported methods: LookupRows, VersionedLookupRows, SelectRows, ExplainQuery,
+// CreateTableReader, GetNode, ListNode, NodeExists, CreateFileReader
+//
+// For initial configuration every YT-client needs an InitialPenalty value.
+// This value is used to determine in which order YT-clients will be used.
+//
+// MinInitialPenalty - minimal retry timeout value out of all YT-clients.
+// EffectivePenalty - is a delay value for starting a request with a corresponding YT-client.
+// For every client this value is calculated as: InitialPenalty - MinInitialPenalty.
+//
+// If any of the clients responses with a success result: requests to other YT-clients are cancelled.
+//
+// If any of the clients responses with an error: it's InitialPenalty is
+// increased by BanPenalty value for the next BanDuration time interval.
+// Both BanPenalty and BanDuration values are set in MultiClientCluster config.
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// from config.proto
+class THedgingClientConfig;
+
+// @brief Options for hedging client.
+// from hedging_executor.h
+struct THedgingClientOptions;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// @brief Method for creating HedgingClient with given options.
+NApi::IClientPtr CreateHedgingClient(const THedgingClientOptions& options);
+
+// @brief Method for creating HedgingClient with given options and ability to use penalty updater policy.
+// Currently for experimental usage.
+NApi::IClientPtr CreateHedgingClient(const THedgingClientOptions& options, const IPenaltyProviderPtr& penaltyProvider);
+
+// @brief Method for creating HedgingClient with given rpc clients config.
+NApi::IClientPtr CreateHedgingClient(const THedgingClientConfig& config);
+
+// @brief Method for creating HedgingClient with given rpc clients config and preinitialized clients.
+NApi::IClientPtr CreateHedgingClient(const THedgingClientConfig& config, const IClientsCachePtr& clientsCache);
+
+// @brief Method for creating HedgingClient with given rpc clients config, preinitialized clients and PenaltyProvider.
+NApi::IClientPtr CreateHedgingClient(const THedgingClientConfig& config, const IClientsCachePtr& clientsCache, const IPenaltyProviderPtr& penaltyProvider);
+
+// @brief Method for creating HedgingClient options from given config and preinitialized clients.
+THedgingClientOptions GetHedgingClientOptions(const THedgingClientConfig& config, const IClientsCachePtr& clientsCache);
+
+// @brief Method for creating HedgingClient options from given config.
+THedgingClientOptions GetHedgingClientOptions(const THedgingClientConfig& config);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/hedging_executor.cpp b/yt/yt/client/hedging/hedging_executor.cpp
new file mode 100644
index 0000000000..bd3e98e4fe
--- /dev/null
+++ b/yt/yt/client/hedging/hedging_executor.cpp
@@ -0,0 +1,80 @@
+#include "hedging_executor.h"
+
+#include "logger.h"
+
+#include <yt/yt/core/logging/log.h>
+
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+THedgingExecutor::THedgingExecutor(const THedgingClientOptions& options, const IPenaltyProviderPtr& penaltyProvider)
+ : BanPenalty_(NProfiling::DurationToCpuDuration(options.BanPenalty))
+ , BanDuration_(NProfiling::DurationToCpuDuration(options.BanDuration))
+ , PenaltyProvider_(penaltyProvider)
+{
+ Y_ENSURE(!options.Clients.empty(), "Clients should not be empty!");
+ for (const auto& clientOptions : options.Clients) {
+ Y_ENSURE(clientOptions.Client, "Client pointer should be valid!");
+ Clients_.emplace_back(
+ clientOptions.Client,
+ NProfiling::DurationToCpuDuration(clientOptions.InitialPenalty),
+ clientOptions.Counter ? clientOptions.Counter : New<TCounter>(clientOptions.Client->GetConnection()->GetClusterId()),
+ clientOptions.ClusterName);
+ }
+}
+
+NApi::IConnectionPtr THedgingExecutor::GetConnection()
+{
+ return Clients_[0].Client->GetConnection();
+}
+
+void THedgingExecutor::OnFinishRequest(
+ size_t clientIndex,
+ TDuration effectivePenalty,
+ NProfiling::TCpuDuration adaptivePenalty,
+ NProfiling::TCpuDuration externalPenalty,
+ NProfiling::TCpuInstant start,
+ const TError& error)
+{
+ auto& clientInfo = Clients_[clientIndex];
+ if (error.IsOK()) {
+ if (adaptivePenalty) {
+ TGuard guard(SpinLock_);
+ clientInfo.BanUntil = Max<NProfiling::TCpuInstant>();
+ clientInfo.AdaptivePenalty = 0;
+ }
+ clientInfo.Counter->SuccessRequestCount.Increment();
+ clientInfo.Counter->RequestDuration.Record(NProfiling::CpuDurationToDuration(NProfiling::GetCpuInstant() - start));
+ } else if (effectivePenalty && (error.GetCode() == EErrorCode::Canceled || error.GetCode() == EErrorCode::FutureCombinerShortcut)) {
+ clientInfo.Counter->CancelRequestCount.Increment();
+ } else {
+ with_lock (SpinLock_) {
+ clientInfo.BanUntil = NProfiling::GetCpuInstant() + BanDuration_;
+ clientInfo.AdaptivePenalty += BanPenalty_;
+ }
+ clientInfo.Counter->ErrorRequestCount.Increment();
+ YT_LOG_WARNING("client#%v failed with error %v", clientIndex, error);
+ }
+ clientInfo.Counter->EffectivePenalty.Update(effectivePenalty);
+ clientInfo.Counter->ExternalPenalty.Update(NProfiling::CpuDurationToDuration(externalPenalty));
+}
+
+THedgingExecutor::TEntry::TEntry(NApi::IClientPtr client,
+ NProfiling::TCpuDuration initialPenalty,
+ TCounterPtr counter,
+ TString clusterName)
+ : Client(std::move(client))
+ , ClusterName(std::move(clusterName))
+ , AdaptivePenalty(0)
+ , InitialPenalty(initialPenalty)
+ , ExternalPenalty(0)
+ , BanUntil(Max<NProfiling::TCpuInstant>())
+ , Counter(std::move(counter))
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/hedging_executor.h b/yt/yt/client/hedging/hedging_executor.h
new file mode 100644
index 0000000000..6c29a111f7
--- /dev/null
+++ b/yt/yt/client/hedging/hedging_executor.h
@@ -0,0 +1,110 @@
+#pragma once
+
+#include "config.h"
+#include "counter.h"
+#include "penalty_provider.h"
+#include "public.h"
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/core/profiling/timing.h>
+
+#include <yt/yt/core/rpc/dispatcher.h>
+
+#include <library/cpp/iterator/enumerate.h>
+
+#include <util/datetime/base.h>
+
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+
+#include <util/system/spinlock.h>
+
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(THedgingExecutor)
+
+class THedgingExecutor final
+{
+public:
+ THedgingExecutor(const THedgingClientOptions& options, const IPenaltyProviderPtr& penaltyProvider);
+
+ NApi::IConnectionPtr GetConnection();
+
+ template <typename T>
+ TFuture<T> DoWithHedging(TCallback<TFuture<T>(NApi::IClientPtr)> callback)
+ {
+ auto now = NProfiling::GetCpuInstant();
+ auto clients = [&] {
+ TGuard guard(SpinLock_);
+ for (auto& client : Clients_) {
+ if (client.BanUntil < now) {
+ client.AdaptivePenalty = 0;
+ }
+ }
+
+ return Clients_;
+ }();
+
+ NProfiling::TCpuDuration minInitialPenalty = Max<i64>();
+ for (auto& client : clients) {
+ client.ExternalPenalty = PenaltyProvider_->Get(client.ClusterName);
+ NProfiling::TCpuDuration currentInitialPenalty = client.InitialPenalty + client.AdaptivePenalty + client.ExternalPenalty;
+ minInitialPenalty = Min(minInitialPenalty, currentInitialPenalty);
+ }
+
+ TVector<TFuture<T>> futures(Reserve(clients.size()));
+ for (auto [i, client] : Enumerate(clients)) {
+ TDuration effectivePenalty = NProfiling::CpuDurationToDuration(client.InitialPenalty + client.AdaptivePenalty + client.ExternalPenalty - minInitialPenalty);
+ if (effectivePenalty) {
+ auto delayedFuture = NConcurrency::TDelayedExecutor::MakeDelayed(effectivePenalty, NYT::NRpc::TDispatcher::Get()->GetHeavyInvoker());
+ futures.push_back(delayedFuture.Apply(BIND(callback, client.Client)));
+ } else {
+ futures.push_back(callback(client.Client));
+ }
+ futures.back().Subscribe(BIND(&THedgingExecutor::OnFinishRequest, MakeWeak(this), i, effectivePenalty, client.AdaptivePenalty, client.ExternalPenalty, now));
+ }
+
+ return AnySucceeded(std::move(futures));
+ }
+
+private:
+ void OnFinishRequest(
+ size_t clientIndex,
+ TDuration effectivePenalty,
+ NProfiling::TCpuDuration adaptivePenalty,
+ NProfiling::TCpuDuration externalPenalty,
+ NProfiling::TCpuInstant start,
+ const TError& r);
+
+ struct TEntry
+ {
+ TEntry(NApi::IClientPtr client,
+ NProfiling::TCpuDuration initialPenalty,
+ TCounterPtr counter,
+ TString clusterName);
+
+ NApi::IClientPtr Client;
+ TString ClusterName;
+ NProfiling::TCpuDuration AdaptivePenalty;
+ NProfiling::TCpuDuration InitialPenalty;
+ NProfiling::TCpuDuration ExternalPenalty;
+ NProfiling::TCpuInstant BanUntil;
+ TCounterPtr Counter;
+ };
+
+ TVector<TEntry> Clients_;
+ NProfiling::TCpuDuration BanPenalty_;
+ NProfiling::TCpuDuration BanDuration_;
+ IPenaltyProviderPtr PenaltyProvider_;
+ TSpinLock SpinLock_;
+};
+
+DEFINE_REFCOUNTED_TYPE(THedgingExecutor)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/logger.cpp b/yt/yt/client/hedging/logger.cpp
new file mode 100644
index 0000000000..97dd0f6fb4
--- /dev/null
+++ b/yt/yt/client/hedging/logger.cpp
@@ -0,0 +1,11 @@
+#include "logger.h"
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const NLogging::TLogger Logger("TYtHedgingClient");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/logger.h b/yt/yt/client/hedging/logger.h
new file mode 100644
index 0000000000..d4771e3331
--- /dev/null
+++ b/yt/yt/client/hedging/logger.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+extern const NLogging::TLogger Logger;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/options.cpp b/yt/yt/client/hedging/options.cpp
new file mode 100644
index 0000000000..f2f7cb772d
--- /dev/null
+++ b/yt/yt/client/hedging/options.cpp
@@ -0,0 +1,48 @@
+#include "options.h"
+
+#include <util/folder/dirut.h>
+#include <util/folder/path.h>
+
+#include <util/stream/file.h>
+#include <util/string/strip.h>
+
+#include <util/system/env.h>
+
+namespace NYT::NClient::NHedging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NApi::TClientOptions GetClientOpsFromEnv()
+{
+ NApi::TClientOptions options;
+
+ auto user = Strip(GetEnv("YT_USER"));
+ if (!user.empty()) {
+ options.User = user;
+ }
+
+ auto token = Strip(GetEnv("YT_TOKEN"));
+ if (!token.empty()) {
+ options.Token = token;
+ } else {
+ auto tokenPath = Strip(GetEnv("YT_TOKEN_PATH"));
+ if (tokenPath.empty()) {
+ tokenPath = GetHomeDir() + "/.yt/token";
+ }
+ TFsPath path(tokenPath);
+ if (path.IsFile()) {
+ options.Token = Strip(TIFStream(path).ReadAll());
+ }
+ }
+ return options;
+}
+
+const NApi::TClientOptions& GetClientOpsFromEnvStatic()
+{
+ static const NApi::TClientOptions options = GetClientOpsFromEnv();
+ return options;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging
diff --git a/yt/yt/client/hedging/options.h b/yt/yt/client/hedging/options.h
new file mode 100644
index 0000000000..a80d378792
--- /dev/null
+++ b/yt/yt/client/hedging/options.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <yt/yt/client/api/connection.h>
+
+namespace NYT::NClient::NHedging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Fill client options from environment variable (client options is permanent for whole lifecycle of program).
+// UserName is extracted from YT_USER env variable or uses current system username.
+// Token is extracted from YT_TOKEN env variable or from file `~/.yt/token`.
+NApi::TClientOptions GetClientOpsFromEnv();
+
+//! Resolves options only once per launch and then returns the cached result.
+const NApi::TClientOptions& GetClientOpsFromEnvStatic();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging
diff --git a/yt/yt/client/hedging/penalty_provider.cpp b/yt/yt/client/hedging/penalty_provider.cpp
new file mode 100644
index 0000000000..b610f7f877
--- /dev/null
+++ b/yt/yt/client/hedging/penalty_provider.cpp
@@ -0,0 +1,230 @@
+#include "penalty_provider.h"
+
+#include "counter.h"
+#include "logger.h"
+#include "public.h"
+
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <yt/yt/client/transaction_client/helpers.h>
+
+#include <yt/yt/core/concurrency/periodic_executor.h>
+
+#include <yt/yt/core/logging/log.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/profiling/timing.h>
+
+#include <yt/yt/core/rpc/dispatcher.h>
+
+#include <util/generic/hash.h>
+#include <util/generic/xrange.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDummyLagProvider: public IPenaltyProvider {
+public:
+ NProfiling::TCpuDuration Get(const TString&) override {
+ return 0;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLagPenaltyProvider
+ : public IPenaltyProvider
+{
+public:
+ TLagPenaltyProvider(const TReplicationLagPenaltyProviderConfig& config, NApi::IClientPtr client)
+ : TablePath_(config.GetTablePath())
+ , MaxTabletLag_(TDuration::Seconds(config.GetMaxTabletLag()))
+ , LagPenalty_(NProfiling::DurationToCpuDuration(TDuration::MilliSeconds(config.GetLagPenalty())))
+ , MaxTabletsWithLagFraction_(config.GetMaxTabletsWithLagFraction())
+ , Client_(client)
+ , ClearPenaltiesOnErrors_(config.GetClearPenaltiesOnErrors())
+ , Counters_(New<TLagPenaltyProviderCounters>(TablePath_,
+ TVector<TString>{config.GetReplicaClusters().begin(), config.GetReplicaClusters().end()}))
+ , Executor_(New<NConcurrency::TPeriodicExecutor>(
+ NYT::NRpc::TDispatcher::Get()->GetLightInvoker(),
+ BIND(&TLagPenaltyProvider::UpdateCurrentLagPenalty, MakeWeak(this)),
+ TDuration::Seconds(config.GetCheckPeriod())))
+ {
+ Y_ENSURE(Executor_);
+ Y_ENSURE(Client_);
+
+ for (const auto& cluster : config.GetReplicaClusters()) {
+ auto [_, inserted] = ReplicaClusters_.try_emplace(cluster);
+ Y_ENSURE(inserted, "Replica cluster " << cluster << " is listed twice");
+ }
+
+ GetNodeOptions_.Timeout = TDuration::Seconds(5);
+ GetNodeOptions_.ReadFrom = NApi::EMasterChannelKind::Cache;
+
+ Executor_->Start();
+ }
+
+ // Checks that all ReplicaIds in ReplicaClusters_ have been set.
+ TError CheckAllReplicaIdsPresent() const
+ {
+ for (const auto& [cluster, info] : ReplicaClusters_) {
+ if (!info.ReplicaId) {
+ return TError{"ReplicaId was not found for %v", cluster};
+ }
+ }
+ return {};
+ }
+
+ // Fills ReplicaIds in ReplicaClusters_.
+ void UpdateReplicaIds()
+ {
+ auto replicasNode = NYTree::ConvertToNode(NConcurrency::WaitFor(Client_->GetNode(TablePath_ + "/@replicas", GetNodeOptions_)).ValueOrThrow())->AsMap();
+
+ for (const auto& row : replicasNode->GetChildren()) {
+ TString cluster = row.second->AsMap()->GetChildOrThrow("cluster_name")->AsString()->GetValue();
+ if (auto* info = ReplicaClusters_.FindPtr(cluster)) {
+ info->ReplicaId = NTabletClient::TTableReplicaId::FromString(row.first);
+ YT_LOG_INFO("Found ReplicaId %v for table %v in cluster %v", info->ReplicaId, TablePath_, cluster);
+ };
+ }
+ CheckAllReplicaIdsPresent().ThrowOnError();
+ }
+
+ ui64 GetTotalNumberOfTablets()
+ {
+ return NYTree::ConvertTo<ui64>(NConcurrency::WaitFor(Client_->GetNode(TablePath_ + "/@tablet_count", GetNodeOptions_)).ValueOrThrow());
+ }
+
+ // Returns a map: ReplicaId -> # of tablets.
+ THashMap<NTabletClient::TTableReplicaId, ui64> CalculateNumbersOfTabletsWithLag(const ui64 tabletsCount)
+ {
+ auto tabletsRange = xrange(tabletsCount);
+ auto tabletsInfo = NConcurrency::WaitFor(Client_->GetTabletInfos(TablePath_, {tabletsRange.begin(), tabletsRange.end()})).ValueOrThrow();
+
+ const auto now = TInstant::Now();
+ THashMap<NTabletClient::TTableReplicaId, ui64> tabletsWithLag;
+
+ for (const auto& tabletInfo : tabletsInfo) {
+ if (!tabletInfo.TableReplicaInfos) {
+ continue;
+ }
+
+ for (const auto& replicaInfo : *tabletInfo.TableReplicaInfos) {
+ auto lastReplicationTimestamp = TInstant::Seconds(NTransactionClient::UnixTimeFromTimestamp(replicaInfo.LastReplicationTimestamp));
+ if (now - lastReplicationTimestamp > MaxTabletLag_) {
+ ++tabletsWithLag[replicaInfo.ReplicaId];
+ }
+ }
+ }
+
+ return tabletsWithLag;
+ }
+
+ NProfiling::TCpuDuration CalculateLagPenalty(const ui64 tabletsCount, const ui64 tabletsWithLag)
+ {
+ return tabletsWithLag >= tabletsCount * MaxTabletsWithLagFraction_ ? LagPenalty_ : 0;
+ }
+
+ void UpdateCurrentLagPenalty()
+ {
+ try {
+ YT_LOG_INFO("Start penalty updater check for: %v", TablePath_);
+
+ if (!CheckAllReplicaIdsPresent().IsOK()) {
+ UpdateReplicaIds();
+ }
+
+ auto tabletsCount = GetTotalNumberOfTablets();
+ auto tabletsWithLag = CalculateNumbersOfTabletsWithLag(tabletsCount);
+
+ Counters_->TotalTabletsCount.Update(tabletsCount);
+
+ for (auto& [cluster, info] : ReplicaClusters_) {
+ Y_ASSERT(info.ReplicaId);
+ auto curTabletsWithLag = tabletsWithLag.Value(info.ReplicaId, 0);
+ NProfiling::TCpuDuration newLagPenalty = CalculateLagPenalty(tabletsCount, curTabletsWithLag);
+ info.CurrentLagPenalty.store(newLagPenalty, std::memory_order::relaxed);
+
+ Counters_->LagTabletsCount.at(cluster).Update(curTabletsWithLag);
+ YT_LOG_INFO(
+ "Finish penalty updater check (%v: %v/%v tablets lagging => penalty %v ms) for: %v",
+ cluster, curTabletsWithLag, tabletsCount,
+ NProfiling::CpuDurationToDuration(newLagPenalty).MilliSeconds(),
+ TablePath_
+ );
+ }
+
+ Counters_->SuccessRequestCount.Increment();
+ } catch (const std::exception& err) {
+ Counters_->ErrorRequestCount.Increment();
+
+ YT_LOG_ERROR("Lag penalty updater for %v failed: %v", TablePath_, err.what());
+
+ if (ClearPenaltiesOnErrors_) {
+ for (auto& [cluster, info] : ReplicaClusters_) {
+ info.CurrentLagPenalty.store(0, std::memory_order::relaxed);
+ YT_LOG_INFO("Clearing penalty for cluster %v and table %v", cluster, TablePath_);
+ }
+ }
+ }
+ }
+
+ NProfiling::TCpuDuration Get(const TString& cluster) override
+ {
+ if (const TReplicaInfo* info = ReplicaClusters_.FindPtr(cluster)) {
+ return info->CurrentLagPenalty.load(std::memory_order::relaxed);
+ }
+ return 0;
+ }
+
+ ~TLagPenaltyProvider()
+ {
+ YT_UNUSED_FUTURE(Executor_->Stop());
+ }
+
+private:
+ struct TReplicaInfo
+ {
+ NTabletClient::TTableReplicaId ReplicaId = {};
+ std::atomic<NProfiling::TCpuDuration> CurrentLagPenalty = 0;
+ };
+
+ const TString TablePath_;
+ THashMap<TString, TReplicaInfo> ReplicaClusters_;
+ const TDuration MaxTabletLag_;
+ const NProfiling::TCpuDuration LagPenalty_;
+ const float MaxTabletsWithLagFraction_;
+ NApi::IClientPtr Client_;
+ const bool ClearPenaltiesOnErrors_;
+ TLagPenaltyProviderCountersPtr Counters_;
+ NApi::TGetNodeOptions GetNodeOptions_;
+ NConcurrency::TPeriodicExecutorPtr Executor_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+IPenaltyProviderPtr CreateDummyPenaltyProvider()
+{
+ return New<TDummyLagProvider>();
+}
+
+IPenaltyProviderPtr CreateReplicationLagPenaltyProvider(
+ const TReplicationLagPenaltyProviderConfig& config,
+ NApi::IClientPtr client)
+{
+ return New<TLagPenaltyProvider>(config, client);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/penalty_provider.h b/yt/yt/client/hedging/penalty_provider.h
new file mode 100644
index 0000000000..04b1cea826
--- /dev/null
+++ b/yt/yt/client/hedging/penalty_provider.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/core/profiling/public.h>
+
+
+// @brief IPenaltyProvider interface is used in HedgingClient to provide external penalties for different clusters.
+// Current implementations are DummyPenaltyProvider and ReplicationLagPenaltyProvider.
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(IPenaltyProvider)
+
+class IPenaltyProvider
+ : public TRefCounted
+{
+public:
+ virtual NProfiling::TCpuDuration Get(const TString& cluster) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IPenaltyProvider)
+
+////////////////////////////////////////////////////////////////////////////////
+
+// @brief DummyPenaltyProvider - always returns 0.
+IPenaltyProviderPtr CreateDummyPenaltyProvider();
+
+// From config.proto.
+class TReplicationLagPenaltyProviderConfig;
+
+// @brief ReplicationLagPenaltyProvider - periodically checks replication lag for given table AND replica cluster.
+// Based on values from TReplicationLagPenaltyProviderConfig add current number of tablets with lag, it either returns 0 or LagPenalty value.
+// Master client - main cluster with replicated table. ReplicaCluster + TablePath specifies concrete replica for table from main cluster.
+IPenaltyProviderPtr CreateReplicationLagPenaltyProvider(
+ const TReplicationLagPenaltyProviderConfig& config, NApi::IClientPtr client);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/public.h b/yt/yt/client/hedging/public.h
new file mode 100644
index 0000000000..ea6c577d56
--- /dev/null
+++ b/yt/yt/client/hedging/public.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <yt/yt/client/api/public.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(TCounter)
+DECLARE_REFCOUNTED_STRUCT(TLagPenaltyProviderCounters)
+DECLARE_REFCOUNTED_CLASS(IClientsCache)
+
+DECLARE_REFCOUNTED_STRUCT(TClientConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/rpc.cpp b/yt/yt/client/hedging/rpc.cpp
new file mode 100644
index 0000000000..1aad3dc908
--- /dev/null
+++ b/yt/yt/client/hedging/rpc.cpp
@@ -0,0 +1,156 @@
+#include "rpc.h"
+#include "options.h"
+
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <yt/yt/client/api/client.h>
+
+#include <yt/yt/client/api/rpc_proxy/config.h>
+#include <yt/yt/client/api/rpc_proxy/connection.h>
+
+#include <util/string/strip.h>
+
+#include <util/system/env.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+NCompression::ECodec GetResponseCodecFromProto(const ECompressionCodec& protoCodec)
+{
+ switch (protoCodec) {
+ case ECompressionCodec::None:
+ return NCompression::ECodec::None;
+ case ECompressionCodec::Lz4:
+ return NCompression::ECodec::Lz4;
+ }
+ Y_UNREACHABLE();
+}
+
+NApi::NRpcProxy::TConnectionConfigPtr GetConnectionConfig(const TConfig& config)
+{
+ auto connectionConfig = New<NApi::NRpcProxy::TConnectionConfig>();
+ connectionConfig->SetDefaults();
+
+ connectionConfig->ClusterUrl = config.GetClusterName();
+ if (!config.GetProxyRole().empty()) {
+ connectionConfig->ProxyRole = config.GetProxyRole();
+ }
+ if (0 != config.GetChannelPoolSize()) {
+ connectionConfig->DynamicChannelPool->MaxPeerCount = config.GetChannelPoolSize();
+ }
+ if (0 != config.GetChannelPoolRebalanceIntervalSeconds()) {
+ connectionConfig->DynamicChannelPool->RandomPeerEvictionPeriod = TDuration::Seconds(config.GetChannelPoolRebalanceIntervalSeconds());
+ }
+ if (0 != config.GetModifyRowsBatchCapacity()) {
+ connectionConfig->ModifyRowsBatchCapacity = config.GetModifyRowsBatchCapacity();
+ }
+
+#define SET_TIMEOUT_OPTION(name) \
+ if (0 != config.Get##name()) connectionConfig->name = TDuration::MilliSeconds(config.Get ## name())
+
+ SET_TIMEOUT_OPTION(DefaultTransactionTimeout);
+ SET_TIMEOUT_OPTION(DefaultSelectRowsTimeout);
+ SET_TIMEOUT_OPTION(DefaultLookupRowsTimeout);
+ SET_TIMEOUT_OPTION(DefaultTotalStreamingTimeout);
+ SET_TIMEOUT_OPTION(DefaultStreamingStallTimeout);
+ SET_TIMEOUT_OPTION(DefaultPingPeriod);
+
+#undef SET_TIMEOUT_OPTION
+
+ connectionConfig->ResponseCodec = GetResponseCodecFromProto(config.GetResponseCodec());
+ connectionConfig->EnableRetries = config.GetEnableRetries();
+
+ if (config.HasRetryBackoffTime()) {
+ connectionConfig->RetryingChannel->RetryBackoffTime = TDuration::MilliSeconds(config.GetRetryBackoffTime());
+ }
+ if (config.HasRetryAttempts()) {
+ connectionConfig->RetryingChannel->RetryAttempts = config.GetRetryAttempts();
+ }
+ if (config.HasRetryTimeout()) {
+ connectionConfig->RetryingChannel->RetryTimeout = TDuration::MilliSeconds(config.GetRetryTimeout());
+ }
+
+ connectionConfig->Postprocess();
+
+ return connectionConfig;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::pair<TStringBuf, TStringBuf> ExtractClusterAndProxyRole(TStringBuf clusterUrl)
+{
+ TStringBuf cluster;
+ TStringBuf proxyRole;
+ clusterUrl.Split('/', cluster, proxyRole);
+ return {cluster, proxyRole};
+}
+
+void SetClusterUrl(const NApi::NRpcProxy::TConnectionConfigPtr& config, TStringBuf clusterUrl)
+{
+ auto [cluster, proxyRole] = ExtractClusterAndProxyRole(clusterUrl);
+ if (!proxyRole.empty()) {
+ Y_ENSURE(!config->ProxyRole || config->ProxyRole.value().empty(), "ProxyRole specified in both: config and url");
+ config->ProxyRole = ToString(proxyRole);
+ }
+ config->ClusterUrl = ToString(cluster);
+}
+
+void SetClusterUrl(TConfig& config, TStringBuf clusterUrl)
+{
+ auto [cluster, proxyRole] = ExtractClusterAndProxyRole(clusterUrl);
+ if (!proxyRole.empty()) {
+ Y_ENSURE(config.GetProxyRole().empty(), "ProxyRole specified in both: config and url");
+ config.SetProxyRole(ToString(proxyRole));
+ }
+ config.SetClusterName(ToString(cluster));
+}
+
+NApi::IClientPtr CreateClient(const NApi::NRpcProxy::TConnectionConfigPtr& config, const NApi::TClientOptions& options)
+{
+ return NApi::NRpcProxy::CreateConnection(config)->CreateClient(options);
+}
+
+NApi::IClientPtr CreateClient(const TConfig& config, const NApi::TClientOptions& options)
+{
+ return CreateClient(GetConnectionConfig(config), options);
+}
+
+NApi::IClientPtr CreateClient(const NApi::NRpcProxy::TConnectionConfigPtr& config)
+{
+ return CreateClient(config, GetClientOpsFromEnvStatic());
+}
+
+NApi::IClientPtr CreateClient(const TConfig& config)
+{
+ return CreateClient(GetConnectionConfig(config));
+}
+
+NApi::IClientPtr CreateClient(TStringBuf clusterUrl)
+{
+ return CreateClient(clusterUrl, GetClientOpsFromEnvStatic());
+}
+
+NApi::IClientPtr CreateClient(TStringBuf cluster, TStringBuf proxyRole)
+{
+ auto config = New<NApi::NRpcProxy::TConnectionConfig>();
+ config->ClusterUrl = ToString(cluster);
+ if (!proxyRole.empty()) {
+ config->ProxyRole = ToString(proxyRole);
+ }
+ return CreateClient(config);
+}
+
+NApi::IClientPtr CreateClient()
+{
+ return CreateClient(Strip(GetEnv("YT_PROXY")));
+}
+
+NApi::IClientPtr CreateClient(TStringBuf clusterUrl, const NApi::TClientOptions& options)
+{
+ TConfig config;
+ SetClusterUrl(config, clusterUrl);
+ return CreateClient(config, options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/rpc.h b/yt/yt/client/hedging/rpc.h
new file mode 100644
index 0000000000..102e6b6791
--- /dev/null
+++ b/yt/yt/client/hedging/rpc.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <yt/yt/client/api/public.h>
+#include <yt/yt/client/api/rpc_proxy/config.h>
+
+#include <util/generic/strbuf.h>
+
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TConfig;
+
+NApi::NRpcProxy::TConnectionConfigPtr GetConnectionConfig(const TConfig& config);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Helper function to extract ClusterName and ProxyRole from clusterUrl.
+std::pair<TStringBuf, TStringBuf> ExtractClusterAndProxyRole(TStringBuf clusterUrl);
+
+//! Helper function to properly set ClusterName and ProxyRole from clusterUrl.
+// Expected that clusterUrl will be in format cluster[/proxyRole].
+void SetClusterUrl(const NApi::NRpcProxy::TConnectionConfigPtr& config, TStringBuf clusterUrl);
+void SetClusterUrl(TConfig& config, TStringBuf clusterUrl);
+
+NApi::IClientPtr CreateClient(const NApi::NRpcProxy::TConnectionConfigPtr& config, const NApi::TClientOptions& options);
+NApi::IClientPtr CreateClient(const TConfig& config, const NApi::TClientOptions& options);
+
+//! Shortcut to create rpc client with options from env variables
+NApi::IClientPtr CreateClient(const NApi::NRpcProxy::TConnectionConfigPtr& config);
+NApi::IClientPtr CreateClient(const TConfig& config);
+
+//! Shortcut to create client to specified cluster.
+// `clusterUrl` may have format cluster[/proxyRole],
+// where cluster may be short name of yt cluster (markov, hahn) or ip address + port.
+NApi::IClientPtr CreateClient(TStringBuf clusterUrl);
+
+//! Allows to specify proxyRole as dedicated option.
+NApi::IClientPtr CreateClient(TStringBuf cluster, TStringBuf proxyRole);
+
+//! Shortcut to create client with default config and options from env variables (use env:YT_PROXY as serverName).
+NApi::IClientPtr CreateClient();
+
+//! Shortcut to create client to cluster with custom options.
+NApi::IClientPtr CreateClient(TStringBuf clusterUrl, const NApi::TClientOptions& options);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/unittests/cache_ut.cpp b/yt/yt/client/hedging/unittests/cache_ut.cpp
new file mode 100644
index 0000000000..25f61e7a2d
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/cache_ut.cpp
@@ -0,0 +1,110 @@
+#include <yt/yt/client/hedging/cache.h>
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/string/format.h>
+
+#include <util/generic/vector.h>
+
+#include <util/system/env.h>
+
+#include <thread>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// YT does not create physical connection immediately, so try to use this fact to create connection to non existence server.
+TEST(TClientsCacheTest, GetSameClient)
+{
+ SetEnv("YT_TOKEN", "AAAA-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+ auto cache = CreateClientsCache();
+ auto client1 = cache->GetClient("localhost");
+ auto client2 = cache->GetClient("localhost");
+ EXPECT_TRUE(client1 == client2);
+}
+
+TEST(TClientsCacheTest, GetClientWithProxyRole)
+{
+ SetEnv("YT_TOKEN", "AAAA-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+ auto cache = CreateClientsCache();
+ auto client1 = cache->GetClient("bigb@localhost");
+ auto client2 = cache->GetClient("localhost");
+ EXPECT_TRUE(client1 != client2);
+}
+
+TEST(TClientsCacheTest, MultiThreads)
+{
+ SetEnv("YT_TOKEN", "AAAA-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+ auto cache = CreateClientsCache();
+ TVector<std::thread> threads(Reserve(10));
+ TVector<NApi::IClientPtr> clients(threads.capacity());
+ TVector<size_t> collisions(threads.capacity());
+
+ for (size_t i = 0 ; i < threads.capacity(); ++i) {
+ threads.emplace_back([=, &clients, &collisions] () {
+ try {
+ for (size_t j = 0; j < 1000; ++j) {
+ auto client = cache->GetClient(Format("localhost:6000%v", i));
+ if (client != clients[i]) {
+ clients[i] = client;
+ ++collisions[i];
+ }
+ }
+ } catch (...) {
+ collisions[i] = 100500; // exception marker
+ }
+ });
+ }
+
+ for (auto& t : threads) {
+ t.join();
+ }
+ for (const auto& client : clients) {
+ EXPECT_TRUE(client);
+ }
+ for (auto collision : collisions) {
+ EXPECT_EQ(1u, collision);
+ }
+}
+
+TEST(TClientsCacheTest, MakeClusterConfig) {
+ TClustersConfig clustersCfg;
+ clustersCfg.MutableDefaultConfig()->SetClusterName("seneca-nan"); // will be ignored
+ clustersCfg.MutableDefaultConfig()->SetProxyRole("default_role"); // can be overwritten
+ clustersCfg.MutableDefaultConfig()->SetChannelPoolSize(42u);
+ auto& senecaVlaCfg = (*clustersCfg.MutableClusterConfigs())["seneca-vla"];
+ senecaVlaCfg.SetClusterName(""); // will be ignored
+ senecaVlaCfg.SetProxyRole("seneca_vla_role"); // can be overwritten
+ senecaVlaCfg.SetChannelPoolSize(43u);
+
+ {
+ auto cfg = MakeClusterConfig(clustersCfg, "seneca-man");
+ EXPECT_EQ(cfg.GetClusterName(), "seneca-man");
+ EXPECT_EQ(cfg.GetProxyRole(), "default_role");
+ EXPECT_EQ(cfg.GetChannelPoolSize(), 42u);
+ }
+ {
+ auto cfg = MakeClusterConfig(clustersCfg, "seneca-man/overwriting_role");
+ EXPECT_EQ(cfg.GetClusterName(), "seneca-man");
+ EXPECT_EQ(cfg.GetProxyRole(), "overwriting_role");
+ EXPECT_EQ(cfg.GetChannelPoolSize(), 42u);
+ }
+ {
+ auto cfg = MakeClusterConfig(clustersCfg, "seneca-vla");
+ EXPECT_EQ(cfg.GetClusterName(), "seneca-vla");
+ EXPECT_EQ(cfg.GetProxyRole(), "seneca_vla_role");
+ EXPECT_EQ(cfg.GetChannelPoolSize(), 43u);
+ }
+ {
+ auto cfg = MakeClusterConfig(clustersCfg, "seneca-vla/overwriting_role");
+ EXPECT_EQ(cfg.GetClusterName(), "seneca-vla");
+ EXPECT_EQ(cfg.GetProxyRole(), "overwriting_role");
+ EXPECT_EQ(cfg.GetChannelPoolSize(), 43u);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/unittests/counters_ut.cpp b/yt/yt/client/hedging/unittests/counters_ut.cpp
new file mode 100644
index 0000000000..e6dc5eeb48
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/counters_ut.cpp
@@ -0,0 +1,241 @@
+#include <yt/yt/client/hedging/counter.h>
+#include <yt/yt/client/hedging/hedging.h>
+
+#include <yt/yt/client/unittests/mock/client.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/actions/cancelable_context.h>
+
+#include <yt/yt/library/profiling/sensor.h>
+#include <yt/yt/library/profiling/testing.h>
+#include <yt/yt/library/profiling/solomon/registry.h>
+
+#include <library/cpp/iterator/zip.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <util/generic/vector.h>
+
+#include <util/string/join.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+using namespace NYT::NProfiling;
+
+using TStrictMockClient = StrictMock<NApi::TMockClient>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+const auto SleepQuantum = TDuration::MilliSeconds(100);
+
+#define EXPECT_DURATION_NEAR(a, b) EXPECT_NEAR(a.MilliSeconds(), b.MilliSeconds(), 1)
+
+NApi::IClientPtr CreateTestHedgingClient(std::initializer_list<NApi::IClientPtr> clients,
+ std::initializer_list<TCounterPtr> counters,
+ TDuration banDuration = SleepQuantum * 5)
+{
+ THedgingClientOptions options;
+ options.BanPenalty = SleepQuantum * 2;
+ options.BanDuration = banDuration;
+ std::initializer_list<TDuration> initialPenalties = {TDuration::Zero(), SleepQuantum};
+
+ for (auto [client, initialPenalty, counter] : Zip(clients, initialPenalties, counters)) {
+ options.Clients.emplace_back(client, initialPenalty, counter);
+ }
+ return CreateHedgingClient(options);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(THedgingClientCountersTest, CountersAfterSuccessFromFirstClient)
+{
+ NYPath::TYPath path = "/test/1234";
+ NApi::TListNodeOptions options;
+ options.Attributes = {"some_attribute"};
+
+ NYson::TYsonString firstClientResult(TStringBuf("FirstClientData"));
+
+ auto firstMockClient = New<TStrictMockClient>();
+ auto secondMockClient = New<TStrictMockClient>();
+
+ EXPECT_CALL(*firstMockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(firstClientResult)));
+ EXPECT_CALL(*secondMockClient, ListNode(path, _)).Times(0);
+
+ auto solomon = New<TSolomonRegistry>();
+ TRegistry registry(solomon, "/d");
+
+ auto firstClientCounter = New<TCounter>(registry.WithTag("c", "first"));
+ auto secondClientCounter = New<TCounter>(registry.WithTag("c", "first"));
+
+ auto client = CreateTestHedgingClient({firstMockClient, secondMockClient},
+ {firstClientCounter, secondClientCounter});
+ auto queryResult = NConcurrency::WaitFor(client->ListNode(path, options));
+
+ // Wait for cancelled request finish.
+ Sleep(SleepQuantum);
+
+ // Success result from first client with effective initial penalty equals to 0 ms
+ EXPECT_EQ(1, TTesting::ReadCounter(firstClientCounter->SuccessRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(firstClientCounter->ErrorRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(firstClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(TDuration::Zero(), TTesting::ReadTimeGauge(firstClientCounter->EffectivePenalty));
+
+ // Cancel result from second client with effective initial penalty increment to ban duration
+ EXPECT_EQ(0, TTesting::ReadCounter(secondClientCounter->SuccessRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(secondClientCounter->ErrorRequestCount));
+ EXPECT_EQ(1, TTesting::ReadCounter(secondClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(SleepQuantum, TTesting::ReadTimeGauge(secondClientCounter->EffectivePenalty));
+}
+
+TEST(THedgingClientCountersTest, CountersAfterFirstClientHasFailed)
+{
+ NYPath::TYPath path = "/test/1234";
+ NApi::TListNodeOptions options;
+ options.Attributes = {"some_attribute"};
+
+ NYson::TYsonString firstClientResult(TStringBuf("FirstClientData"));
+ NYson::TYsonString secondClientResult(TStringBuf("SecondClientData"));
+
+ auto firstMockClient = New<TStrictMockClient>();
+ auto secondMockClient = New<TStrictMockClient>();
+
+ EXPECT_CALL(*firstMockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+ EXPECT_CALL(*secondMockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(secondClientResult)));
+
+ auto solomon = New<TSolomonRegistry>();
+ TRegistry registry(solomon, "/d");
+
+ auto firstClientCounter = New<TCounter>(registry.WithTag("c", "first"));
+ auto secondClientCounter = New<TCounter>(registry.WithTag("c", "second"));
+
+ auto client = CreateTestHedgingClient({firstMockClient, secondMockClient},
+ {firstClientCounter, secondClientCounter},
+ TDuration::Seconds(5));
+ auto queryResult = NConcurrency::WaitFor(client->ListNode(path, options));
+
+ // Error result from first client with effective initial penalty equals to 0 ms
+ EXPECT_EQ(0, TTesting::ReadCounter(firstClientCounter->SuccessRequestCount));
+ EXPECT_EQ(1, TTesting::ReadCounter(firstClientCounter->ErrorRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(firstClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(TDuration::Zero(), TTesting::ReadTimeGauge(firstClientCounter->EffectivePenalty));
+
+ // Success result from second client with effective initial penalty equals to 8 ms
+ EXPECT_EQ(1, TTesting::ReadCounter(secondClientCounter->SuccessRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(secondClientCounter->ErrorRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(secondClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(SleepQuantum, TTesting::ReadTimeGauge(secondClientCounter->EffectivePenalty));
+}
+
+TEST(THedgingClientCountersTest, CountersWhenFirstClientIsBanned)
+{
+ NYPath::TYPath path = "/test/1234";
+ NApi::TListNodeOptions options;
+ options.Attributes = {"some_attribute"};
+
+ NYson::TYsonString firstClientResult(TStringBuf("FirstClientData"));
+ NYson::TYsonString secondClientResult(TStringBuf("SecondClientData"));
+
+ auto firstMockClient = New<TStrictMockClient>();
+ auto secondMockClient = New<TStrictMockClient>();
+
+ EXPECT_CALL(*firstMockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+ EXPECT_CALL(*secondMockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(secondClientResult)))
+ .WillOnce(Return(MakeFuture(secondClientResult)));
+
+ auto solomon = New<TSolomonRegistry>();
+ solomon->SetWindowSize(12);
+ TRegistry registry(solomon, "/d");
+
+ auto firstClientCounter = New<TCounter>(registry.WithTag("c", "first"));
+ auto secondClientCounter = New<TCounter>(registry.WithTag("c", "second"));
+
+ auto client = CreateTestHedgingClient({firstMockClient, secondMockClient},
+ {firstClientCounter, secondClientCounter});
+ auto firstQueryResult = NConcurrency::WaitFor(client->ListNode(path, options));
+
+ auto secondQueryResult = NConcurrency::WaitFor(client->ListNode(path, options));
+
+ // Wait for cancelled request finish.
+ Sleep(SleepQuantum);
+
+ // Cancel result from banned client with effective initial penalty equals to BanDuration ms
+ EXPECT_EQ(0, TTesting::ReadCounter(firstClientCounter->SuccessRequestCount));
+ EXPECT_EQ(1, TTesting::ReadCounter(firstClientCounter->ErrorRequestCount));
+ EXPECT_EQ(1, TTesting::ReadCounter(firstClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(SleepQuantum, TTesting::ReadTimeGauge(firstClientCounter->EffectivePenalty));
+
+ // Success result from second client with effective initial penalty equals to 0 ms
+ EXPECT_EQ(2, TTesting::ReadCounter(secondClientCounter->SuccessRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(secondClientCounter->ErrorRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(secondClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(TDuration::Zero(), TTesting::ReadTimeGauge(secondClientCounter->EffectivePenalty));
+}
+
+TEST(THedgingClientCountersTest, CountersAfterFirstClientBanHasElapsed)
+{
+ NYPath::TYPath path = "/test/1234";
+ NApi::TListNodeOptions options;
+ options.Attributes = {"some_attribute"};
+
+ NYson::TYsonString firstClientResult(TStringBuf("FirstClientData"));
+ NYson::TYsonString secondClientResult(TStringBuf("SecondClientData"));
+
+ auto firstMockClient = New<TStrictMockClient>();
+ auto secondMockClient = New<TStrictMockClient>();
+
+ EXPECT_CALL(*firstMockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))))
+ .WillOnce(Return(MakeFuture(firstClientResult)));
+ EXPECT_CALL(*secondMockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(secondClientResult)));
+
+ auto solomon = New<TSolomonRegistry>();
+ solomon->SetWindowSize(12);
+ TRegistry registry(solomon, "/d");
+
+ auto firstClientCounter = New<TCounter>(registry.WithTag("c", "first"));
+ auto secondClientCounter = New<TCounter>(registry.WithTag("c", "second"));
+
+ auto banDuration = SleepQuantum * 2;
+
+ auto client = CreateTestHedgingClient({firstMockClient, secondMockClient},
+ {firstClientCounter, secondClientCounter},
+ banDuration);
+ auto firstQueryResult = NConcurrency::WaitFor(client->ListNode(path, options));
+
+ Sleep(banDuration);
+ auto secondQueryResult = NConcurrency::WaitFor(client->ListNode(path, options));
+
+ // Wait for cancelled request finish.
+ Sleep(SleepQuantum);
+
+ // Success result from first client, after ban time has elapsed, with effective initial penalty equals to 0 ms
+ EXPECT_EQ(1, TTesting::ReadCounter(firstClientCounter->SuccessRequestCount));
+ EXPECT_EQ(1, TTesting::ReadCounter(firstClientCounter->ErrorRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(firstClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(TDuration::Zero(), TTesting::ReadTimeGauge(firstClientCounter->EffectivePenalty));
+
+ // Cancel result from second client with effective initial penalty equals to 8 ms
+ EXPECT_EQ(1, TTesting::ReadCounter(secondClientCounter->SuccessRequestCount));
+ EXPECT_EQ(0, TTesting::ReadCounter(secondClientCounter->ErrorRequestCount));
+ EXPECT_EQ(1, TTesting::ReadCounter(secondClientCounter->CancelRequestCount));
+ EXPECT_DURATION_NEAR(SleepQuantum, TTesting::ReadTimeGauge(secondClientCounter->EffectivePenalty));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/unittests/hedging_ut.cpp b/yt/yt/client/hedging/unittests/hedging_ut.cpp
new file mode 100644
index 0000000000..6362d491b9
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/hedging_ut.cpp
@@ -0,0 +1,533 @@
+#include <yt/yt/client/hedging/cache.h>
+#include <yt/yt/client/hedging/counter.h>
+#include <yt/yt/client/hedging/hedging.h>
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <yt/yt/client/transaction_client/helpers.h>
+
+#include <yt/yt/client/unittests/mock/client.h>
+
+#include <yt/yt/core/concurrency/delayed_executor.h>
+#include <yt/yt/core/concurrency/thread_pool.h>
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <yt/yt/core/actions/cancelable_context.h>
+
+#include <library/cpp/iterator/zip.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+namespace {
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+using TStrictMockClient = StrictMock<NApi::TMockClient>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const auto SleepQuantum = TDuration::MilliSeconds(100);
+const auto CheckPeriod = TDuration::Seconds(1);
+
+class TMockClientsCache
+ : public IClientsCache
+{
+public:
+ MOCK_METHOD(NApi::IClientPtr, GetClient, (TStringBuf url), (override));
+};
+
+NApi::IClientPtr CreateTestHedgingClient(
+ TDuration banPenalty,
+ TDuration banDuration,
+ std::initializer_list<NApi::IClientPtr> clients,
+ std::initializer_list<TDuration> initialPenalties = {TDuration::Zero(), SleepQuantum},
+ const IPenaltyProviderPtr& penaltyProvider = CreateDummyPenaltyProvider())
+{
+ THedgingClientOptions options;
+ options.BanPenalty = banPenalty;
+ options.BanDuration = banDuration;
+ size_t clientId = 0;
+ for (auto [client, initialPenalty] : Zip(clients, initialPenalties)) {
+ auto currCliendId = "seneca-" + ToString(++clientId);
+ options.Clients.emplace_back(client, currCliendId, initialPenalty, New<TCounter>(currCliendId));
+ }
+ return CreateHedgingClient(options, penaltyProvider);
+}
+
+IPenaltyProviderPtr CreateReplicationLagPenaltyProvider(
+ const NYPath::TYPath& path,
+ const TString& cluster,
+ TDuration maxTabletLag,
+ TDuration lagPenalty,
+ NApi::IClientPtr client,
+ const bool clearPenaltiesOnErrors = false,
+ const TDuration checkPeriod = CheckPeriod)
+{
+ TReplicationLagPenaltyProviderConfig config;
+
+ config.SetTablePath(path);
+ config.AddReplicaClusters(cluster);
+ config.SetMaxTabletsWithLagFraction(0.5);
+ config.SetMaxTabletLag(maxTabletLag.Seconds());
+ config.SetCheckPeriod(checkPeriod.Seconds());
+ config.SetLagPenalty(lagPenalty.MilliSeconds());
+ config.SetClearPenaltiesOnErrors(clearPenaltiesOnErrors);
+
+ return CreateReplicationLagPenaltyProvider(config, client);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Using LinkNode method for testing because it's return value is YsonString.
+// It makes easier to check from which client result has come from just by comparing corresponding string values.
+TEST(THedgingClientTest, GetResultFromClientWithMinEffectivePenalty)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(clientResult1)));
+ EXPECT_CALL(*mockClient2, ListNode(path, _)).Times(0);
+
+ auto hedgingClient = CreateTestHedgingClient(
+ SleepQuantum * 2,
+ SleepQuantum,
+ {mockClient1, mockClient2});
+
+ auto queryResult = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that query result is from first client, because it's effective initial penalty is minimal.
+ EXPECT_TRUE(queryResult.IsOK());
+ EXPECT_EQ(queryResult.Value().AsStringBuf(), clientResult1.AsStringBuf());
+}
+
+TEST(THedgingClientTest, GetclientResult2WhenFirstClientHasFailed)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillOnce(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(clientResult2)));
+
+ auto client = CreateTestHedgingClient(
+ SleepQuantum * 2,
+ SleepQuantum * 2,
+ {mockClient1, mockClient2});
+
+ auto queryResult = NConcurrency::WaitFor(client->ListNode(path));
+ // Check that query result is from second client, because first client returned failure and got banned.
+ EXPECT_TRUE(queryResult.IsOK());
+ EXPECT_EQ(queryResult.Value().AsStringBuf(), clientResult2.AsStringBuf());
+}
+
+TEST(THedgingClientTest, GetclientResult1AfterBanTimeHasElapsed)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillOnce(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))))
+ .WillOnce(Return(MakeFuture(clientResult1)));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(clientResult2)));
+
+ auto banDuration = SleepQuantum * 2;
+ auto hedgingClient = CreateTestHedgingClient(
+ banDuration,
+ banDuration,
+ {mockClient1, mockClient2});
+
+ auto queryResult1 = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that first query result is from second client, because first client returned failure and got banned.
+ EXPECT_TRUE(queryResult1.IsOK());
+ EXPECT_EQ(queryResult1.Value().AsStringBuf(), clientResult2.AsStringBuf());
+
+ NConcurrency::TDelayedExecutor::WaitForDuration(banDuration);
+
+ auto queryResult2 = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that second query result is from first client, because ban time has elapsed and it's effective initial penalty is minimal again.
+ EXPECT_TRUE(queryResult2.IsOK());
+ EXPECT_EQ(queryResult2.Value().AsStringBuf(), clientResult1.AsStringBuf());
+}
+
+TEST(THedgingClientTest, GetclientResult2WhenFirstClientIsBanned)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillOnce(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillRepeatedly(Return(MakeFuture(clientResult2)));
+
+ auto hedgingClient = CreateTestHedgingClient(
+ SleepQuantum * 2,
+ TDuration::Seconds(2),
+ {mockClient1, mockClient2});
+
+ auto queryResult1 = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that first query result is from second client, because first client returned failure and got banned.
+ EXPECT_TRUE(queryResult1.IsOK());
+ EXPECT_EQ(queryResult1.Value().AsStringBuf(), clientResult2.AsStringBuf());
+
+ auto queryResult2 = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that second query result is from second client, because first client is still banned.
+ EXPECT_TRUE(queryResult2.IsOK());
+ EXPECT_EQ(queryResult2.Value().AsStringBuf(), clientResult2.AsStringBuf());
+}
+
+TEST(THedgingClientTest, GetclientResult2WhenFirstClientIsSleeping)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillOnce(Return(NConcurrency::TDelayedExecutor::MakeDelayed(TDuration::Seconds(2)).Apply(BIND([=] () { return clientResult1; }))));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillRepeatedly(Return(MakeFuture(clientResult2)));
+
+ auto hedgingClient = CreateTestHedgingClient(
+ SleepQuantum * 2,
+ SleepQuantum,
+ {mockClient1, mockClient2});
+
+ auto queryResult = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that query result is from second client, because first client is sleeping.
+ EXPECT_TRUE(queryResult.IsOK());
+ EXPECT_EQ(queryResult.Value().AsStringBuf(), clientResult2.AsStringBuf());
+}
+
+TEST(THedgingClientTest, FirstClientIsBannedBecauseResponseWasCancelled)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillOnce(Return(NConcurrency::TDelayedExecutor::MakeDelayed(SleepQuantum * 2).Apply(BIND([=] () { return clientResult1; }))))
+ .WillRepeatedly(Return(MakeFuture(clientResult1)));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillRepeatedly(Return(MakeFuture(clientResult2)));
+
+ auto client = CreateTestHedgingClient(
+ SleepQuantum * 2,
+ TDuration::Seconds(2),
+ {mockClient1, mockClient2});
+
+ auto queryResult1 = NConcurrency::WaitFor(client->ListNode(path));
+ // Check that query result is from second client, because first client is sleeping.
+ EXPECT_TRUE(queryResult1.IsOK());
+ EXPECT_EQ(queryResult1.Value().AsStringBuf(), clientResult2.AsStringBuf());
+
+ // Wait for finish of all requests
+ Sleep(SleepQuantum);
+
+ auto queryResult2 = NConcurrency::WaitFor(client->ListNode(path));
+ // Check that second query result is from second client, because first client was cancelled and got banned.
+ EXPECT_TRUE(queryResult2.IsOK());
+ EXPECT_EQ(queryResult2.Value().AsStringBuf(), clientResult2.AsStringBuf());
+}
+
+TEST(THedgingClientTest, AmnestyBanPenaltyIfClientSucceeded)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+ NYson::TYsonString thirdClientResult(TStringBuf("ThirdClientData"));
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+ auto mockClient3 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillOnce(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))))
+ .WillRepeatedly(Return(MakeFuture(clientResult1)));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(clientResult2)))
+ .WillOnce(Return(NConcurrency::TDelayedExecutor::MakeDelayed(TDuration::Seconds(100)).Apply(BIND([=] () { return clientResult2; }))))
+ .WillRepeatedly(Return(MakeFuture(clientResult2)));
+ EXPECT_CALL(*mockClient3, ListNode(path, _))
+ .WillRepeatedly(Return(NConcurrency::TDelayedExecutor::MakeDelayed(TDuration::Seconds(100)).Apply(BIND([=] () { return thirdClientResult; }))));
+
+ auto client = CreateTestHedgingClient(
+ SleepQuantum * 2,
+ TDuration::Seconds(30),
+ {mockClient1, mockClient2, mockClient3},
+ {TDuration::Zero(), SleepQuantum, SleepQuantum * 2});
+
+ auto queryResult1 = NConcurrency::WaitFor(client->ListNode(path));
+ // Check that query result is from second client, because first client finished with an error.
+ EXPECT_TRUE(queryResult1.IsOK());
+ EXPECT_EQ(queryResult1.Value().AsStringBuf(), clientResult2.AsStringBuf());
+
+ // Wait for finish of all requests
+ Sleep(SleepQuantum * 2);
+
+ auto queryResult2 = NConcurrency::WaitFor(client->ListNode(path));
+ // Check that second query result is from first client, because other clients were sleeping.
+ EXPECT_TRUE(queryResult2.IsOK());
+ EXPECT_EQ(queryResult2.Value().AsStringBuf(), clientResult1.AsStringBuf());
+
+ // Wait for finish of all requests
+ Sleep(SleepQuantum * 2);
+
+ auto queryResult3 = NConcurrency::WaitFor(client->ListNode(path));
+ // Check that third query result is from first client again, because it's penalty was amnestied.
+ EXPECT_TRUE(queryResult3.IsOK());
+ EXPECT_EQ(queryResult3.Value().AsStringBuf(), clientResult1.AsStringBuf());
+}
+
+TEST(THedgingClientTest, MultiThread)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _)).WillRepeatedly([=] (const NYPath::TYPath&, const NApi::TListNodeOptions& options) {
+ if (options.Timeout) {
+ return NConcurrency::TDelayedExecutor::MakeDelayed(*options.Timeout).Apply(BIND([=] () {
+ return clientResult1;
+ }));
+ }
+ return MakeFuture(clientResult1);
+ });
+ EXPECT_CALL(*mockClient2, ListNode(path, _)).WillRepeatedly(Return(MakeFuture(clientResult2)));
+
+ auto hedgingClient = CreateTestHedgingClient(
+ TDuration::MilliSeconds(1),
+ SleepQuantum,
+ {mockClient1, mockClient2},
+ {SleepQuantum, SleepQuantum * 3});
+
+ auto threadPool = NConcurrency::CreateThreadPool(10, "test");
+ std::vector<TFuture<void>> futures;
+ for (int i = 0; i < 100; ++i) {
+ futures.push_back(BIND([=] () {
+ for (int j = 0; j < 100; ++j) {
+ NApi::TListNodeOptions options;
+ // on each 5th request for 1st and 2nd thread, the first client will timeout
+ if (i < 2 && (0 == j % 5)) {
+ options.Timeout = TDuration::Seconds(2);
+ }
+ auto v = NConcurrency::WaitFor(hedgingClient->ListNode(path, options)).ValueOrThrow();
+ if (options.Timeout) {
+ EXPECT_EQ(clientResult2.AsStringBuf(), v.AsStringBuf());
+ } else {
+ EXPECT_EQ(clientResult1.AsStringBuf(), v.AsStringBuf());
+ }
+ }
+ }).AsyncVia(threadPool->GetInvoker()).Run());
+ }
+
+ for (const auto& future : futures) {
+ EXPECT_NO_THROW(future.Get().ThrowOnError());
+ }
+}
+
+TEST(THedgingClientTest, ResponseFromSecondClientWhenFirstHasReplicationLag)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillRepeatedly(Return(MakeFuture(clientResult1)));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillRepeatedly(Return(MakeFuture(clientResult2)));
+
+ auto hedgingClient = CreateTestHedgingClient(
+ SleepQuantum * 2,
+ SleepQuantum,
+ {mockClient1, mockClient2});
+
+ auto queryResult = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that query result is from first client, because it's effective initial penalty is minimal.
+ EXPECT_TRUE(queryResult.IsOK());
+ EXPECT_EQ(queryResult.Value().AsStringBuf(), clientResult1.AsStringBuf());
+
+ TString cluster = "seneca-1";
+ auto maxTabletLag = TDuration::Seconds(10);
+ auto lagPenalty = 2 * SleepQuantum;
+
+ NYson::TYsonString replicasResult(TStringBuf("{\"575f-131-40502c5-201b420f\" = {\"cluster_name\" = \"seneca-1\"}}"));
+ NYson::TYsonString tabletCountResult(TStringBuf("1"));
+
+ auto mockClient3 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient3, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture(replicasResult)));
+
+ EXPECT_CALL(*mockClient3, GetNode(path + "/@tablet_count", _))
+ .WillRepeatedly(Return(MakeFuture(tabletCountResult)));
+
+ std::vector<NApi::TTabletInfo> tabletInfos(1);
+ tabletInfos[0].TableReplicaInfos = std::make_optional(std::vector<NApi::TTabletInfo::TTableReplicaInfo>());
+ auto& replicaTabletsInfo = tabletInfos[0].TableReplicaInfos->emplace_back();
+ replicaTabletsInfo.ReplicaId = NTabletClient::TTableReplicaId::FromString("575f-131-40502c5-201b420f");
+ replicaTabletsInfo.LastReplicationTimestamp = NTransactionClient::TimestampFromUnixTime(
+ TInstant::Now().Seconds() - 2 * maxTabletLag.Seconds());
+
+ EXPECT_CALL(*mockClient3, GetTabletInfos(path, _, _))
+ .WillRepeatedly(Return(MakeFuture(tabletInfos)));
+
+ auto penaltyProvier = CreateReplicationLagPenaltyProvider(
+ path,
+ cluster,
+ maxTabletLag,
+ lagPenalty,
+ mockClient3);
+ Sleep(2 * CheckPeriod);
+
+ auto hedgingClientWithPenaltyProvider = CreateTestHedgingClient(
+ TDuration::MilliSeconds(1),
+ SleepQuantum,
+ {mockClient1, mockClient2},
+ {TDuration::Zero(), SleepQuantum},
+ penaltyProvier);
+
+ auto queryResultWithReplicationLagPolicy = NConcurrency::WaitFor(hedgingClientWithPenaltyProvider->ListNode(path));
+
+ // Check that query result is from second client, because first client received penalty updater because of replication lag.
+ EXPECT_TRUE(queryResultWithReplicationLagPolicy.IsOK());
+ EXPECT_EQ(queryResultWithReplicationLagPolicy.Value().AsStringBuf(), clientResult2.AsStringBuf());
+}
+
+TEST(THedgingClientTest, CreatingHedgingClientWithPreinitializedClients)
+{
+ const TString clusterName = "test_cluster";
+ NYPath::TYPath path = "/test/1234";
+ NYson::TYsonString clientResult(TStringBuf("ClientData"));
+
+ auto mockClient = New<TStrictMockClient>();
+ EXPECT_CALL(*mockClient, ListNode(path, _))
+ .WillOnce(Return(MakeFuture(clientResult)));
+
+ auto mockClientsCache = New<StrictMock<TMockClientsCache>>();
+ EXPECT_CALL(*mockClientsCache, GetClient(clusterName)).WillOnce(Return(mockClient));
+
+ THedgingClientConfig hedgingClientConfig;
+ hedgingClientConfig.SetBanDuration(100);
+ hedgingClientConfig.SetBanPenalty(200);
+ auto clientOptions = hedgingClientConfig.AddClients();
+ clientOptions->SetInitialPenalty(0);
+ clientOptions->MutableClientConfig()->SetClusterName(clusterName);
+
+ auto hedgingClient = CreateHedgingClient(hedgingClientConfig, mockClientsCache);
+
+ auto queryResult = NConcurrency::WaitFor(hedgingClient->ListNode(path));
+ // Check that query result is from preinitialized client.
+ EXPECT_TRUE(queryResult.IsOK());
+ EXPECT_EQ(queryResult.Value().AsStringBuf(), clientResult.AsStringBuf());
+}
+
+TEST(THedgingClientTest, ResponseFromFirstClientWhenReplicationLagUpdaterFails)
+{
+ NYPath::TYPath path = "/test/1234";
+
+ auto mockClient1 = New<TStrictMockClient>();
+ auto mockClient2 = New<TStrictMockClient>();
+ NYson::TYsonString clientResult1(TStringBuf("FirstClientData"));
+ NYson::TYsonString clientResult2(TStringBuf("SecondClientData"));
+
+ EXPECT_CALL(*mockClient1, ListNode(path, _))
+ .WillRepeatedly(Return(MakeFuture(clientResult1)));
+ EXPECT_CALL(*mockClient2, ListNode(path, _))
+ .WillRepeatedly(Return(MakeFuture(clientResult2)));
+
+ TString cluster = "seneca-1";
+ auto maxTabletLag = TDuration::Seconds(10);
+ auto lagPenalty = 2 * SleepQuantum;
+
+ NYson::TYsonString replicasResult(TStringBuf("{\"575f-131-40502c5-201b420f\" = {\"cluster_name\" = \"seneca-1\"}}"));
+ NYson::TYsonString tabletCountResult(TStringBuf("1"));
+
+ auto mockClient3 = New<TStrictMockClient>();
+
+ EXPECT_CALL(*mockClient3, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture(replicasResult)));
+
+ EXPECT_CALL(*mockClient3, GetNode(path + "/@tablet_count", _))
+ .WillOnce(Return(MakeFuture(tabletCountResult)))
+ .WillRepeatedly(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+
+ std::vector<NApi::TTabletInfo> tabletInfos(1);
+ tabletInfos[0].TableReplicaInfos = std::make_optional(std::vector<NApi::TTabletInfo::TTableReplicaInfo>());
+ auto& replicaTabletsInfo = tabletInfos[0].TableReplicaInfos->emplace_back();
+ replicaTabletsInfo.ReplicaId = NTabletClient::TTableReplicaId::FromString("575f-131-40502c5-201b420f");
+ replicaTabletsInfo.LastReplicationTimestamp = NTransactionClient::TimestampFromUnixTime(TInstant::Now().Seconds() - 2 * maxTabletLag.Seconds());
+
+ EXPECT_CALL(*mockClient3, GetTabletInfos(path, _, _))
+ .WillRepeatedly(Return(MakeFuture(tabletInfos)));
+
+ auto penaltyProvider = CreateReplicationLagPenaltyProvider(
+ path,
+ cluster,
+ maxTabletLag,
+ lagPenalty,
+ mockClient3,
+ true,
+ 2 * CheckPeriod);
+ Sleep(CheckPeriod);
+
+ auto hedgingClientWithPenaltyProvider = CreateTestHedgingClient(
+ TDuration::MilliSeconds(1),
+ SleepQuantum,
+ {mockClient1, mockClient2},
+ {TDuration::Zero(), SleepQuantum},
+ penaltyProvider);
+
+ auto queryResultWithReplicationLagPolicy = NConcurrency::WaitFor(hedgingClientWithPenaltyProvider->ListNode(path));
+ // Check that query result is from second client, because first client received penalty because of replication lag.
+ EXPECT_TRUE(queryResultWithReplicationLagPolicy.IsOK());
+ EXPECT_EQ(queryResultWithReplicationLagPolicy.Value().AsStringBuf(), clientResult2.AsStringBuf());
+
+ Sleep(2 * CheckPeriod);
+
+ auto queryResultWithCleanedPenalty = NConcurrency::WaitFor(hedgingClientWithPenaltyProvider->ListNode(path));
+ // Check that query result is from first client, because replication lag was cleaned.
+ EXPECT_TRUE(queryResultWithCleanedPenalty.IsOK());
+ EXPECT_EQ(queryResultWithCleanedPenalty.Value().AsStringBuf(), clientResult1.AsStringBuf());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/unittests/hook.cpp b/yt/yt/client/hedging/unittests/hook.cpp
new file mode 100644
index 0000000000..d7c527999b
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/hook.cpp
@@ -0,0 +1,20 @@
+#include <yt/yt/core/logging/log_manager.h>
+
+#include <yt/yt/core/misc/shutdown.h>
+
+#include <library/cpp/testing/hook/hook.h>
+
+Y_TEST_HOOK_BEFORE_RUN(TEST_YT_SETUP)
+{
+ NYT::NLogging::TLogManager::Get()->ConfigureFromEnv();
+}
+
+Y_TEST_HOOK_AFTER_RUN(TEST_YT_TEARDOWN)
+{
+ NYT::Shutdown();
+#ifdef _asan_enabled_
+ // Wait for some time to ensure background cleanup is somewhat complete.
+ Sleep(TDuration::Seconds(1));
+ NYT::TRefCountedTrackerFacade::Dump();
+#endif
+}
diff --git a/yt/yt/client/hedging/unittests/options_ut.cpp b/yt/yt/client/hedging/unittests/options_ut.cpp
new file mode 100644
index 0000000000..3c9c11b322
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/options_ut.cpp
@@ -0,0 +1,83 @@
+#include <yt/yt/client/hedging/options.h>
+
+#include <library/cpp/testing/common/scope.h>
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <util/folder/dirut.h>
+#include <util/folder/tempdir.h>
+
+#include <util/stream/file.h>
+
+#include <util/system/env.h>
+
+namespace NYT::NClient::NHedging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TClientOptionsTest, TokenFromFile)
+{
+ TTempDir tmpDir;
+ MakeDirIfNotExist(tmpDir.Name() + "/.yt");
+ {
+ TFileOutput token(tmpDir.Name() + "/.yt/token");
+ token.Write(" AAAA-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \n");
+ }
+ NTesting::TScopedEnvironment envGuard{{
+ {"HOME", tmpDir.Name()},
+ {"YT_TOKEN", ""},
+ {"YT_TOKEN_PATH", ""},
+ }};
+ const auto clientOptions = GetClientOpsFromEnv();
+ EXPECT_TRUE(clientOptions.Token);
+ EXPECT_EQ("AAAA-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", *clientOptions.Token);
+}
+
+TEST(TClientOptionsTest, TokenFromYtTokenPath)
+{
+ TTempDir tmpDir;
+ const TString tokenPath = tmpDir.Name() + "/token";
+ {
+ TFileOutput token(tokenPath);
+ token.Write(" BBBB-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA \n");
+ }
+ NTesting::TScopedEnvironment envGuard{{
+ {"YT_TOKEN", ""},
+ {"YT_TOKEN_PATH", tokenPath},
+ }};
+ const auto clientOptions = GetClientOpsFromEnv();
+ EXPECT_TRUE(clientOptions.Token);
+ EXPECT_EQ("BBBB-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", *clientOptions.Token);
+}
+
+TEST(TClientOptionsTest, TokenFromEnv)
+{
+ NTesting::TScopedEnvironment tokenGuard("YT_TOKEN", "BBBB-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
+ const auto& clientOptions = GetClientOpsFromEnv();
+ EXPECT_TRUE(clientOptions.Token);
+ EXPECT_EQ("BBBB-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", *clientOptions.Token);
+}
+
+TEST(TClientOptionsTest, UserFromEnv)
+{
+ NTesting::TScopedEnvironment envGuard{{
+ {"YT_TOKEN", "BBBB-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"},
+ {"YT_USER", "yt_test_user"},
+ }};
+ const auto& clientOptions = GetClientOpsFromEnv();
+ EXPECT_TRUE(clientOptions.User);
+ EXPECT_EQ("yt_test_user", *clientOptions.User);
+}
+
+TEST(TClientOptionsTest, AllowEmptyUser)
+{
+ NTesting::TScopedEnvironment envGuard{{
+ {"YT_TOKEN", "BBBB-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"},
+ {"YT_USER", ""},
+ }};
+ const auto& clientOptions = GetClientOpsFromEnv();
+ EXPECT_TRUE(!clientOptions.User);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging
diff --git a/yt/yt/client/hedging/unittests/penalty_provider_ut.cpp b/yt/yt/client/hedging/unittests/penalty_provider_ut.cpp
new file mode 100644
index 0000000000..914c0d6ce7
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/penalty_provider_ut.cpp
@@ -0,0 +1,224 @@
+#include <yt/yt/client/hedging/penalty_provider.h>
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <yt/yt/client/transaction_client/helpers.h>
+
+#include <yt/yt/client/unittests/mock/client.h>
+
+#include <yt/yt/core/profiling/timing.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+using TStrictMockClient = StrictMock<NApi::TMockClient>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+ const auto CheckPeriod = TDuration::Seconds(1);
+
+ TReplicationLagPenaltyProviderConfig GenerateReplicationLagPenaltyProviderConfig(
+ const NYPath::TYPath& path,
+ const TString& cluster,
+ const ui32 maxLagInSeconds = 10,
+ const bool clearPenaltiesOnErrors = false,
+ const TDuration checkPeriod = CheckPeriod)
+ {
+ TReplicationLagPenaltyProviderConfig config;
+
+ config.SetTablePath(path);
+ config.AddReplicaClusters(cluster);
+ config.SetMaxTabletsWithLagFraction(0.5);
+ config.SetMaxTabletLag(maxLagInSeconds);
+ config.SetCheckPeriod(checkPeriod.Seconds());
+ config.SetClearPenaltiesOnErrors(clearPenaltiesOnErrors);
+
+ return config;
+ }
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TLagPenaltyProviderTest, UpdateExternalPenaltyWhenReplicaHasLag)
+{
+ NYPath::TYPath path = "/test/1234";
+ TString cluster = "seneca-vla";
+
+ NYson::TYsonString replicasResult(TStringBuf("{\"575f-131-40502c5-201b420f\" = {\"cluster_name\" = \"seneca-vla\"}}"));
+ NYson::TYsonString tabletCountResult(TStringBuf("1"));
+
+ ui32 maxLagInSeconds = 10;
+ TReplicationLagPenaltyProviderConfig config = GenerateReplicationLagPenaltyProviderConfig(path, cluster, maxLagInSeconds);
+
+ auto client = New<TStrictMockClient>();
+
+ EXPECT_CALL(*client, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture(replicasResult)));
+
+ EXPECT_CALL(*client, GetNode(path + "/@tablet_count", _))
+ .WillRepeatedly(Return(MakeFuture(tabletCountResult)));
+
+ std::vector<NApi::TTabletInfo> tabletInfos(1);
+ tabletInfos[0].TableReplicaInfos = std::make_optional(std::vector<NApi::TTabletInfo::TTableReplicaInfo>());
+ auto& replicaTabletsInfo = tabletInfos[0].TableReplicaInfos->emplace_back();
+ replicaTabletsInfo.ReplicaId = NTabletClient::TTableReplicaId::FromString("575f-131-40502c5-201b420f");
+ replicaTabletsInfo.LastReplicationTimestamp = NTransactionClient::TimestampFromUnixTime(TInstant::Now().Seconds() - 2 * maxLagInSeconds);
+
+ EXPECT_CALL(*client, GetTabletInfos(path, _, _))
+ .WillRepeatedly(Return(MakeFuture(tabletInfos)));
+
+ auto PenaltyProviderPtr = CreateReplicationLagPenaltyProvider(config, client);
+ Sleep(2 * CheckPeriod);
+
+ EXPECT_EQ(PenaltyProviderPtr->Get(cluster), NProfiling::DurationToCpuDuration(TDuration::MilliSeconds(config.GetLagPenalty())));
+}
+
+TEST(TLagPenaltyProviderTest, DoNotUpdatePenaltyWhenReplicaHasNoLag)
+{
+ NYPath::TYPath path = "/test/1234";
+ TString cluster = "seneca-vla";
+
+ NYson::TYsonString replicasResult(TStringBuf("{\"575f-131-40502c5-201b420f\" = {\"cluster_name\" = \"seneca-vla\"}}"));
+ NYson::TYsonString tabletCountResult(TStringBuf("1"));
+
+ TReplicationLagPenaltyProviderConfig config = GenerateReplicationLagPenaltyProviderConfig(path, cluster);
+
+ auto client = New<TStrictMockClient>();
+
+ EXPECT_CALL(*client, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture(replicasResult)));
+
+ EXPECT_CALL(*client, GetNode(path + "/@tablet_count", _))
+ .WillRepeatedly(Return(MakeFuture(tabletCountResult)));
+
+ std::vector<NApi::TTabletInfo> tabletInfos(1);
+ tabletInfos[0].TableReplicaInfos = std::make_optional(std::vector<NApi::TTabletInfo::TTableReplicaInfo>());
+ auto& replicaTabletsInfo = tabletInfos[0].TableReplicaInfos->emplace_back();
+ replicaTabletsInfo.ReplicaId = NTabletClient::TTableReplicaId::FromString("575f-131-40502c5-201b420f");
+ replicaTabletsInfo.LastReplicationTimestamp = NTransactionClient::TimestampFromUnixTime(TInstant::Now().Seconds());
+
+ EXPECT_CALL(*client, GetTabletInfos(path, _, _))
+ .WillRepeatedly(Return(MakeFuture(tabletInfos)));
+
+ auto PenaltyProviderPtr = CreateReplicationLagPenaltyProvider(config, client);
+ Sleep(2 * CheckPeriod);
+
+ EXPECT_EQ(PenaltyProviderPtr->Get(cluster), 0);
+}
+
+TEST(TLagPenaltyProviderTest, DoNotUpdatePenaltyWhenGetReplicaIdFailed)
+{
+ NYPath::TYPath path = "/test/1234";
+ TString cluster = "seneca-vla";
+
+ NClient::NHedging::NRpc::TReplicationLagPenaltyProviderConfig config = GenerateReplicationLagPenaltyProviderConfig(path, cluster);
+
+ auto client = New<TStrictMockClient>();
+
+ EXPECT_CALL(*client, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+
+ auto PenaltyProviderPtr = CreateReplicationLagPenaltyProvider(config, client);
+ Sleep(2 * CheckPeriod);
+
+ EXPECT_EQ(PenaltyProviderPtr->Get(cluster), 0);
+}
+
+TEST(TLagPenaltyProviderTest, DoNotUpdatePenaltyWhenGetTabletsCountFailed)
+{
+ NYPath::TYPath path = "/test/1234";
+ TString cluster = "seneca-vla";
+
+ NYson::TYsonString replicasResult(TStringBuf("{\"575f-131-40502c5-201b420f\" = {\"cluster_name\" = \"seneca-vla\"}}"));
+
+ TReplicationLagPenaltyProviderConfig config = GenerateReplicationLagPenaltyProviderConfig(path, cluster);
+
+ auto client = New<TStrictMockClient>();
+
+ EXPECT_CALL(*client, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture(replicasResult)));
+
+ EXPECT_CALL(*client, GetNode(path + "/@tablet_count", _))
+ .WillRepeatedly(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+
+ auto PenaltyProviderPtr = CreateReplicationLagPenaltyProvider(config, client);
+ Sleep(2 * CheckPeriod);
+
+ EXPECT_EQ(PenaltyProviderPtr->Get(cluster), 0);
+}
+
+TEST(TLagPenaltyProviderTest, DoNotUpdatePenaltyWhenGetTabletsInfoFailed)
+{
+ NYPath::TYPath path = "/test/1234";
+ TString cluster = "seneca-vla";
+
+ NYson::TYsonString replicasResult(TStringBuf("{\"575f-131-40502c5-201b420f\" = {\"cluster_name\" = \"seneca-vla\"}}"));
+ NYson::TYsonString tabletCountResult(TStringBuf("1"));
+
+ NClient::NHedging::NRpc::TReplicationLagPenaltyProviderConfig config =
+ GenerateReplicationLagPenaltyProviderConfig(path, cluster);
+
+ auto client = New<TStrictMockClient>();
+
+ EXPECT_CALL(*client, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture(replicasResult)));
+
+ EXPECT_CALL(*client, GetNode(path + "/@tablet_count", _))
+ .WillRepeatedly(Return(MakeFuture(tabletCountResult)));
+
+ EXPECT_CALL(*client, GetTabletInfos(path, _, _))
+ .WillRepeatedly(Return(MakeFuture<std::vector<NApi::TTabletInfo>>(TError("Failure"))));
+
+ auto PenaltyProviderPtr = CreateReplicationLagPenaltyProvider(config, client);
+ Sleep(2 * CheckPeriod);
+
+ EXPECT_EQ(PenaltyProviderPtr->Get(cluster), 0);
+}
+
+TEST(TLagPenaltyProviderTest, ClearPenaltiesAfterError)
+{
+ NYPath::TYPath path = "/test/1234";
+ TString cluster = "seneca-vla";
+
+ NYson::TYsonString replicasResult(TStringBuf("{\"575f-131-40502c5-201b420f\" = {\"cluster_name\" = \"seneca-vla\"}}"));
+ NYson::TYsonString tabletCountResult(TStringBuf("1"));
+
+ ui32 maxLagInSeconds = 10;
+ auto config =
+ GenerateReplicationLagPenaltyProviderConfig(path, cluster, maxLagInSeconds, true, 2 * CheckPeriod);
+
+ auto client = New<TStrictMockClient>();
+
+ EXPECT_CALL(*client, GetNode(path + "/@replicas", _))
+ .WillRepeatedly(Return(MakeFuture(replicasResult)));
+
+ EXPECT_CALL(*client, GetNode(path + "/@tablet_count", _))
+ .WillOnce(Return(MakeFuture(tabletCountResult)))
+ .WillRepeatedly(Return(MakeFuture<NYson::TYsonString>(TError("Failure"))));
+
+ std::vector<NApi::TTabletInfo> tabletInfos(1);
+ tabletInfos[0].TableReplicaInfos = std::make_optional(std::vector<NApi::TTabletInfo::TTableReplicaInfo>());
+ auto& replicaTabletsInfo = tabletInfos[0].TableReplicaInfos->emplace_back();
+ replicaTabletsInfo.ReplicaId = NTabletClient::TTableReplicaId::FromString("575f-131-40502c5-201b420f");
+ replicaTabletsInfo.LastReplicationTimestamp = NTransactionClient::TimestampFromUnixTime(TInstant::Now().Seconds() - 2 * maxLagInSeconds);
+
+ EXPECT_CALL(*client, GetTabletInfos(path, _, _))
+ .WillRepeatedly(Return(MakeFuture(tabletInfos)));
+
+ auto PenaltyProviderPtr = CreateReplicationLagPenaltyProvider(config, client);
+ Sleep(CheckPeriod);
+
+ EXPECT_EQ(PenaltyProviderPtr->Get(cluster), NProfiling::DurationToCpuDuration(TDuration::MilliSeconds(config.GetLagPenalty())));
+
+ Sleep(2 * CheckPeriod);
+ EXPECT_EQ(PenaltyProviderPtr->Get(cluster), 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/unittests/rpc_ut.cpp b/yt/yt/client/hedging/unittests/rpc_ut.cpp
new file mode 100644
index 0000000000..09b7c4e820
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/rpc_ut.cpp
@@ -0,0 +1,35 @@
+#include <yt/yt/client/hedging/rpc.h>
+#include <yt/yt_proto/yt/client/hedging/proto/config.pb.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+namespace NYT::NClient::NHedging::NRpc {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(RpcClientTest, SetClusterUrlWithoutProxy)
+{
+ TConfig config;
+ SetClusterUrl(config, "markov");
+ EXPECT_EQ("markov", config.GetClusterName());
+ EXPECT_EQ("", config.GetProxyRole());
+}
+
+TEST(RpcClientTest, SetClusterUrlWithProxy)
+{
+ TConfig config;
+ SetClusterUrl(config, "markov/bigb");
+ EXPECT_EQ("markov", config.GetClusterName());
+ EXPECT_EQ("bigb", config.GetProxyRole());
+}
+
+TEST(RpcClientTest, ProxyRoleOverride)
+{
+ TConfig config;
+ config.SetProxyRole("role");
+ EXPECT_THROW(SetClusterUrl(config, "markov/bigb"), std::exception);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NClient::NHedging::NRpc
diff --git a/yt/yt/client/hedging/unittests/ya.make b/yt/yt/client/hedging/unittests/ya.make
new file mode 100644
index 0000000000..4ab3e1abbb
--- /dev/null
+++ b/yt/yt/client/hedging/unittests/ya.make
@@ -0,0 +1,30 @@
+GTEST()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+EXPLICIT_DATA()
+
+SRCS(
+ cache_ut.cpp
+ counters_ut.cpp
+ hedging_ut.cpp
+ options_ut.cpp
+ penalty_provider_ut.cpp
+ rpc_ut.cpp
+
+ GLOBAL hook.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ library/cpp/iterator
+ library/cpp/testing/common
+ library/cpp/testing/hook
+ yt/yt/client/hedging
+ yt/yt/client/unittests/mock
+ yt/yt/core
+ yt/yt/library/profiling/solomon
+)
+
+END()
diff --git a/yt/yt/client/hedging/ya.make b/yt/yt/client/hedging/ya.make
new file mode 100644
index 0000000000..acee58f50a
--- /dev/null
+++ b/yt/yt/client/hedging/ya.make
@@ -0,0 +1,28 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ cache.cpp
+ config.cpp
+ counter.cpp
+ hedging.cpp
+ hedging_executor.cpp
+ logger.cpp
+ options.cpp
+ penalty_provider.cpp
+ rpc.cpp
+)
+
+PEERDIR(
+ yt/yt/core
+ yt/yt/client
+ yt/yt/library/profiling
+ yt/yt_proto/yt/client/hedging
+
+ library/cpp/iterator
+)
+
+END()
+
+RECURSE_FOR_TESTS(unittests)
diff --git a/yt/yt/client/hive/public.h b/yt/yt/client/hive/public.h
new file mode 100644
index 0000000000..5e8c8284bc
--- /dev/null
+++ b/yt/yt/client/hive/public.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/client/hydra/public.h>
+
+#include <yt/yt/client/transaction_client/public.h>
+
+namespace NYT::NHiveClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+class TTimestampMap;
+class TClusterDirectory;
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+using NTransactionClient::TTransactionId;
+using NTransactionClient::NullTransactionId;
+using NTransactionClient::TTimestamp;
+using NTransactionClient::NullTimestamp;
+
+using NHydra::TCellId;
+using NHydra::NullCellId;
+
+struct TTimestampMap;
+
+DECLARE_REFCOUNTED_STRUCT(ITransactionParticipant)
+
+YT_DEFINE_ERROR_ENUM(
+ ((MailboxNotCreatedYet) (2200))
+ ((ParticipantUnregistered) (2201))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NHiveClient
diff --git a/yt/yt/client/hive/timestamp_map.cpp b/yt/yt/client/hive/timestamp_map.cpp
new file mode 100644
index 0000000000..02487f1525
--- /dev/null
+++ b/yt/yt/client/hive/timestamp_map.cpp
@@ -0,0 +1,77 @@
+#include "timestamp_map.h"
+
+#include <yt/yt/core/misc/serialize.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <yt/yt_proto/yt/client/hive/proto/timestamp_map.pb.h>
+
+namespace NYT::NHiveClient {
+
+using namespace NObjectClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<TTimestamp> TTimestampMap::FindTimestamp(TCellTag cellTag) const
+{
+ for (auto [someCellTag, someTimestamp] : Timestamps) {
+ if (someCellTag == cellTag) {
+ return someTimestamp;
+ }
+ }
+ return std::nullopt;
+}
+
+TTimestamp TTimestampMap::GetTimestamp(TCellTag cellTag) const
+{
+ auto timestamp = FindTimestamp(cellTag);
+ YT_VERIFY(timestamp);
+ return *timestamp;
+}
+
+void TTimestampMap::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+ Persist<TVectorSerializer<TTupleSerializer<std::pair<TCellTag, TTimestamp>, 2>>>(
+ context,
+ Timestamps);
+}
+
+void ToProto(NProto::TTimestampMap* protoMap, const TTimestampMap& map)
+{
+ protoMap->clear_cell_tags();
+ protoMap->clear_timestamps();
+ for (auto [cellTag, timestamp] : map.Timestamps) {
+ protoMap->add_cell_tags(ToProto<int>(cellTag));
+ protoMap->add_timestamps(timestamp);
+ }
+}
+
+void FromProto(TTimestampMap* map, const NProto::TTimestampMap& protoMap)
+{
+ map->Timestamps.clear();
+ YT_VERIFY(protoMap.cell_tags_size() == protoMap.timestamps_size());
+ for (int index = 0; index < protoMap.cell_tags_size(); ++index) {
+ map->Timestamps.emplace_back(
+ protoMap.cell_tags(index),
+ protoMap.timestamps(index));
+ }
+}
+
+void FormatValue(TStringBuilderBase* builder, const TTimestampMap& map, TStringBuf /*spec*/)
+{
+ builder->AppendChar('{');
+ bool first = true;
+ for (auto [cellTag, timestamp] : map.Timestamps) {
+ if (!first) {
+ builder->AppendString(TStringBuf(", "));
+ }
+ builder->AppendFormat("%v => %v", cellTag, timestamp);
+ first = false;
+ }
+ builder->AppendChar('}');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NHiveClient
diff --git a/yt/yt/client/hive/timestamp_map.h b/yt/yt/client/hive/timestamp_map.h
new file mode 100644
index 0000000000..1958a8a96b
--- /dev/null
+++ b/yt/yt/client/hive/timestamp_map.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+#include <optional>
+
+namespace NYT::NHiveClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTimestampMap
+{
+ TCompactVector<std::pair<NObjectClient::TCellTag, TTimestamp>, 4> Timestamps;
+
+ std::optional<TTimestamp> FindTimestamp(NObjectClient::TCellTag) const;
+ TTimestamp GetTimestamp(NObjectClient::TCellTag) const;
+
+ void Persist(const TStreamPersistenceContext& context);
+};
+
+void ToProto(NProto::TTimestampMap* protoMap, const TTimestampMap& map);
+void FromProto(TTimestampMap* map, const NProto::TTimestampMap& protoMap);
+
+void FormatValue(TStringBuilderBase* builder, const TTimestampMap& map, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NHiveClient
diff --git a/yt/yt/client/hive/transaction_participant.h b/yt/yt/client/hive/transaction_participant.h
new file mode 100644
index 0000000000..e52d381f26
--- /dev/null
+++ b/yt/yt/client/hive/transaction_participant.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/transaction_client/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NHiveClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ETransactionParticipantState,
+ // Participant is available for interaction; this does not imply any partcular health status.
+ (Valid)
+ // Cluster connection was terminated, participant is no longer usable.
+ (Invalidated)
+ // Participant is not known; e.g. cluster is not registered.
+ (NotRegistered)
+ // Participant is known to be unregistered and will never be resurrected.
+ (Unregistered)
+);
+
+struct ITransactionParticipant
+ : public virtual TRefCounted
+{
+ virtual TCellId GetCellId() const = 0;
+
+ //! Returns tag of a cluster timestamps were generated from.
+ virtual NApi::TClusterTag GetClockClusterTag() const = 0;
+
+ virtual const NTransactionClient::ITimestampProviderPtr& GetTimestampProvider() const = 0;
+
+ virtual ETransactionParticipantState GetState() const = 0;
+
+ virtual TFuture<void> PrepareTransaction(
+ TTransactionId transactionId,
+ TTimestamp prepareTimestamp,
+ NApi::TClusterTag prepareTimestampClusterTag,
+ const std::vector<TCellId>& cellIdsToSyncWith,
+ const NRpc::TAuthenticationIdentity& identity) = 0;
+ virtual TFuture<void> CommitTransaction(
+ TTransactionId transactionId,
+ TTimestamp commitTimestamp,
+ NApi::TClusterTag commitTimestampClusterTag,
+ const NRpc::TAuthenticationIdentity& identity) = 0;
+ virtual TFuture<void> AbortTransaction(
+ TTransactionId transactionId,
+ const NRpc::TAuthenticationIdentity& identity) = 0;
+
+ virtual TFuture<void> CheckAvailability() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ITransactionParticipant)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NHiveClient
diff --git a/yt/yt/client/hydra/public.h b/yt/yt/client/hydra/public.h
new file mode 100644
index 0000000000..5f1e63206b
--- /dev/null
+++ b/yt/yt/client/hydra/public.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <yt/yt/client/election/public.h>
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NHydra {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EPeerState,
+ ((None) (0))
+ ((Stopped) (1))
+ ((Elections) (2))
+ ((FollowerRecovery) (3))
+ ((Following) (4))
+ ((LeaderRecovery) (5))
+ ((Leading) (6))
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((NoSuchSnapshot) (600))
+ ((NoSuchChangelog) (601))
+ ((InvalidEpoch) (602))
+ ((InvalidVersion) (603))
+ ((OutOfOrderMutations) (609))
+ ((InvalidSnapshotVersion) (610))
+ ((ReadOnlySnapshotBuilt) (611))
+ ((ReadOnlySnapshotBuildFailed) (612))
+ ((BrokenChangelog) (613))
+ ((ChangelogIOError) (614))
+ ((InvalidChangelogState) (615))
+ ((ReadOnly) (616))
+);
+
+DEFINE_ENUM(EPeerKind,
+ ((Leader) (0))
+ ((Follower) (1))
+ ((LeaderOrFollower) (2))
+);
+
+using TRevision = ui64;
+constexpr TRevision NullRevision = 0;
+
+struct TVersion;
+struct TReachableState;
+struct TElectionPriority;
+
+////////////////////////////////////////////////////////////////////////////////
+
+using NElection::TCellId;
+using NElection::NullCellId;
+using NElection::TPeerId;
+using NElection::InvalidPeerId;
+using NElection::TPeerPriority;
+using NElection::TEpochId;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NHydra
diff --git a/yt/yt/client/hydra/version.cpp b/yt/yt/client/hydra/version.cpp
new file mode 100644
index 0000000000..904f090db6
--- /dev/null
+++ b/yt/yt/client/hydra/version.cpp
@@ -0,0 +1,112 @@
+#include "version.h"
+
+#include <library/cpp/yt/string/format.h>
+
+namespace NYT::NHydra {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TReachableState::TReachableState(int segmentId, i64 sequenceNumber) noexcept
+ : SegmentId(segmentId)
+ , SequenceNumber(sequenceNumber)
+{ }
+
+std::strong_ordering TReachableState::operator <=> (const TReachableState& other) const
+{
+ if (SegmentId != other.SegmentId) {
+ return SegmentId <=> other.SegmentId;
+ }
+ return SequenceNumber <=> other.SequenceNumber;
+}
+
+void FormatValue(TStringBuilderBase* builder, TReachableState state, TStringBuf /* spec */)
+{
+ builder->AppendFormat("%v:%v", state.SegmentId, state.SequenceNumber);
+}
+
+TString ToString(TReachableState state)
+{
+ return ToStringViaBuilder(state);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TElectionPriority::TElectionPriority(int lastMutationTerm, int segmentId, i64 sequenceNumber) noexcept
+ : LastMutationTerm(lastMutationTerm)
+ , ReachableState(segmentId, sequenceNumber)
+{ }
+
+TElectionPriority::TElectionPriority(int lastMutationTerm, TReachableState reachableState) noexcept
+ : LastMutationTerm(lastMutationTerm)
+ , ReachableState(reachableState)
+{ }
+
+std::strong_ordering TElectionPriority::operator <=> (const TElectionPriority& other) const
+{
+ if (LastMutationTerm != other.LastMutationTerm) {
+ return LastMutationTerm <=> other.LastMutationTerm;
+ }
+ return ReachableState.SequenceNumber <=> other.ReachableState.SequenceNumber;
+}
+
+void FormatValue(TStringBuilderBase* builder, TElectionPriority priority, TStringBuf /* spec */)
+{
+ builder->AppendFormat("{MutationTerm: %v, State: %v}",
+ priority.LastMutationTerm,
+ priority.ReachableState);
+}
+
+TString ToString(TElectionPriority state)
+{
+ return ToStringViaBuilder(state);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TVersion::TVersion(int segmentId, int recordId) noexcept
+ : SegmentId(segmentId)
+ , RecordId(recordId)
+{ }
+
+std::strong_ordering TVersion::operator <=> (const TVersion& other) const
+{
+ if (SegmentId != other.SegmentId) {
+ return SegmentId <=> other.SegmentId;
+ }
+ return RecordId <=> other.RecordId;
+}
+
+TRevision TVersion::ToRevision() const
+{
+ return (static_cast<TRevision>(SegmentId) << 32) | static_cast<TRevision>(RecordId);
+}
+
+TVersion TVersion::FromRevision(TRevision revision)
+{
+ return TVersion(revision >> 32, revision & 0xffffffff);
+}
+
+TVersion TVersion::Advance(int delta) const
+{
+ YT_ASSERT(delta >= 0);
+ return TVersion(SegmentId, RecordId + delta);
+}
+
+TVersion TVersion::Rotate() const
+{
+ return TVersion(SegmentId + 1, 0);
+}
+
+void FormatValue(TStringBuilderBase* builder, TVersion version, TStringBuf /* spec */)
+{
+ builder->AppendFormat("%v:%v", version.SegmentId, version.RecordId);
+}
+
+TString ToString(TVersion version)
+{
+ return ToStringViaBuilder(version);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NHydra
diff --git a/yt/yt/client/hydra/version.h b/yt/yt/client/hydra/version.h
new file mode 100644
index 0000000000..4ef7bfc7f5
--- /dev/null
+++ b/yt/yt/client/hydra/version.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include "public.h"
+
+#include <util/generic/typetraits.h>
+
+namespace NYT::NHydra {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TReachableState
+{
+ int SegmentId = 0;
+ i64 SequenceNumber = 0;
+
+ TReachableState() = default;
+ TReachableState(int segmentId, i64 sequenceNumber) noexcept;
+
+ std::strong_ordering operator <=> (const TReachableState& other) const;
+ bool operator == (const TReachableState& other) const = default;
+};
+
+void FormatValue(TStringBuilderBase* builder, TReachableState state, TStringBuf /*spec*/);
+TString ToString(TReachableState state);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TElectionPriority
+{
+ // Actual election priority is a pair (LastMutationTerm, ReachableState.SequenceNumber).
+ // Term is used to determine a new leader's term.
+ // ReachableState.SegmentId is used as a hint for recovery.
+
+ int LastMutationTerm = 0;
+ TReachableState ReachableState;
+
+ TElectionPriority() = default;
+ TElectionPriority(
+ int lastMutationTerm,
+ int segmentId,
+ i64 sequenceNumber) noexcept;
+ TElectionPriority(
+ int lastMutationTerm,
+ TReachableState reachableState) noexcept;
+
+ std::strong_ordering operator <=> (const TElectionPriority& other) const;
+ bool operator == (const TElectionPriority& other) const = default;
+};
+
+void FormatValue(TStringBuilderBase* builder, TElectionPriority state, TStringBuf /*spec*/);
+TString ToString(TElectionPriority state);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TVersion
+{
+ int SegmentId = 0;
+ int RecordId = 0;
+
+ TVersion() = default;
+ TVersion(int segmentId, int recordId) noexcept;
+
+ std::strong_ordering operator <=> (const TVersion& other) const;
+ bool operator == (const TVersion& other) const = default;
+
+ TRevision ToRevision() const;
+ static TVersion FromRevision(TRevision revision);
+
+ TVersion Advance(int delta = 1) const;
+ TVersion Rotate() const;
+};
+
+void FormatValue(TStringBuilderBase* builder, TVersion version, TStringBuf spec);
+TString ToString(TVersion version);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NHydra
+
+Y_DECLARE_PODTYPE(NYT::NHydra::TVersion);
diff --git a/yt/yt/client/job_tracker_client/helpers.cpp b/yt/yt/client/job_tracker_client/helpers.cpp
new file mode 100644
index 0000000000..0c8df0bb8e
--- /dev/null
+++ b/yt/yt/client/job_tracker_client/helpers.cpp
@@ -0,0 +1,29 @@
+#include "helpers.h"
+
+namespace NYT::NJobTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsJobFinished(EJobState state)
+{
+ return state == EJobState::Completed ||
+ state == EJobState::Failed ||
+ state == EJobState::Aborted ||
+ state == EJobState::Lost;
+}
+
+bool IsJobInProgress(EJobState state)
+{
+ return state == EJobState::Waiting ||
+ state == EJobState::Running ||
+ state == EJobState::Aborting;
+}
+
+bool IsMasterJobType(EJobType jobType)
+{
+ return FirstMasterJobType <= jobType && jobType <= LastMasterJobType;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJobTrackerClient
diff --git a/yt/yt/client/job_tracker_client/helpers.h b/yt/yt/client/job_tracker_client/helpers.h
new file mode 100644
index 0000000000..7a11c648f7
--- /dev/null
+++ b/yt/yt/client/job_tracker_client/helpers.h
@@ -0,0 +1,14 @@
+#include "public.h"
+
+namespace NYT::NJobTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsJobFinished(EJobState state);
+bool IsJobInProgress(EJobState state);
+
+bool IsMasterJobType(EJobType jobType);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJobTrackerClient
diff --git a/yt/yt/client/job_tracker_client/public.cpp b/yt/yt/client/job_tracker_client/public.cpp
new file mode 100644
index 0000000000..ab7e82cc78
--- /dev/null
+++ b/yt/yt/client/job_tracker_client/public.cpp
@@ -0,0 +1,13 @@
+#include "public.h"
+
+namespace NYT::NJobTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TJobId NullJobId;
+const TOperationId NullOperationId;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJobTrackerClient
+
diff --git a/yt/yt/client/job_tracker_client/public.h b/yt/yt/client/job_tracker_client/public.h
new file mode 100644
index 0000000000..bfa4869039
--- /dev/null
+++ b/yt/yt/client/job_tracker_client/public.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <library/cpp/yt/misc/enum.h>
+#include <library/cpp/yt/misc/guid.h>
+
+namespace NYT::NJobTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TJobId = TGuid;
+extern const TJobId NullJobId;
+
+using TOperationId = TGuid;
+extern const TOperationId NullOperationId;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Please keep the range of values small as this type
+// is used as a key of TEnumIndexedVector.
+DEFINE_ENUM(EJobType,
+ // Scheduler jobs
+ ((Map) ( 1))
+ ((PartitionMap) ( 2))
+ ((SortedMerge) ( 3))
+ ((OrderedMerge) ( 4))
+ ((UnorderedMerge) ( 5))
+ ((Partition) ( 6))
+ ((SimpleSort) ( 7))
+ ((FinalSort) ( 8))
+ ((SortedReduce) ( 9))
+ ((PartitionReduce) ( 10))
+ ((ReduceCombiner) ( 11))
+ ((RemoteCopy) ( 12))
+ ((IntermediateSort) ( 13))
+ ((OrderedMap) ( 14))
+ ((JoinReduce) ( 15))
+ ((Vanilla) ( 16))
+ ((ShallowMerge) ( 17))
+ ((SchedulerUnknown) ( 98)) // Used by node to report aborted jobs for which spec request has failed.
+
+ // Master jobs
+ ((ReplicateChunk) (100))
+ ((RemoveChunk) (101))
+ ((RepairChunk) (102))
+ ((SealChunk) (103))
+ ((MergeChunks) (104))
+ ((AutotomizeChunk) (105))
+ ((ReincarnateChunk) (106))
+);
+
+constexpr auto FirstSchedulerJobType = EJobType::Map;
+constexpr auto LastSchedulerJobType = EJobType::SchedulerUnknown;
+
+constexpr auto FirstMasterJobType = EJobType::ReplicateChunk;
+constexpr auto LastMasterJobType = EJobType::ReincarnateChunk;
+
+// NB: Please keep the range of values small as this type
+// is used as a key of TEnumIndexedVector.
+DEFINE_ENUM(EJobState,
+ ((Waiting) (0))
+ ((Running) (1))
+ // COMPAT(pogorelov): Remove it after 23.1
+ ((Aborting) (2))
+ // |Completed| is used as sentinel in NJobTrackerClient::HasJobFinished.
+ ((Completed) (3))
+ ((Failed) (4))
+ ((Aborted) (5))
+ // This sentinel is only used in TJob::GetStatisticsSuffix.
+ ((Lost) (7))
+ // Initial state of newly created job.
+ ((None) (8))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJobTrackerClient
diff --git a/yt/yt/client/journal_client/config.cpp b/yt/yt/client/journal_client/config.cpp
new file mode 100644
index 0000000000..dde03da128
--- /dev/null
+++ b/yt/yt/client/journal_client/config.cpp
@@ -0,0 +1,17 @@
+#include "config.h"
+namespace NYT::NJournalClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("replica_data_size_read_threshold", &TThis::ReplicaDataSizeReadThreshold)
+ .Default(1_MB);
+
+ registrar.Parameter("slow_path_delay", &TThis::SlowPathDelay)
+ .Default(TDuration::Seconds(5));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJournalClient
diff --git a/yt/yt/client/journal_client/config.h b/yt/yt/client/journal_client/config.h
new file mode 100644
index 0000000000..e44278f99b
--- /dev/null
+++ b/yt/yt/client/journal_client/config.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/ytree/yson_serializable.h>
+
+#include <yt/yt/client/chunk_client/config.h>
+
+namespace NYT::NJournalClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkReaderConfig
+ : public virtual NChunkClient::TReplicationReaderConfig
+{
+public:
+ //! Reader will skip replicas with less than this amount of relevant data
+ //! data available.
+ i64 ReplicaDataSizeReadThreshold;
+
+ //! When fetching replica metas, journal reader will wait for this period of time
+ //! before starting slow path in hope to run fast path.
+ TDuration SlowPathDelay;
+
+ REGISTER_YSON_STRUCT(TChunkReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJournalClient
diff --git a/yt/yt/client/journal_client/public.cpp b/yt/yt/client/journal_client/public.cpp
new file mode 100644
index 0000000000..3c3fdaa136
--- /dev/null
+++ b/yt/yt/client/journal_client/public.cpp
@@ -0,0 +1,10 @@
+#include "public.h"
+
+namespace NYT::NJournalClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJournalClient
+
diff --git a/yt/yt/client/journal_client/public.h b/yt/yt/client/journal_client/public.h
new file mode 100644
index 0000000000..12dbfaf6a8
--- /dev/null
+++ b/yt/yt/client/journal_client/public.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NJournalClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TChunkReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr i64 DefaultReplicaLagLimit = 32768;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NJournalClient
diff --git a/yt/yt/client/misc/config.cpp b/yt/yt/client/misc/config.cpp
new file mode 100644
index 0000000000..9adabdb19d
--- /dev/null
+++ b/yt/yt/client/misc/config.cpp
@@ -0,0 +1,15 @@
+#include "config.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TWorkloadConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("workload_descriptor", &TThis::WorkloadDescriptor)
+ .Default(TWorkloadDescriptor(EWorkloadCategory::UserBatch));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/misc/config.h b/yt/yt/client/misc/config.h
new file mode 100644
index 0000000000..c8c82c1e06
--- /dev/null
+++ b/yt/yt/client/misc/config.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "workload.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWorkloadConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ TWorkloadDescriptor WorkloadDescriptor;
+
+ REGISTER_YSON_STRUCT(TWorkloadConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TWorkloadConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/misc/io_tags.cpp b/yt/yt/client/misc/io_tags.cpp
new file mode 100644
index 0000000000..6e7af3312e
--- /dev/null
+++ b/yt/yt/client/misc/io_tags.cpp
@@ -0,0 +1,32 @@
+#include "io_tags.h"
+
+#include <yt/yt/core/ytree/helpers.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString FormatIOTag(ERawIOTag tag)
+{
+ return FormatEnum(tag);
+}
+
+TString FormatIOTag(EAggregateIOTag tag)
+{
+ return FormatEnum(tag) + "@";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void AddTagToBaggage(const NYTree::IAttributeDictionaryPtr& baggage, T tag, const TStringBuf& value)
+{
+ baggage->Set(FormatIOTag(tag), value);
+}
+
+template void AddTagToBaggage<ERawIOTag>(const NYTree::IAttributeDictionaryPtr& baggage, ERawIOTag tag, const TStringBuf& value);
+template void AddTagToBaggage<EAggregateIOTag>(const NYTree::IAttributeDictionaryPtr& baggage, EAggregateIOTag tag, const TStringBuf& value);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/misc/io_tags.h b/yt/yt/client/misc/io_tags.h
new file mode 100644
index 0000000000..17aee8a6dd
--- /dev/null
+++ b/yt/yt/client/misc/io_tags.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/ytree/attributes.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ERawIOTag,
+ ((ReadSessionId) (0))
+ ((WriteSessionId) (1))
+ ((JobId) (2))
+ ((ObjectPath) (3))
+ ((ObjectId) (4))
+ ((OperationId) (5))
+ ((ChunkId) (6))
+ ((LocationId) (7))
+ ((SlotIndex) (8))
+);
+
+DEFINE_ENUM(EAggregateIOTag,
+ ((Direction) (0))
+ ((Medium) (1))
+ ((DiskFamily) (2))
+ ((User) (3))
+ ((LocationType) (4))
+ ((DataNodeMethod) (5))
+ ((JobType) (6))
+ ((Account) (7))
+ ((ApiMethod ) (8))
+ ((ProxyKind ) (9))
+ ((Pool) (10))
+ ((PoolTree) (11))
+ ((OperationType) (12))
+ ((TaskName) (13))
+ ((JobIoKind) (14))
+ ((PoolPath) (15))
+ ((ErasureCodec) (16))
+ ((CompressionCodec) (17))
+);
+
+TString FormatIOTag(ERawIOTag tag);
+TString FormatIOTag(EAggregateIOTag tag);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void AddTagToBaggage(const NYTree::IAttributeDictionaryPtr& baggage, T tag, const TStringBuf& value);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/misc/method_helpers.cpp b/yt/yt/client/misc/method_helpers.cpp
new file mode 100644
index 0000000000..2a835f3953
--- /dev/null
+++ b/yt/yt/client/misc/method_helpers.cpp
@@ -0,0 +1,12 @@
+#include "method_helpers.h"
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT {
+
+[[noreturn]] void ThrowUnimplementedClientMethodError(TStringBuf methodName, TStringBuf reason)
+{
+ throw TErrorException() <<= TError("%v method: %v", reason, methodName);
+}
+
+} // namespace NYT
diff --git a/yt/yt/client/misc/method_helpers.h b/yt/yt/client/misc/method_helpers.h
new file mode 100644
index 0000000000..7c47a40c49
--- /dev/null
+++ b/yt/yt/client/misc/method_helpers.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <util/generic/string.h>
+#include <util/generic/yexception.h>
+#include <util/system/compiler.h>
+
+namespace NYT {
+
+[[noreturn]] void ThrowUnimplementedClientMethodError(TStringBuf methodName, TStringBuf reason);
+
+#define Y_COMMA() ,
+
+#define Y_FUNCTION_ARG_UNUSED(x) x
+#define Y_FUNCTION_ARG(i, x) x Y_CAT(a, i)
+#define Y_PASS_ARG(i, x) Y_CAT(a, i)
+
+#define Y_FUNCTION_ARG_UNUSED_COMMA(x) x Y_COMMA()
+#define Y_FUNCTION_ARG_COMMA(i, x) x Y_CAT(a, i) Y_COMMA()
+#define Y_PASS_ARG_COMMA(i, x) Y_CAT(a, i) Y_COMMA()
+
+#define Y_VA_ARGS(...) __VA_ARGS__
+
+// Map input arguments types.
+// Example: Y_METHOD_UNUSED_ARGS_DECLARATION((int, TString)) ---> int, TString.
+#define Y_METHOD_UNUSED_ARGS_DECLARATION(Args) Y_MAP_ARGS_WITH_LAST(Y_FUNCTION_ARG_UNUSED_COMMA, Y_FUNCTION_ARG_UNUSED, Y_PASS_VA_ARGS(Y_VA_ARGS Args))
+
+// Pass capture list as is.
+// Example: Y_PASS_CAPTURE_LIST((&a1, a2)) ---> &a1, a2.
+#define Y_PASS_CAPTURE_LIST(Args) Y_METHOD_UNUSED_ARGS_DECLARATION(Args)
+
+// Map input arguments types with parameter names introduction.
+// Example: Y_METHOD_USED_ARGS_DECLARATION((int, TString)) ---> int a2, TString a1.
+#define Y_METHOD_USED_ARGS_DECLARATION(Args) Y_MAP_ARGS_WITH_LAST_N(Y_FUNCTION_ARG_COMMA, Y_FUNCTION_ARG, Y_PASS_VA_ARGS(Y_VA_ARGS Args))
+
+// Map input arguments types into corresponding parameter names.
+// Example: Y_PASS_METHOD_USED_ARGS((int, TString)) ---> a2, a1.
+#define Y_PASS_METHOD_USED_ARGS(Args) Y_MAP_ARGS_WITH_LAST_N(Y_PASS_ARG_COMMA, Y_PASS_ARG, Y_PASS_VA_ARGS(Y_VA_ARGS Args))
+
+#define UNIMPLEMENTED_METHOD(ReturnType, MethodName, Args) NO_METHOD_IMPL(ReturnType, MethodName, "Not implemented", Args)
+#define UNIMPLEMENTED_CONST_METHOD(ReturnType, MethodName, Args) NO_CONST_METHOD_IMPL(ReturnType, MethodName, "Not implemented", Args)
+
+#define UNSUPPORTED_METHOD(ReturnType, MethodName, Args) NO_METHOD_IMPL(ReturnType, MethodName, "Not supported", Args)
+#define UNSUPPORTED_CONST_METHOD(ReturnType, MethodName, Args) NO_CONST_METHOD_IMPL(ReturnType, MethodName, "Not supported", Args)
+
+#define NO_METHOD_IMPL(ReturnType, MethodName, Reason, Args) \
+ ReturnType MethodName(Y_METHOD_UNUSED_ARGS_DECLARATION(Args)) override \
+ { \
+ ThrowUnimplementedClientMethodError(Y_STRINGIZE(MethodName), Y_STRINGIZE(Reason)); \
+ Y_UNREACHABLE(); \
+ } Y_SEMICOLON_GUARD
+
+#define NO_CONST_METHOD_IMPL(ReturnType, MethodName, Reason, Args) \
+ ReturnType MethodName(Y_METHOD_UNUSED_ARGS_DECLARATION(Args)) const override \
+ { \
+ ThrowUnimplementedClientMethodError(Y_STRINGIZE(MethodName), Y_STRINGIZE(Reason)); \
+ Y_UNREACHABLE(); \
+ } Y_SEMICOLON_GUARD
+
+} // namespace NYT
diff --git a/yt/yt/client/misc/public.h b/yt/yt/client/misc/public.h
new file mode 100644
index 0000000000..a5f8132c17
--- /dev/null
+++ b/yt/yt/client/misc/public.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+class TWorkloadDescriptor;
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! The type of the workload.
+//! Order is given by |GetBasicPriority| function and not by concrete enum values.
+/*!
+ * This is the most fine-grained categorization available.
+ * Most subsystems will map EWorkloadCategory to their own coarser categories.
+ *
+ * NB: This enum is serializable, so please keep values intact or advance
+ * protocol version where appropriate.
+ */
+DEFINE_ENUM(EWorkloadCategory,
+ ((Idle) (0))
+ ((SystemArtifactCacheDownload) (9))
+ ((SystemRepair) (2))
+ ((SystemReincarnation) (17))
+ ((SystemReplication) (1))
+ ((SystemMerge) (16))
+ ((SystemTabletCompaction) (6))
+ ((SystemTabletLogging) (5))
+ ((SystemTabletPartitioning) (7))
+ ((SystemTabletPreload) (8))
+ ((SystemTabletRecovery) (10))
+ ((SystemTabletReplication) (14))
+ ((SystemTabletSnapshot) (13))
+ ((SystemTabletStoreFlush) (12))
+ ((UserBatch) (3))
+ ((UserInteractive) (11))
+ ((UserRealtime) (4))
+ ((UserDynamicStoreRead) (15))
+);
+
+struct TWorkloadDescriptor;
+
+DECLARE_REFCOUNTED_CLASS(TWorkloadConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/misc/workload.cpp b/yt/yt/client/misc/workload.cpp
new file mode 100644
index 0000000000..098e3df182
--- /dev/null
+++ b/yt/yt/client/misc/workload.cpp
@@ -0,0 +1,188 @@
+#include "workload.h"
+
+#include <yt/yt_proto/yt/client/misc/proto/workload.pb.h>
+
+#include <yt/yt/core/concurrency/action_queue.h>
+#include <yt/yt/core/concurrency/fair_share_thread_pool.h>
+
+#include <yt/yt/core/rpc/service.h>
+#include <yt/yt/core/rpc/dispatcher.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT {
+
+using namespace NConcurrency;
+using namespace NYTree;
+using namespace NYson;
+using namespace NRpc;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const i64 CategoryPriorityFactor = (i64) 1 << 56;
+static const i64 BandPriorityFactor = (i64) 1 << 48;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TWorkloadDescriptor::TWorkloadDescriptor(
+ EWorkloadCategory category,
+ int band,
+ TInstant instant,
+ std::vector<TString> annotations,
+ std::optional<NConcurrency::TFairShareThreadPoolTag> compressionFairShareTag)
+ : Category(category)
+ , Band(band)
+ , Instant(instant)
+ , Annotations(std::move(annotations))
+ , CompressionFairShareTag(std::move(compressionFairShareTag))
+{ }
+
+TWorkloadDescriptor TWorkloadDescriptor::SetCurrentInstant() const
+{
+ return TWorkloadDescriptor(Category, Band, TInstant::Now(), Annotations, CompressionFairShareTag);
+}
+
+i64 TWorkloadDescriptor::GetPriority() const
+{
+ auto priority = GetBasicPriority(Category) + BandPriorityFactor * Band;
+ if (Category == EWorkloadCategory::UserBatch) {
+ priority -= Instant.MilliSeconds();
+ }
+ return priority;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+i64 GetBasicPriority(EWorkloadCategory category)
+{
+ switch (category) {
+ case EWorkloadCategory::Idle:
+ return 0;
+
+ case EWorkloadCategory::SystemReplication:
+ case EWorkloadCategory::SystemMerge:
+ case EWorkloadCategory::SystemTabletCompaction:
+ case EWorkloadCategory::SystemTabletPartitioning:
+ case EWorkloadCategory::SystemTabletPreload:
+ case EWorkloadCategory::SystemTabletReplication:
+ case EWorkloadCategory::SystemTabletStoreFlush:
+ case EWorkloadCategory::SystemArtifactCacheDownload:
+ case EWorkloadCategory::UserBatch:
+ return CategoryPriorityFactor * 1;
+
+ case EWorkloadCategory::SystemRepair:
+ case EWorkloadCategory::SystemTabletSnapshot:
+ return CategoryPriorityFactor * 2;
+
+ case EWorkloadCategory::UserInteractive:
+ case EWorkloadCategory::SystemTabletRecovery:
+ return CategoryPriorityFactor * 3;
+
+ case EWorkloadCategory::UserRealtime:
+ case EWorkloadCategory::SystemTabletLogging:
+ return CategoryPriorityFactor * 4;
+
+ // Graceful fallback for possible future extensions of categories.
+ default:
+ return 0;
+ }
+}
+
+IInvokerPtr GetCompressionInvoker(const TWorkloadDescriptor& workloadDescriptor)
+{
+ if (workloadDescriptor.CompressionFairShareTag) {
+ return NRpc::TDispatcher::Get()->GetFairShareCompressionThreadPool()
+ ->GetInvoker(*workloadDescriptor.CompressionFairShareTag);
+ } else {
+ return CreateFixedPriorityInvoker(
+ NRpc::TDispatcher::Get()->GetPrioritizedCompressionPoolInvoker(),
+ workloadDescriptor.GetPriority());
+ }
+}
+
+struct TSerializableWorkloadDescriptor
+ : public TWorkloadDescriptor
+ , public TYsonStructLite
+{
+ REGISTER_YSON_STRUCT_LITE(TSerializableWorkloadDescriptor);
+
+ static void Register(TRegistrar registrar)
+ {
+ registrar.BaseClassParameter("category", &TWorkloadDescriptor::Category);
+ registrar.BaseClassParameter("band", &TWorkloadDescriptor::Band)
+ .Default(0);
+ registrar.BaseClassParameter("annotations", &TWorkloadDescriptor::Annotations)
+ .Default();
+ }
+};
+
+void Serialize(const TWorkloadDescriptor& descriptor, IYsonConsumer* consumer)
+{
+ TSerializableWorkloadDescriptor wrapper = TSerializableWorkloadDescriptor::Create();
+ static_cast<TWorkloadDescriptor&>(wrapper) = descriptor;
+ Serialize(static_cast<const TYsonStructLite&>(wrapper), consumer);
+}
+
+void Deserialize(TWorkloadDescriptor& descriptor, INodePtr node)
+{
+ TSerializableWorkloadDescriptor wrapper = TSerializableWorkloadDescriptor::Create();
+ Deserialize(static_cast<TYsonStructLite&>(wrapper), node);
+ descriptor = static_cast<TWorkloadDescriptor&>(wrapper);
+}
+
+void Deserialize(TWorkloadDescriptor& descriptor, NYson::TYsonPullParserCursor* cursor)
+{
+ TSerializableWorkloadDescriptor wrapper = TSerializableWorkloadDescriptor::Create();
+ Deserialize(static_cast<TYsonStructLite&>(wrapper), cursor);
+ descriptor = static_cast<TWorkloadDescriptor&>(wrapper);
+}
+
+void ToProto(NYT::NProto::TWorkloadDescriptor* protoDescriptor, const TWorkloadDescriptor& descriptor)
+{
+ protoDescriptor->set_category(static_cast<int>(descriptor.Category));
+ protoDescriptor->set_band(descriptor.Band);
+ protoDescriptor->set_instant(ToProto<i64>(descriptor.Instant));
+ ToProto(protoDescriptor->mutable_annotations(), descriptor.Annotations);
+}
+
+void FromProto(TWorkloadDescriptor* descriptor, const NYT::NProto::TWorkloadDescriptor& protoDescriptor)
+{
+ descriptor->Category = EWorkloadCategory(protoDescriptor.category());
+ descriptor->Band = protoDescriptor.band();
+ descriptor->Instant = FromProto<TInstant>(protoDescriptor.instant());
+ FromProto(&descriptor->Annotations, protoDescriptor.annotations());
+}
+
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TWorkloadDescriptor& descriptor,
+ TStringBuf /*format*/)
+{
+ builder->AppendFormat("%v:%v",
+ descriptor.Category,
+ descriptor.Band);
+ if (descriptor.Instant != TInstant::Zero()) {
+ builder->AppendFormat(":%v",
+ descriptor.Instant);
+ }
+ if (!descriptor.Annotations.empty()) {
+ builder->AppendString(":{");
+ for (size_t index = 0; index < descriptor.Annotations.size(); ++index) {
+ builder->AppendString(descriptor.Annotations[index]);
+ if (index != descriptor.Annotations.size() - 1) {
+ builder->AppendString(", ");
+ }
+ }
+ builder->AppendChar('}');
+ }
+}
+
+TString ToString(const TWorkloadDescriptor& descriptor)
+{
+ return ToStringViaBuilder(descriptor);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
diff --git a/yt/yt/client/misc/workload.h b/yt/yt/client/misc/workload.h
new file mode 100644
index 0000000000..4fa95201e9
--- /dev/null
+++ b/yt/yt/client/misc/workload.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/actions/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <yt/yt/core/concurrency/public.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWorkloadDescriptor
+{
+ TWorkloadDescriptor(
+ EWorkloadCategory category = EWorkloadCategory::Idle,
+ int band = 0,
+ TInstant instant = {},
+ std::vector<TString> annotations = {},
+ std::optional<NConcurrency::TFairShareThreadPoolTag> compressionFairShareTag = {});
+
+ //! The type of the workload defining its basic priority.
+ EWorkloadCategory Category;
+
+ //! The relative importance of the workload (among others within the category).
+ //! Zero is the default value.
+ //! Larger is better.
+ int Band;
+
+ //! The time instant when this workload has been initiated.
+ //! #EWorkloadCategory::UserBatch workloads rely on this value for FIFO ordering.
+ TInstant Instant;
+
+ //! Arbitrary client-supplied strings to be logged at server-side.
+ std::vector<TString> Annotations;
+
+ //! If present, invoker from fair share thread pool will be used for decompression.
+ std::optional<NConcurrency::TFairShareThreadPoolTag> CompressionFairShareTag;
+
+ //! Updates the instant field with the current time.
+ TWorkloadDescriptor SetCurrentInstant() const;
+
+ //! Computes the aggregated priority.
+ //! Larger is better.
+ i64 GetPriority() const;
+};
+
+i64 GetBasicPriority(EWorkloadCategory category);
+
+IInvokerPtr GetCompressionInvoker(const TWorkloadDescriptor& workloadDescriptor);
+
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TWorkloadDescriptor& descriptor,
+ TStringBuf format);
+TString ToString(const TWorkloadDescriptor& descriptor);
+
+void ToProto(NYT::NProto::TWorkloadDescriptor* protoDescriptor, const TWorkloadDescriptor& descriptor);
+void FromProto(TWorkloadDescriptor* descriptor, const NYT::NProto::TWorkloadDescriptor& protoDescriptor);
+
+void Serialize(const TWorkloadDescriptor& descriptor, NYson::IYsonConsumer* consumer);
+void Deserialize(TWorkloadDescriptor& descriptor, NYTree::INodePtr node);
+void Deserialize(TWorkloadDescriptor& descriptor, NYson::TYsonPullParserCursor* cursor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/node_tracker_client/helpers.cpp b/yt/yt/client/node_tracker_client/helpers.cpp
new file mode 100644
index 0000000000..6f9d7b5365
--- /dev/null
+++ b/yt/yt/client/node_tracker_client/helpers.cpp
@@ -0,0 +1,58 @@
+#include "helpers.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt_proto/yt/client/node_tracker_client/proto/node.pb.h>
+
+namespace NYT::NNodeTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NYPath::TYPath GetClusterNodesPath()
+{
+ return "//sys/cluster_nodes";
+}
+
+NYPath::TYPath GetExecNodesPath()
+{
+ return "//sys/exec_nodes";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+void FormatValue(TStringBuilderBase* builder, const TDiskLocationResources& locationResources, TStringBuf /*spec*/)
+{
+ builder->AppendFormat(
+ "{usage: %v, limit: %v, medium_index: %v}",
+ locationResources.usage(),
+ locationResources.limit(),
+ locationResources.medium_index());
+}
+
+TString ToString(const TDiskLocationResources& locationResources)
+{
+ return ToStringViaBuilder(locationResources);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TDiskResources& diskResources, TStringBuf /*spec*/)
+{
+ builder->AppendFormat(
+ "%v",
+ MakeFormattableView(diskResources.disk_location_resources(), [] (TStringBuilderBase* builder, const TDiskLocationResources& diskLocationResources) {
+ FormatValue(builder, diskLocationResources, "%v");
+ }));
+}
+
+TString ToString(const TDiskResources& diskResources)
+{
+ return ToStringViaBuilder(diskResources);
+}
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
+
diff --git a/yt/yt/client/node_tracker_client/helpers.h b/yt/yt/client/node_tracker_client/helpers.h
new file mode 100644
index 0000000000..43e17db4d6
--- /dev/null
+++ b/yt/yt/client/node_tracker_client/helpers.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/ypath/public.h>
+
+namespace NYT::NNodeTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NYPath::TYPath GetClusterNodesPath();
+
+NYPath::TYPath GetExecNodesPath();
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+void FormatValue(TStringBuilderBase* builder, const TDiskLocationResources& locationResources, TStringBuf spec);
+TString ToString(const TDiskLocationResources& locationResources);
+
+void FormatValue(TStringBuilderBase* builder, const TDiskResources& diskResources, TStringBuf spec);
+TString ToString(const TDiskResources& diskResources);
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
diff --git a/yt/yt/client/node_tracker_client/node_directory.cpp b/yt/yt/client/node_tracker_client/node_directory.cpp
new file mode 100644
index 0000000000..1374d57890
--- /dev/null
+++ b/yt/yt/client/node_tracker_client/node_directory.cpp
@@ -0,0 +1,753 @@
+#include "node_directory.h"
+#include "private.h"
+
+#include <yt/yt/client/node_tracker_client/node_directory.h>
+
+#include <yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.pb.h>
+
+#include <yt/yt/core/concurrency/thread_affinity.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/core/net/address.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+#include <library/cpp/yt/misc/hash.h>
+
+#include <util/digest/numeric.h>
+
+namespace NYT::NNodeTrackerClient {
+
+using namespace NChunkClient;
+using namespace NYson;
+using namespace NYTree;
+using namespace NNet;
+using namespace NRpc;
+using namespace NConcurrency;
+
+using NYT::FromProto;
+using NYT::ToProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto& Logger = NodeTrackerClientLogger;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString& NullNodeAddress()
+{
+ static const TString Result("<null>");
+ return Result;
+}
+
+const TNodeDescriptor& NullNodeDescriptor()
+{
+ static const TNodeDescriptor Result(NullNodeAddress());
+ return Result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+constexpr int TypicalTagCount = 16;
+
+// Cf. YT-10645
+TCompactVector<TStringBuf, TypicalTagCount> GetSortedTags(const std::vector<TString>& tags)
+{
+ TCompactVector<TStringBuf, TypicalTagCount> result;
+ result.reserve(tags.size());
+ for (const auto& tag : tags) {
+ result.push_back(tag);
+ }
+ std::sort(result.begin(), result.end());
+ return result;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNodeDescriptor::TNodeDescriptor()
+ : DefaultAddress_(NullNodeAddress())
+{ }
+
+TNodeDescriptor::TNodeDescriptor(const TString& defaultAddress)
+ : Addresses_{std::make_pair(DefaultNetworkName, defaultAddress)}
+ , DefaultAddress_(defaultAddress)
+{ }
+
+TNodeDescriptor::TNodeDescriptor(const std::optional<TString>& defaultAddress)
+{
+ if (defaultAddress) {
+ *this = TNodeDescriptor(*defaultAddress);
+ }
+}
+
+TNodeDescriptor::TNodeDescriptor(
+ TAddressMap addresses,
+ std::optional<TString> host,
+ std::optional<TString> rack,
+ std::optional<TString> dc,
+ const std::vector<TString>& tags,
+ std::optional<TInstant> lastSeenTime)
+ : Addresses_(std::move(addresses))
+ , DefaultAddress_(NNodeTrackerClient::GetDefaultAddress(Addresses_))
+ , Host_(std::move(host))
+ , Rack_(std::move(rack))
+ , DataCenter_(std::move(dc))
+ , Tags_(tags)
+ , LastSeenTime_(lastSeenTime ? InstantToCpuInstant(*lastSeenTime) : 0)
+{ }
+
+bool TNodeDescriptor::IsNull() const
+{
+ return Addresses_.empty();
+}
+
+const TAddressMap& TNodeDescriptor::Addresses() const
+{
+ return Addresses_;
+}
+
+const TString& TNodeDescriptor::GetDefaultAddress() const
+{
+ return DefaultAddress_;
+}
+
+const TString& TNodeDescriptor::GetAddressOrThrow(const TNetworkPreferenceList& networks) const
+{
+ return NNodeTrackerClient::GetAddressOrThrow(Addresses(), networks);
+}
+
+std::optional<TString> TNodeDescriptor::FindAddress(const TNetworkPreferenceList& networks) const
+{
+ return NNodeTrackerClient::FindAddress(Addresses(), networks);
+}
+
+const std::optional<TString>& TNodeDescriptor::GetHost() const
+{
+ return Host_;
+}
+
+const std::optional<TString>& TNodeDescriptor::GetRack() const
+{
+ return Rack_;
+}
+
+const std::optional<TString>& TNodeDescriptor::GetDataCenter() const
+{
+ return DataCenter_;
+}
+
+const std::vector<TString>& TNodeDescriptor::GetTags() const
+{
+ return Tags_;
+}
+
+std::optional<TInstant> TNodeDescriptor::GetLastSeenTime() const
+{
+ auto cpuTime = LastSeenTime_.Load();
+ if (cpuTime != 0) {
+ return CpuInstantToInstant(cpuTime);
+ } else {
+ return {};
+ }
+}
+
+void TNodeDescriptor::UpdateLastSeenTime(TInstant at) const
+{
+ auto cpuTime = InstantToCpuInstant(at);
+ if (auto currentTime = LastSeenTime_.Load(); cpuTime > currentTime) {
+ LastSeenTime_.Store(cpuTime);
+ }
+}
+
+void TNodeDescriptor::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+ Persist(context, Addresses_);
+ Persist(context, Host_);
+ Persist(context, Rack_);
+ Persist(context, DataCenter_);
+
+ if (context.IsLoad()) {
+ DefaultAddress_ = NNodeTrackerClient::GetDefaultAddress(Addresses_);
+ }
+}
+
+void FormatValue(TStringBuilderBase* builder, const TNodeDescriptor& descriptor, TStringBuf /*spec*/)
+{
+ if (descriptor.IsNull()) {
+ builder->AppendString(NullNodeAddress());
+ return;
+ }
+
+ builder->AppendString(descriptor.GetDefaultAddress());
+ if (const auto& host = descriptor.GetHost()) {
+ builder->AppendChar('$');
+ builder->AppendString(*host);
+ }
+ if (const auto& rack = descriptor.GetRack()) {
+ builder->AppendChar('@');
+ builder->AppendString(*rack);
+ }
+ if (const auto& dataCenter = descriptor.GetDataCenter()) {
+ builder->AppendChar('#');
+ builder->AppendString(*dataCenter);
+ }
+}
+
+TString ToString(const TNodeDescriptor& descriptor)
+{
+ return ToStringViaBuilder(descriptor);
+}
+
+std::optional<TString> FindDefaultAddress(const TAddressMap& addresses)
+{
+ if (addresses.empty()) {
+ return NullNodeAddress();
+ }
+
+ if (auto it = addresses.find(DefaultNetworkName); it != addresses.end()) {
+ return it->second;
+ }
+ return std::nullopt;
+}
+
+const TString& GetDefaultAddress(const TAddressMap& addresses)
+{
+ if (addresses.empty()) {
+ return NullNodeAddress();
+ }
+ return GetOrCrash(addresses, DefaultNetworkName);
+}
+
+const TString& GetDefaultAddress(const NProto::TAddressMap& addresses)
+{
+ if (addresses.entries_size() == 0) {
+ return NullNodeAddress();
+ }
+ for (const auto& entry : addresses.entries()) {
+ if (entry.network() == DefaultNetworkName) {
+ return entry.address();
+ }
+ }
+ YT_ABORT();
+}
+
+EAddressLocality ComputeAddressLocality(const TNodeDescriptor& first, const TNodeDescriptor& second)
+{
+ if (first.IsNull() || second.IsNull()) {
+ return EAddressLocality::None;
+ };
+
+ try {
+ // COMPAT(gritukan)
+ if (GetServiceHostName(first.GetDefaultAddress()) == GetServiceHostName(second.GetDefaultAddress())) {
+ return EAddressLocality::SameHost;
+ }
+
+ if (first.GetHost() && second.GetHost() && *first.GetHost() == *second.GetHost()) {
+ return EAddressLocality::SameHost;
+ }
+
+ if (first.GetRack() && second.GetRack() && *first.GetRack() == *second.GetRack()) {
+ return EAddressLocality::SameRack;
+ }
+
+ if (first.GetDataCenter() && second.GetDataCenter() && *first.GetDataCenter() == *second.GetDataCenter()) {
+ return EAddressLocality::SameDataCenter;
+ }
+ } catch (const std::exception&) {
+ // If one of the descriptors is malformed, treat it as None locality and ignore errors.
+ }
+
+ return EAddressLocality::None;
+}
+
+namespace NProto {
+
+void ToProto(NNodeTrackerClient::NProto::TAddressMap* protoAddresses, const NNodeTrackerClient::TAddressMap& addresses)
+{
+ for (const auto& [networkName, networkAddress] : addresses) {
+ auto* entry = protoAddresses->add_entries();
+ entry->set_network(networkName);
+ entry->set_address(networkAddress);
+ }
+}
+
+void FromProto(NNodeTrackerClient::TAddressMap* addresses, const NNodeTrackerClient::NProto::TAddressMap& protoAddresses)
+{
+ addresses->clear();
+ addresses->reserve(protoAddresses.entries_size());
+ for (const auto& entry : protoAddresses.entries()) {
+ YT_VERIFY(addresses->emplace(entry.network(), entry.address()).second);
+ }
+}
+
+void ToProto(NNodeTrackerClient::NProto::TNodeAddressMap* proto, const NNodeTrackerClient::TNodeAddressMap& nodeAddresses)
+{
+ for (const auto& [addressType, addresses] : nodeAddresses) {
+ auto* entry = proto->add_entries();
+ entry->set_address_type(static_cast<int>(addressType));
+ ToProto(entry->mutable_addresses(), addresses);
+ }
+}
+
+void FromProto(NNodeTrackerClient::TNodeAddressMap* nodeAddresses, const NNodeTrackerClient::NProto::TNodeAddressMap& proto)
+{
+ nodeAddresses->clear();
+ nodeAddresses->reserve(proto.entries_size());
+ for (const auto& entry : proto.entries()) {
+ NNodeTrackerClient::TAddressMap addresses;
+ FromProto(&addresses, entry.addresses());
+
+ YT_VERIFY(nodeAddresses->emplace(static_cast<EAddressType>(entry.address_type()), std::move(addresses)).second);
+ }
+}
+
+void ToProto(NNodeTrackerClient::NProto::TNodeDescriptor* protoDescriptor, const NNodeTrackerClient::TNodeDescriptor& descriptor)
+{
+ using NYT::ToProto;
+
+ ToProto(protoDescriptor->mutable_addresses(), descriptor.Addresses());
+
+ if (auto host = descriptor.GetHost()) {
+ protoDescriptor->set_host(*host);
+ } else {
+ protoDescriptor->clear_host();
+ }
+
+ if (auto rack = descriptor.GetRack()) {
+ protoDescriptor->set_rack(*rack);
+ } else {
+ protoDescriptor->clear_rack();
+ }
+
+ if (auto dataCenter = descriptor.GetDataCenter()) {
+ protoDescriptor->set_data_center(*dataCenter);
+ } else {
+ protoDescriptor->clear_data_center();
+ }
+
+ ToProto(protoDescriptor->mutable_tags(), descriptor.GetTags());
+
+ if (auto lastHeartbeatTime = descriptor.GetLastSeenTime()) {
+ protoDescriptor->set_last_seen_time(ToProto<i64>(*lastHeartbeatTime));
+ } else {
+ protoDescriptor->clear_last_seen_time();
+ }
+}
+
+void FromProto(NNodeTrackerClient::TNodeDescriptor* descriptor, const NNodeTrackerClient::NProto::TNodeDescriptor& protoDescriptor)
+{
+ using NYT::FromProto;
+
+ *descriptor = NNodeTrackerClient::TNodeDescriptor(
+ FromProto<NNodeTrackerClient::TAddressMap>(protoDescriptor.addresses()),
+ protoDescriptor.has_host() ? std::make_optional(protoDescriptor.host()) : std::nullopt,
+ protoDescriptor.has_rack() ? std::make_optional(protoDescriptor.rack()) : std::nullopt,
+ protoDescriptor.has_data_center() ? std::make_optional(protoDescriptor.data_center()) : std::nullopt,
+ FromProto<std::vector<TString>>(protoDescriptor.tags()),
+ protoDescriptor.has_last_seen_time() ? std::make_optional(FromProto<TInstant>(protoDescriptor.last_seen_time())) : std::nullopt);
+}
+
+} // namespace NProto
+
+bool operator == (const TNodeDescriptor& lhs, const TNodeDescriptor& rhs)
+{
+ return
+ lhs.GetDefaultAddress() == rhs.GetDefaultAddress() && // shortcut
+ lhs.Addresses() == rhs.Addresses() &&
+ lhs.GetHost() == rhs.GetHost() &&
+ lhs.GetRack() == rhs.GetRack() &&
+ lhs.GetDataCenter() == rhs.GetDataCenter() &&
+ GetSortedTags(lhs.GetTags()) == GetSortedTags(rhs.GetTags());
+}
+
+bool operator != (const TNodeDescriptor& lhs, const TNodeDescriptor& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool operator == (const TNodeDescriptor& lhs, const NProto::TNodeDescriptor& rhs)
+{
+ if (std::ssize(lhs.Addresses()) != rhs.addresses().entries_size()) {
+ return false;
+ }
+
+ for (const auto& protoEntry : rhs.addresses().entries()) {
+ const auto& network = protoEntry.network();
+ const auto& address = protoEntry.address();
+ auto it = lhs.Addresses().find(network);
+ if (it == lhs.Addresses().end()) {
+ return false;
+ }
+ if (it->second != address) {
+ return false;
+ }
+ }
+
+ const auto& lhsMaybeHost = lhs.GetHost();
+ auto lhsHost = lhsMaybeHost ? TStringBuf(*lhsMaybeHost) : TStringBuf();
+ if (lhsHost != rhs.host()) {
+ return false;
+ }
+
+ const auto& lhsMaybeRack = lhs.GetRack();
+ auto lhsRack = lhsMaybeRack ? TStringBuf(*lhsMaybeRack) : TStringBuf();
+ if (lhsRack != rhs.rack()) {
+ return false;
+ }
+
+ const auto& lhsMaybeDataCenter = lhs.GetDataCenter();
+ auto lhsDataCenter = lhsMaybeDataCenter ? TStringBuf(*lhsMaybeDataCenter) : TStringBuf();
+ if (lhsDataCenter != rhs.data_center()) {
+ return false;
+ }
+
+ const auto& lhsTags = lhs.GetTags();
+ auto rhsTags = FromProto<std::vector<TString>>(rhs.tags());
+ if (GetSortedTags(lhsTags) != GetSortedTags(rhsTags)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool operator != (const TNodeDescriptor& lhs, const NProto::TNodeDescriptor& rhs)
+{
+ return !(lhs == rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TNodeDirectory::MergeFrom(const NProto::TNodeDirectory& source)
+{
+ std::vector<const NProto::TNodeDirectory_TItem*> items;
+ items.reserve(source.items_size());
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ for (const auto& item : source.items()) {
+ auto nodeId = TNodeId(item.node_id());
+ if (CheckNodeDescriptor(nodeId, item.node_descriptor())) {
+ items.push_back(&item);
+ }
+ }
+ }
+
+ {
+ auto guard = WriterGuard(SpinLock_);
+ for (const auto* item : items) {
+ auto nodeId = TNodeId(item->node_id());
+ DoAddDescriptor(nodeId, item->node_descriptor());
+ }
+ }
+}
+
+void TNodeDirectory::MergeFrom(const TNodeDirectoryPtr& source)
+{
+ if (this == source.Get()) {
+ return;
+ }
+
+ std::vector<std::pair<TNodeId, TNodeDescriptor>> items;
+ {
+ auto thisGuard = WriterGuard(SpinLock_);
+ auto sourceGuard = ReaderGuard(source->SpinLock_);
+ items.reserve(source->IdToDescriptor_.size());
+ for (auto [id, descriptor] : source->IdToDescriptor_) {
+ if (CheckNodeDescriptor(id, *descriptor)) {
+ items.emplace_back(id, *descriptor);
+ }
+ }
+ }
+ {
+ auto thisGuard = WriterGuard(SpinLock_);
+ for (const auto& [id, descriptor] : items) {
+ DoAddDescriptor(id, descriptor);
+ }
+ }
+}
+
+void TNodeDirectory::DumpTo(NProto::TNodeDirectory* destination)
+{
+ auto guard = ReaderGuard(SpinLock_);
+ for (auto [id, descriptor] : IdToDescriptor_) {
+ auto* item = destination->add_items();
+ item->set_node_id(ToProto<ui32>(id));
+ ToProto(item->mutable_node_descriptor(), *descriptor);
+ }
+}
+
+void TNodeDirectory::Serialize(IYsonConsumer* consumer) const
+{
+ auto guard = ReaderGuard(SpinLock_);
+
+ BuildYsonFluently(consumer)
+ .BeginList()
+ .DoFor(IdToDescriptor_, [&] (TFluentList fluent, const std::pair<TNodeId, const TNodeDescriptor*>& pair) {
+ fluent
+ .Item()
+ .BeginMap()
+ .Item("node_id").Value(pair.first)
+ .Item("addresses").Value(pair.second->Addresses())
+ .EndMap();
+ })
+ .EndList();
+}
+
+void Serialize(const TNodeDirectory& nodeDirectory, NYson::IYsonConsumer* consumer)
+{
+ nodeDirectory.Serialize(consumer);
+}
+
+void TNodeDirectory::AddDescriptor(TNodeId id, const TNodeDescriptor& descriptor)
+{
+ auto guard = WriterGuard(SpinLock_);
+ DoAddDescriptor(id, descriptor);
+}
+
+bool TNodeDirectory::CheckNodeDescriptor(TNodeId id, const TNodeDescriptor& descriptor)
+{
+ auto it = IdToDescriptor_.find(id);
+ if (it == IdToDescriptor_.end()) {
+ return true;
+ }
+
+ if (auto lastSeenOnline = descriptor.GetLastSeenTime()) {
+ it->second->UpdateLastSeenTime(*lastSeenOnline);
+ }
+
+ return *it->second != descriptor;
+}
+
+void TNodeDirectory::DoAddDescriptor(TNodeId id, const TNodeDescriptor& descriptor)
+{
+ if (!CheckNodeDescriptor(id, descriptor)) {
+ return;
+ }
+ DoCaptureAndAddDescriptor(id, TNodeDescriptor(descriptor));
+}
+
+bool TNodeDirectory::CheckNodeDescriptor(TNodeId id, const NProto::TNodeDescriptor& descriptor)
+{
+ auto it = IdToDescriptor_.find(id);
+ if (it == IdToDescriptor_.end()) {
+ return true;
+ }
+
+ if (descriptor.has_last_seen_time()) {
+ it->second->UpdateLastSeenTime(FromProto<TInstant>(descriptor.last_seen_time()));
+ }
+
+ return *it->second != descriptor;
+}
+
+void TNodeDirectory::DoAddDescriptor(TNodeId id, const NProto::TNodeDescriptor& protoDescriptor)
+{
+ if (!CheckNodeDescriptor(id, protoDescriptor)) {
+ return;
+ }
+ DoCaptureAndAddDescriptor(id, FromProto<TNodeDescriptor>(protoDescriptor));
+}
+
+void TNodeDirectory::DoCaptureAndAddDescriptor(TNodeId id, TNodeDescriptor&& descriptor)
+{
+ auto it = Descriptors_.find(descriptor);
+ if (it == Descriptors_.end()) {
+ it = Descriptors_.insert(std::move(descriptor)).first;
+ }
+ const auto* capturedDescriptor = &*it;
+ IdToDescriptor_[id] = capturedDescriptor;
+ AddressToDescriptor_[capturedDescriptor->GetDefaultAddress()] = capturedDescriptor;
+
+ OnDescriptorAdded(id, capturedDescriptor);
+}
+
+void TNodeDirectory::OnDescriptorAdded(TNodeId id, const TNodeDescriptor* descriptor)
+{
+ if (auto it = IdToPromise_.find(id); it != IdToPromise_.end()) {
+ it->second.TrySet(descriptor);
+ YT_LOG_DEBUG("Awaited node descriptor added (NodeId: %v, NodeAddress: %v)", id, descriptor->GetDefaultAddress());
+ IdToPromise_.erase(it);
+ }
+}
+
+const TNodeDescriptor* TNodeDirectory::FindDescriptor(TNodeId id) const
+{
+ auto guard = ReaderGuard(SpinLock_);
+ auto it = IdToDescriptor_.find(id);
+ return it == IdToDescriptor_.end() ? nullptr : it->second;
+}
+
+const TNodeDescriptor& TNodeDirectory::GetDescriptor(TNodeId id) const
+{
+ const auto* result = FindDescriptor(id);
+ YT_VERIFY(result);
+ return *result;
+}
+
+TFuture<const TNodeDescriptor*> TNodeDirectory::GetAsyncDescriptor(TNodeId id)
+{
+ if (auto* descriptor = FindDescriptor(id)) {
+ return MakeFuture(descriptor);
+ }
+
+ TPromise<const TNodeDescriptor*> promise;
+ {
+ YT_LOG_DEBUG("Waiting for node descriptor (NodeId: %v)", id);
+ auto guard = WriterGuard(SpinLock_);
+ if (auto it = IdToPromise_.find(id); it != IdToPromise_.end()) {
+ promise = it->second;
+ } else {
+ promise = IdToPromise_[id] = NewPromise<const TNodeDescriptor*>();
+ }
+ }
+
+ return promise.ToFuture().ToUncancelable();
+}
+
+const TNodeDescriptor& TNodeDirectory::GetDescriptor(TChunkReplica replica) const
+{
+ return GetDescriptor(replica.GetNodeId());
+}
+
+std::vector<TNodeDescriptor> TNodeDirectory::GetDescriptors(const TChunkReplicaList& replicas) const
+{
+ std::vector<TNodeDescriptor> result;
+ for (auto replica : replicas) {
+ result.push_back(GetDescriptor(replica));
+ }
+ return result;
+}
+
+std::vector<std::pair<TNodeId, TNodeDescriptor>> TNodeDirectory::GetAllDescriptors() const
+{
+ auto guard = ReaderGuard(SpinLock_);
+
+ std::vector<std::pair<TNodeId, TNodeDescriptor>> result;
+ result.reserve(IdToDescriptor_.size());
+ for (auto [id, descriptor] : IdToDescriptor_) {
+ result.emplace_back(id, *descriptor);
+ }
+ return result;
+}
+
+const TNodeDescriptor* TNodeDirectory::FindDescriptor(const TString& address)
+{
+ auto guard = ReaderGuard(SpinLock_);
+ auto it = AddressToDescriptor_.find(address);
+ return it == AddressToDescriptor_.end() ? nullptr : it->second;
+}
+
+const TNodeDescriptor& TNodeDirectory::GetDescriptor(const TString& address)
+{
+ const auto* result = FindDescriptor(address);
+ YT_VERIFY(result);
+ return *result;
+}
+
+void TNodeDirectory::Save(TStreamSaveContext& context) const
+{
+ THashMap<TNodeId, TNodeDescriptor> idToDescriptor;
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ for (auto [id, descriptor] : IdToDescriptor_) {
+ YT_VERIFY(idToDescriptor.emplace(id, *descriptor).second);
+ }
+ }
+ using NYT::Save;
+ Save(context, idToDescriptor);
+}
+
+void TNodeDirectory::Load(TStreamLoadContext& context)
+{
+ using NYT::Load;
+ auto idToDescriptor = Load<THashMap<TNodeId, TNodeDescriptor>>(context);
+ auto guard = WriterGuard(SpinLock_);
+ for (const auto& [id, descriptor] : idToDescriptor) {
+ DoAddDescriptor(id, descriptor);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+TAddressMap::const_iterator SelectAddress(const TAddressMap& addresses, const TNetworkPreferenceList& networks)
+{
+ for (const auto& network : networks) {
+ const auto it = addresses.find(network);
+ if (it != addresses.cend()) {
+ return it;
+ }
+ }
+
+ return addresses.cend();
+}
+
+} // namespace
+
+std::optional<TString> FindAddress(const TAddressMap& addresses, const TNetworkPreferenceList& networks)
+{
+ const auto it = SelectAddress(addresses, networks);
+ return it == addresses.cend() ? std::nullopt : std::make_optional(it->second);
+}
+
+const TString& GetAddressOrThrow(const TAddressMap& addresses, const TNetworkPreferenceList& networks)
+{
+ const auto it = SelectAddress(addresses, networks);
+ if (it != addresses.cend()) {
+ return it->second;
+ }
+
+ THROW_ERROR_EXCEPTION("Cannot select address for host %v since there is no compatible network",
+ FindDefaultAddress(addresses))
+ << TErrorAttribute("remote_networks", GetKeys(addresses))
+ << TErrorAttribute("local_networks", networks);
+}
+
+const TAddressMap& GetAddressesOrThrow(const TNodeAddressMap& nodeAddresses, EAddressType type)
+{
+ auto it = nodeAddresses.find(type);
+ if (it != nodeAddresses.cend()) {
+ return it->second;
+ }
+
+ THROW_ERROR_EXCEPTION("No addresses known for address type %Qlv", type)
+ << TErrorAttribute("known_types", GetKeys(nodeAddresses));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t THash<NYT::NNodeTrackerClient::TNodeDescriptor>::operator()(
+ const NYT::NNodeTrackerClient::TNodeDescriptor& nodeDescriptor) const
+{
+ size_t result = 0;
+ using namespace NYT;
+ HashCombine(result, nodeDescriptor.GetDefaultAddress());
+ HashCombine(result, nodeDescriptor.GetHost());
+ HashCombine(result, nodeDescriptor.GetRack());
+ HashCombine(result, nodeDescriptor.GetDataCenter());
+ for (const auto& [network, address] : nodeDescriptor.Addresses()) {
+ HashCombine(result, network);
+ HashCombine(result, address);
+ }
+ for (const auto& tag : NYT::NNodeTrackerClient::GetSortedTags(nodeDescriptor.GetTags())) {
+ HashCombine(result, tag);
+ }
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt/client/node_tracker_client/node_directory.h b/yt/yt/client/node_tracker_client/node_directory.h
new file mode 100644
index 0000000000..a76245fd8b
--- /dev/null
+++ b/yt/yt/client/node_tracker_client/node_directory.h
@@ -0,0 +1,195 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/chunk_client/chunk_replica.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/rpc/helpers.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/misc/property.h>
+#include <yt/yt/core/misc/copyable_atomic.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+#include <library/cpp/yt/threading/rw_spin_lock.h>
+
+namespace NYT::NNodeTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Network-related node information.
+class TNodeDescriptor
+{
+public:
+ TNodeDescriptor();
+ TNodeDescriptor(const TNodeDescriptor& other) = default;
+ TNodeDescriptor(TNodeDescriptor&& other) = default;
+ explicit TNodeDescriptor(const TString& defaultAddress);
+ explicit TNodeDescriptor(const std::optional<TString>& defaultAddress);
+ explicit TNodeDescriptor(
+ TAddressMap addresses,
+ std::optional<TString> host = std::nullopt,
+ std::optional<TString> rack = std::nullopt,
+ std::optional<TString> dc = std::nullopt,
+ const std::vector<TString>& tags = {},
+ std::optional<TInstant> lastSeenTime = {});
+
+ TNodeDescriptor& operator=(const TNodeDescriptor& other) = default;
+ TNodeDescriptor& operator=(TNodeDescriptor&& other) = default;
+
+ bool IsNull() const;
+
+ const TAddressMap& Addresses() const;
+
+ const TString& GetDefaultAddress() const;
+
+ const TString& GetAddressOrThrow(const TNetworkPreferenceList& networks) const;
+
+ std::optional<TString> FindAddress(const TNetworkPreferenceList& networks) const;
+
+ const std::optional<TString>& GetHost() const;
+ const std::optional<TString>& GetRack() const;
+ const std::optional<TString>& GetDataCenter() const;
+
+ const std::vector<TString>& GetTags() const;
+
+ //! GetLastSeenTime returns last instant when node was seen online on some master.
+ /*!
+ * Might be used for cheap and dirty availability check.
+ * This field is not persisted.
+ */
+ std::optional<TInstant> GetLastSeenTime() const;
+ void UpdateLastSeenTime(TInstant at) const;
+
+ void Persist(const TStreamPersistenceContext& context);
+
+private:
+ TAddressMap Addresses_;
+ TString DefaultAddress_;
+ std::optional<TString> Host_;
+ std::optional<TString> Rack_;
+ std::optional<TString> DataCenter_;
+ std::vector<TString> Tags_;
+
+ // Not persisted.
+ mutable TCopyableAtomic<TCpuInstant> LastSeenTime_;
+};
+
+const TString& NullNodeAddress();
+const TNodeDescriptor& NullNodeDescriptor();
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (const TNodeDescriptor& lhs, const TNodeDescriptor& rhs);
+bool operator != (const TNodeDescriptor& lhs, const TNodeDescriptor& rhs);
+
+bool operator == (const TNodeDescriptor& lhs, const NProto::TNodeDescriptor& rhs);
+bool operator != (const TNodeDescriptor& lhs, const NProto::TNodeDescriptor& rhs);
+
+void FormatValue(TStringBuilderBase* builder, const TNodeDescriptor& descriptor, TStringBuf spec);
+TString ToString(const TNodeDescriptor& descriptor);
+
+// Accessors for some well-known addresses.
+std::optional<TString> FindDefaultAddress(const TAddressMap& addresses);
+const TString& GetDefaultAddress(const TAddressMap& addresses);
+const TString& GetDefaultAddress(const NProto::TAddressMap& addresses);
+
+const TString& GetAddressOrThrow(const TAddressMap& addresses, const TNetworkPreferenceList& networks);
+std::optional<TString> FindAddress(const TAddressMap& addresses, const TNetworkPreferenceList& networks);
+
+const TAddressMap& GetAddressesOrThrow(const TNodeAddressMap& nodeAddresses, EAddressType type);
+
+//! Keep the items in this particular order: the further the better.
+DEFINE_ENUM(EAddressLocality,
+ (None)
+ (SameDataCenter)
+ (SameRack)
+ (SameHost)
+);
+
+EAddressLocality ComputeAddressLocality(const TNodeDescriptor& first, const TNodeDescriptor& second);
+
+namespace NProto {
+
+void ToProto(NNodeTrackerClient::NProto::TAddressMap* protoAddresses, const NNodeTrackerClient::TAddressMap& addresses);
+void FromProto(NNodeTrackerClient::TAddressMap* addresses, const NNodeTrackerClient::NProto::TAddressMap& protoAddresses);
+
+void ToProto(NNodeTrackerClient::NProto::TNodeAddressMap* proto, const NNodeTrackerClient::TNodeAddressMap& nodeAddresses);
+void FromProto(NNodeTrackerClient::TNodeAddressMap* nodeAddresses, const NNodeTrackerClient::NProto::TNodeAddressMap& proto);
+
+void ToProto(NNodeTrackerClient::NProto::TNodeDescriptor* protoDescriptor, const NNodeTrackerClient::TNodeDescriptor& descriptor);
+void FromProto(NNodeTrackerClient::TNodeDescriptor* descriptor, const NNodeTrackerClient::NProto::TNodeDescriptor& protoDescriptor);
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
+
+template <>
+struct THash<NYT::NNodeTrackerClient::TNodeDescriptor>
+{
+ size_t operator()(const NYT::NNodeTrackerClient::TNodeDescriptor& value) const;
+};
+
+namespace NYT::NNodeTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Caches node descriptors obtained by fetch requests.
+/*!
+ * \note
+ * Thread affinity: thread-safe
+ */
+class TNodeDirectory
+ : public TRefCounted
+{
+public:
+ void MergeFrom(const NProto::TNodeDirectory& source);
+ void MergeFrom(const TNodeDirectoryPtr& source);
+ void DumpTo(NProto::TNodeDirectory* destination);
+ void Serialize(NYson::IYsonConsumer* consumer) const;
+
+ void AddDescriptor(TNodeId id, const TNodeDescriptor& descriptor);
+
+ const TNodeDescriptor* FindDescriptor(TNodeId id) const;
+ const TNodeDescriptor& GetDescriptor(TNodeId id) const;
+ TFuture<const TNodeDescriptor*> GetAsyncDescriptor(TNodeId id);
+ const TNodeDescriptor& GetDescriptor(NChunkClient::TChunkReplica replica) const;
+ std::vector<TNodeDescriptor> GetDescriptors(const NChunkClient::TChunkReplicaList& replicas) const;
+ std::vector<std::pair<NNodeTrackerClient::TNodeId, TNodeDescriptor>> GetAllDescriptors() const;
+
+ const TNodeDescriptor* FindDescriptor(const TString& address);
+ const TNodeDescriptor& GetDescriptor(const TString& address);
+
+ void Save(TStreamSaveContext& context) const;
+ void Load(TStreamLoadContext& context);
+
+private:
+ YT_DECLARE_SPIN_LOCK(NThreading::TReaderWriterSpinLock, SpinLock_);
+ THashMap<TNodeId, const TNodeDescriptor*> IdToDescriptor_;
+ THashMap<TString, const TNodeDescriptor*> AddressToDescriptor_;
+ THashSet<TNodeDescriptor> Descriptors_;
+
+ THashMap<TNodeId, TPromise<const TNodeDescriptor*>> IdToPromise_;
+
+ bool CheckNodeDescriptor(TNodeId id, const TNodeDescriptor& descriptor);
+ void DoAddDescriptor(TNodeId id, const TNodeDescriptor& descriptor);
+ bool CheckNodeDescriptor(TNodeId id, const NProto::TNodeDescriptor& descriptor);
+ void DoAddDescriptor(TNodeId id, const NProto::TNodeDescriptor& protoDescriptor);
+ void DoCaptureAndAddDescriptor(TNodeId id, TNodeDescriptor&& descriptorHolder);
+
+ void OnDescriptorAdded(TNodeId id, const TNodeDescriptor* descriptor);
+};
+
+void Serialize(const TNodeDirectory& nodeDirectory, NYson::IYsonConsumer* consumer);
+
+DEFINE_REFCOUNTED_TYPE(TNodeDirectory)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
diff --git a/yt/yt/client/node_tracker_client/private.h b/yt/yt/client/node_tracker_client/private.h
new file mode 100644
index 0000000000..162e8bd259
--- /dev/null
+++ b/yt/yt/client/node_tracker_client/private.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NNodeTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger NodeTrackerClientLogger("NodeTrackerClient");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
diff --git a/yt/yt/client/node_tracker_client/public.cpp b/yt/yt/client/node_tracker_client/public.cpp
new file mode 100644
index 0000000000..0ffc8e4ee8
--- /dev/null
+++ b/yt/yt/client/node_tracker_client/public.cpp
@@ -0,0 +1,13 @@
+#include "public.h"
+
+namespace NYT::NNodeTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString DefaultNetworkName("default");
+const TNetworkPreferenceList DefaultNetworkPreferences{DefaultNetworkName};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
+
diff --git a/yt/yt/client/node_tracker_client/public.h b/yt/yt/client/node_tracker_client/public.h
new file mode 100644
index 0000000000..f0d44bbf8d
--- /dev/null
+++ b/yt/yt/client/node_tracker_client/public.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <library/cpp/yt/misc/strong_typedef.h>
+
+namespace NYT::NNodeTrackerClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+class TNodeStatistics;
+class TNodeResources;
+class TNodeResourceLimitsOverrides;
+
+class TDiskResources;
+class TDiskLocationResources;
+
+class TAddressMap;
+class TNodeAddressMap;
+
+class TNodeDescriptor;
+class TNodeDirectory;
+
+} // namespace NProto
+
+YT_DEFINE_ERROR_ENUM(
+ ((NoSuchNode) (1600))
+ ((InvalidState) (1601))
+ ((NoSuchNetwork) (1602))
+ ((NoSuchRack) (1603))
+ ((NoSuchDataCenter) (1604))
+);
+
+DEFINE_ENUM(EAddressType,
+ ((InternalRpc) (0))
+ ((SkynetHttp) (1))
+ ((MonitoringHttp) (2))
+);
+
+YT_DEFINE_STRONG_TYPEDEF(TNodeId, ui32);
+constexpr TNodeId InvalidNodeId = TNodeId(0);
+constexpr TNodeId MaxNodeId = TNodeId((1 << 24) - 1); // TNodeId must fit into 24 bits (see TChunkReplica)
+
+using THostId = NObjectClient::TObjectId;
+using TRackId = NObjectClient::TObjectId;
+using TDataCenterId = NObjectClient::TObjectId;
+
+// Only domain names, without port number.
+using TNetworkAddressList = std::vector<std::pair<TString, TString>>;
+using TNetworkPreferenceList = std::vector<TString>;
+
+// Network -> host:port.
+using TAddressMap = THashMap<TString, TString>;
+
+// Address type (e.g. RPC, HTTP) -> network -> host:port.
+using TNodeAddressMap = THashMap<EAddressType, TAddressMap>;
+
+DECLARE_REFCOUNTED_CLASS(TNodeDirectory)
+class TNodeDescriptor;
+
+extern const TString DefaultNetworkName;
+extern const TNetworkPreferenceList DefaultNetworkPreferences;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNodeTrackerClient
diff --git a/yt/yt/client/object_client/helpers-inl.h b/yt/yt/client/object_client/helpers-inl.h
new file mode 100644
index 0000000000..9297fec91d
--- /dev/null
+++ b/yt/yt/client/object_client/helpers-inl.h
@@ -0,0 +1,186 @@
+#ifndef HELPERS_INL_H_
+#error "Direct inclusion of this file is not allowed, include helpers.h"
+// For the sake of sane code completion.
+#include "helpers.h"
+#endif
+
+#include <util/random/random.h>
+
+namespace NYT::NObjectClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline EObjectType TypeFromId(TObjectId id)
+{
+ return EObjectType(id.Parts32[1] & 0xffff);
+}
+
+inline TCellTag CellTagFromId(TObjectId id)
+{
+ return TCellTag(id.Parts32[1] >> 16);
+}
+
+inline ui64 CounterFromId(TObjectId id)
+{
+ ui64 result;
+ result = id.Parts32[3];
+ result <<= 32;
+ result |= id.Parts32[2];
+ return result;
+}
+
+inline ui32 HashFromId(TObjectId id)
+{
+ return id.Parts32[0];
+}
+
+inline NHydra::TVersion VersionFromId(TObjectId id)
+{
+ YT_ASSERT(!IsSequoiaId(id));
+ return NHydra::TVersion::FromRevision(CounterFromId(id));
+}
+
+inline NTransactionClient::TTimestamp TimestampFromId(TObjectId id)
+{
+ YT_ASSERT(IsSequoiaId(id));
+ return CounterFromId(id) & ~SequoiaCounterMask;
+}
+
+inline EObjectType SchemaTypeFromType(EObjectType type)
+{
+ YT_ASSERT(HasSchema(type));
+ return EObjectType(static_cast<int>(type) | SchemaObjectTypeMask);
+}
+
+inline EObjectType TypeFromSchemaType(EObjectType type)
+{
+ YT_ASSERT(static_cast<int>(type) & SchemaObjectTypeMask);
+ return EObjectType(static_cast<int>(type) & ~SchemaObjectTypeMask);
+}
+
+inline TObjectId MakeId(
+ EObjectType type,
+ TCellTag cellTag,
+ ui64 counter,
+ ui32 hash)
+{
+ return TObjectId(
+ hash,
+ (static_cast<ui32>(cellTag.Underlying()) << 16) | static_cast<ui32>(type),
+ counter & 0xffffffff,
+ counter >> 32);
+}
+
+inline TObjectId MakeRandomId(
+ EObjectType type,
+ TCellTag cellTag)
+{
+ return MakeId(
+ type,
+ cellTag,
+ RandomNumber<ui64>(),
+ RandomNumber<ui32>());
+}
+
+inline bool IsWellKnownId(TObjectId id)
+{
+ return CounterFromId(id) & WellKnownCounterMask;
+}
+
+inline bool IsSequoiaId(TObjectId id)
+{
+ // NB: Well-known objects have Sequoia bit set.
+ return (CounterFromId(id) & SequoiaCounterMask) && !IsWellKnownId(id);
+}
+
+inline TObjectId MakeRegularId(
+ EObjectType type,
+ TCellTag cellTag,
+ NHydra::TVersion version,
+ ui32 hash)
+{
+ return TObjectId(
+ hash,
+ (static_cast<ui32>(cellTag.Underlying()) << 16) | static_cast<ui32>(type),
+ version.RecordId,
+ version.SegmentId);
+}
+
+inline TObjectId MakeSequoiaId(
+ EObjectType type,
+ TCellTag cellTag,
+ NTransactionClient::TTimestamp timestamp,
+ ui32 hash)
+{
+ YT_ASSERT(!(timestamp & SequoiaCounterMask));
+ return MakeId(
+ type,
+ cellTag,
+ timestamp | SequoiaCounterMask,
+ hash);
+}
+
+inline TObjectId MakeWellKnownId(
+ EObjectType type,
+ TCellTag cellTag,
+ ui64 counter /*= 0xffffffffffffffff*/)
+{
+ YT_VERIFY(counter & WellKnownCounterMask);
+ return MakeId(
+ type,
+ cellTag,
+ counter,
+ static_cast<ui32>(cellTag.Underlying() * 901517) ^ 0x140a8383);
+}
+
+inline TObjectId MakeSchemaObjectId(
+ EObjectType type,
+ TCellTag cellTag)
+{
+ return MakeWellKnownId(SchemaTypeFromType(type), cellTag);
+}
+
+inline TObjectId ReplaceTypeInId(
+ TObjectId id,
+ EObjectType type)
+{
+ auto result = id;
+ result.Parts32[1] &= ~0x0000ffff;
+ result.Parts32[1] |= static_cast<ui32>(type);
+ return result;
+}
+
+inline TObjectId ReplaceCellTagInId(
+ TObjectId id,
+ TCellTag cellTag)
+{
+ auto result = id;
+ result.Parts32[1] &= ~0xffff0000;
+ result.Parts32[1] |= static_cast<ui32>(cellTag.Underlying()) << 16;
+ return result;
+}
+
+template <int ShardCount>
+inline int GetShardIndex(TObjectId id)
+{
+ static_assert(IsPowerOf2(ShardCount), "Number of shards must be a power of 2");
+ return TDirectObjectIdHash()(id) & (ShardCount - 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_FORCE_INLINE size_t TDirectObjectIdHash::operator()(TObjectId id) const
+{
+ return id.Parts32[0];
+}
+
+Y_FORCE_INLINE size_t TDirectVersionedObjectIdHash::operator()(const TVersionedObjectId& id) const
+{
+ return
+ TDirectObjectIdHash()(id.TransactionId) * 497 +
+ TDirectObjectIdHash()(id.ObjectId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NObjectClient
diff --git a/yt/yt/client/object_client/helpers.cpp b/yt/yt/client/object_client/helpers.cpp
new file mode 100644
index 0000000000..3940dedae4
--- /dev/null
+++ b/yt/yt/client/object_client/helpers.cpp
@@ -0,0 +1,263 @@
+#include "helpers.h"
+
+#include <yt/yt/core/misc/guid.h>
+
+namespace NYT::NObjectClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TStringBuf ObjectIdPathPrefix("#");
+
+NYPath::TYPath FromObjectId(TObjectId id)
+{
+ return TString(ObjectIdPathPrefix) + ToString(id);
+}
+
+bool IsVersionedType(EObjectType type)
+{
+ return
+ type == EObjectType::StringNode ||
+ type == EObjectType::Int64Node ||
+ type == EObjectType::Uint64Node ||
+ type == EObjectType::DoubleNode ||
+ type == EObjectType::BooleanNode ||
+ type == EObjectType::MapNode ||
+ type == EObjectType::ListNode ||
+ type == EObjectType::File ||
+ type == EObjectType::Table ||
+ type == EObjectType::ReplicatedTable ||
+ type == EObjectType::ReplicationLogTable ||
+ type == EObjectType::Journal ||
+ type == EObjectType::ChunkMap ||
+ type == EObjectType::LostChunkMap ||
+ type == EObjectType::LostVitalChunkMap ||
+ type == EObjectType::PrecariousChunkMap ||
+ type == EObjectType::PrecariousVitalChunkMap ||
+ type == EObjectType::OverreplicatedChunkMap ||
+ type == EObjectType::UnderreplicatedChunkMap ||
+ type == EObjectType::DataMissingChunkMap ||
+ type == EObjectType::ParityMissingChunkMap ||
+ type == EObjectType::OldestPartMissingChunkMap ||
+ type == EObjectType::QuorumMissingChunkMap ||
+ type == EObjectType::UnsafelyPlacedChunkMap ||
+ type == EObjectType::InconsistentlyPlacedChunkMap ||
+ type == EObjectType::UnexpectedOverreplicatedChunkMap ||
+ type == EObjectType::ReplicaTemporarilyUnavailableChunkMap ||
+ type == EObjectType::ForeignChunkMap ||
+ type == EObjectType::LocalLostChunkMap ||
+ type == EObjectType::LocalLostVitalChunkMap ||
+ type == EObjectType::LocalPrecariousChunkMap ||
+ type == EObjectType::LocalPrecariousVitalChunkMap ||
+ type == EObjectType::LocalOverreplicatedChunkMap ||
+ type == EObjectType::LocalUnderreplicatedChunkMap ||
+ type == EObjectType::LocalDataMissingChunkMap ||
+ type == EObjectType::LocalParityMissingChunkMap ||
+ type == EObjectType::LocalOldestPartMissingChunkMap ||
+ type == EObjectType::LocalQuorumMissingChunkMap ||
+ type == EObjectType::LocalUnsafelyPlacedChunkMap ||
+ type == EObjectType::LocalInconsistentlyPlacedChunkMap ||
+ type == EObjectType::LocalUnexpectedOverreplicatedChunkMap ||
+ type == EObjectType::LocalReplicaTemporarilyUnavailableChunkMap ||
+ type == EObjectType::RackMap ||
+ type == EObjectType::DataCenterMap ||
+ type == EObjectType::HostMap ||
+ type == EObjectType::ChunkLocationMap ||
+ type == EObjectType::ChunkListMap ||
+ type == EObjectType::ChunkViewMap ||
+ type == EObjectType::MediumMap ||
+ type == EObjectType::TransactionMap ||
+ type == EObjectType::TopmostTransactionMap ||
+ type == EObjectType::ClusterNodeNode ||
+ type == EObjectType::LegacyClusterNodeMap ||
+ type == EObjectType::ClusterNodeMap ||
+ type == EObjectType::DataNodeMap ||
+ type == EObjectType::ExecNodeMap ||
+ type == EObjectType::TabletNodeMap ||
+ type == EObjectType::ChaosNodeMap ||
+ type == EObjectType::Orchid ||
+ type == EObjectType::AccountMap ||
+ type == EObjectType::UserMap ||
+ type == EObjectType::GroupMap ||
+ type == EObjectType::AccountResourceUsageLeaseMap ||
+ type == EObjectType::SchedulerPoolTreeMap ||
+ type == EObjectType::Link ||
+ type == EObjectType::Document ||
+ type == EObjectType::LockMap ||
+ type == EObjectType::TabletMap ||
+ type == EObjectType::TabletCellMap ||
+ type == EObjectType::TabletCellNode ||
+ type == EObjectType::TabletCellBundleMap ||
+ type == EObjectType::TabletActionMap ||
+ type == EObjectType::AreaMap ||
+ type == EObjectType::ChaosCellMap ||
+ type == EObjectType::ChaosCellBundleMap ||
+ type == EObjectType::SysNode ||
+ type == EObjectType::PortalEntrance ||
+ type == EObjectType::PortalExit ||
+ type == EObjectType::PortalEntranceMap ||
+ type == EObjectType::PortalExitMap ||
+ type == EObjectType::CypressShardMap ||
+ type == EObjectType::EstimatedCreationTimeMap ||
+ type == EObjectType::NetworkProjectMap ||
+ type == EObjectType::HttpProxyRoleMap ||
+ type == EObjectType::RpcProxyRoleMap ||
+ type == EObjectType::MasterTableSchemaMap ||
+ type == EObjectType::ChaosReplicatedTable ||
+ type == EObjectType::AccessControlObjectNamespaceMap ||
+ type == EObjectType::HunkStorage ||
+ type == EObjectType::ZookeeperShardMap ||
+ type == EObjectType::Rootstock ||
+ type == EObjectType::RootstockMap ||
+ type == EObjectType::Scion ||
+ type == EObjectType::ScionMap;
+}
+
+bool IsUserType(EObjectType type)
+{
+ return
+ type == EObjectType::Transaction ||
+ type == EObjectType::Chunk ||
+ type == EObjectType::JournalChunk ||
+ type == EObjectType::ErasureChunk ||
+ type == EObjectType::ErasureJournalChunk ||
+ type == EObjectType::ChunkList ||
+ type == EObjectType::StringNode ||
+ type == EObjectType::Int64Node ||
+ type == EObjectType::Uint64Node ||
+ type == EObjectType::DoubleNode ||
+ type == EObjectType::BooleanNode ||
+ type == EObjectType::MapNode ||
+ type == EObjectType::ListNode ||
+ type == EObjectType::File ||
+ type == EObjectType::Table ||
+ type == EObjectType::ReplicatedTable ||
+ type == EObjectType::ReplicationLogTable ||
+ type == EObjectType::TableReplica ||
+ type == EObjectType::TabletAction ||
+ type == EObjectType::Journal ||
+ type == EObjectType::Link ||
+ type == EObjectType::AccessControlObject ||
+ type == EObjectType::Document ||
+ type == EObjectType::Account ||
+ type == EObjectType::SchedulerPool ||
+ type == EObjectType::SchedulerPoolTree ||
+ type == EObjectType::ChaosReplicatedTable ||
+ type == EObjectType::HunkStorage;
+}
+
+bool IsSchemafulType(EObjectType type)
+{
+ return
+ IsTableType(type) ||
+ type == EObjectType::ChaosReplicatedTable;
+}
+
+bool IsTableType(EObjectType type)
+{
+ return
+ type == EObjectType::Table ||
+ type == EObjectType::ReplicatedTable ||
+ type == EObjectType::ReplicationLogTable;
+}
+
+bool IsLogTableType(EObjectType type)
+{
+ return
+ type == EObjectType::ReplicatedTable ||
+ type == EObjectType::ReplicationLogTable;
+}
+
+bool IsTabletOwnerType(EObjectType type)
+{
+ return
+ IsTableType(type) ||
+ type == EObjectType::HunkStorage;
+}
+
+bool IsCellType(EObjectType type)
+{
+ return
+ type == EObjectType::TabletCell ||
+ type == EObjectType::ChaosCell;
+}
+
+bool IsCellBundleType(EObjectType type)
+{
+ return
+ type == EObjectType::TabletCellBundle ||
+ type == EObjectType::ChaosCellBundle;
+}
+
+bool IsAlienType(EObjectType type)
+{
+ return type == EObjectType::ChaosCell;
+}
+
+bool IsTabletType(EObjectType type)
+{
+ return
+ type == EObjectType::Tablet ||
+ type == EObjectType::HunkTablet;
+}
+
+bool IsReplicatedTableType(EObjectType type)
+{
+ return
+ type == EObjectType::ReplicatedTable ||
+ type == EObjectType::ReplicationCard;
+}
+
+bool IsTableReplicaType(EObjectType type)
+{
+ return
+ type == EObjectType::TableReplica ||
+ IsChaosTableReplicaType(type);
+}
+
+bool IsChaosTableReplicaType(EObjectType type)
+{
+ return type == EObjectType::ChaosTableReplica;
+}
+
+bool IsCollocationType(EObjectType type)
+{
+ return
+ type == EObjectType::TableCollocation ||
+ type == EObjectType::ReplicationCardCollocation;
+}
+
+bool IsMediumType(EObjectType type)
+{
+ return
+ type == EObjectType::DomesticMedium ||
+ type == EObjectType::S3Medium;
+}
+
+bool HasSchema(EObjectType type)
+{
+ if (type == EObjectType::Master) {
+ return false;
+ }
+ if (IsSchemaType(type)) {
+ return false;
+ }
+ return true;
+}
+
+bool IsSchemaType(EObjectType type)
+{
+ return (static_cast<ui32>(type) & SchemaObjectTypeMask) != 0;
+}
+
+bool IsGlobalCellId(TCellId cellId)
+{
+ auto type = TypeFromId(cellId);
+ return
+ type == EObjectType::MasterCell ||
+ type == EObjectType::ChaosCell;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NObjectClient
+
diff --git a/yt/yt/client/object_client/helpers.h b/yt/yt/client/object_client/helpers.h
new file mode 100644
index 0000000000..4f7280e4a8
--- /dev/null
+++ b/yt/yt/client/object_client/helpers.h
@@ -0,0 +1,191 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/hydra/version.h>
+
+#include <yt/yt/client/transaction_client/public.h>
+
+#include <yt/yt/client/ypath/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+
+namespace NYT::NObjectClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! |#|-prefix.
+extern const TStringBuf ObjectIdPathPrefix;
+
+//! Creates the YPath pointing to an object with a given #id.
+NYPath::TYPath FromObjectId(TObjectId id);
+
+//! Checks if the given type is versioned, i.e. represents a Cypress node.
+bool IsVersionedType(EObjectType type);
+
+//! Checks if the given type is user, i.e. regular users are allowed to create its instances.
+bool IsUserType(EObjectType type);
+
+//! Checks if the given type can have schema assigned to it, i.e. Cypress table or chaos replicated table.
+bool IsSchemafulType(EObjectType type);
+
+//! Checks if the given type is table, i.e. represents a Cypress table.
+bool IsTableType(EObjectType type);
+
+//! Checks if the given type is log table, i.e. table which contains replication log.
+bool IsLogTableType(EObjectType type);
+
+//! Checks if the given type is tablet owner.
+bool IsTabletOwnerType(EObjectType type);
+
+//! Checks if the given type is cell.
+bool IsCellType(EObjectType type);
+
+//! Checks if the given type is cell bundle.
+bool IsCellBundleType(EObjectType type);
+
+//! Checks if the given type allows alien objects.
+bool IsAlienType(EObjectType type);
+
+//! Checks if the given type is a tablet.
+bool IsTabletType(EObjectType type);
+
+//! Checks if the given type holds replication metadata.
+bool IsReplicatedTableType(EObjectType);
+
+//! Checks if the given type is a table replica.
+bool IsTableReplicaType(EObjectType type);
+
+//! Checks if the given type is a chaos replica.
+bool IsChaosTableReplicaType(EObjectType type);
+
+//! Checks if the given type is a collocation.
+bool IsCollocationType(EObjectType type);
+
+//! Checks if the given type is a medium;
+bool IsMediumType(EObjectType type);
+
+//! Extracts the type component from #id.
+EObjectType TypeFromId(TObjectId id);
+
+//! Extracts the cell id component from #id.
+TCellTag CellTagFromId(TObjectId id);
+
+//! Extracts the counter component from #id.
+ui64 CounterFromId(TObjectId id);
+
+//! Extracts the hash component from #id.
+ui32 HashFromId(TObjectId id);
+
+//! Extracts Hydra revision from #id for non-Sequoia objects.
+NHydra::TVersion VersionFromId(TObjectId id);
+
+//! Extracts the object creation timestamp from #id for Sequoia object.
+NTransactionClient::TTimestamp TimestampFromId(TObjectId id);
+
+//! Returns |true| iff a given #type denotes a schema.
+bool IsSchemaType(EObjectType type);
+
+//! Returns |true| iff a given regular #type has an associated schema type.
+bool HasSchema(EObjectType type);
+
+//! Returns the schema type for a given regular #type.
+EObjectType SchemaTypeFromType(EObjectType type);
+
+//! Returns the regular type for a given schema #type.
+EObjectType TypeFromSchemaType(EObjectType type);
+
+//! Constructs the id from its parts.
+TObjectId MakeId(
+ EObjectType type,
+ TCellTag cellTag,
+ ui64 counter,
+ ui32 hash);
+
+//! Creates a random id with given type and cell tag.
+TObjectId MakeRandomId(
+ EObjectType type,
+ TCellTag cellTag);
+
+//! Returns |true| if a given #id is well-known.
+/*
+ * This method checks the highest bit of counter part.
+ */
+bool IsWellKnownId(TObjectId id);
+
+//! Returns |true| if a given #id corresponds to Sequoia.
+/*
+ * This method checks the second highest bit of counter part.
+ */
+bool IsSequoiaId(TObjectId id);
+
+//! Constructs the id for a regular object.
+TObjectId MakeRegularId(
+ EObjectType type,
+ TCellTag cellTag,
+ NHydra::TVersion version,
+ ui32 hash);
+
+//! Constructs the id for a regular Sequoia object.
+TObjectId MakeSequoiaId(
+ EObjectType type,
+ TCellTag cellTag,
+ NTransactionClient::TTimestamp timestamp,
+ ui32 hash);
+
+//! Constructs the id corresponding to well-known (usually singleton) entities.
+/*
+ * The highest bit of #counter must be set.
+ */
+TObjectId MakeWellKnownId(
+ EObjectType type,
+ TCellTag cellTag,
+ ui64 counter = 0xffffffffffffffff);
+
+//! Returns the id of the schema object for a given regular type.
+TObjectId MakeSchemaObjectId(
+ EObjectType type,
+ TCellTag cellTag);
+
+//! Constructs a new object id by replacing type component in a given one.
+TObjectId ReplaceTypeInId(
+ TObjectId id,
+ EObjectType type);
+
+//! Constructs a new object id by replacing cell tag component in a given one.
+TObjectId ReplaceCellTagInId(
+ TObjectId id,
+ TCellTag cellTag);
+
+//! Useful for uniform partition of objects between shards.
+// NB: #shardCount must be a power of 2.
+template <int ShardCount>
+int GetShardIndex(TObjectId id);
+
+//! Returns true if #cellId is uniquely identified by its tag component.
+//! Currently these are master and chaos cells.
+bool IsGlobalCellId(TCellId cellId);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Relies on first 32 bits of object id to be pseudo-random,
+//! cf. TObjectManager::GenerateId.
+struct TDirectObjectIdHash
+{
+ size_t operator()(TObjectId id) const;
+};
+
+//! Cf. TDirectObjectIdHash
+struct TDirectVersionedObjectIdHash
+{
+ size_t operator()(const TVersionedObjectId& id) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NObjectClient
+
+#define HELPERS_INL_H_
+#include "helpers-inl.h"
+#undef HELPERS_INL_H_
diff --git a/yt/yt/client/object_client/public.cpp b/yt/yt/client/object_client/public.cpp
new file mode 100644
index 0000000000..1a780352e5
--- /dev/null
+++ b/yt/yt/client/object_client/public.cpp
@@ -0,0 +1,68 @@
+#include "public.h"
+
+#include <yt/yt/core/misc/guid.h>
+
+#include <util/string/vector.h>
+
+namespace NYT::NObjectClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TVersionedObjectId::TVersionedObjectId(TObjectId objectId)
+ : ObjectId(objectId)
+{ }
+
+TVersionedObjectId::TVersionedObjectId(
+ TObjectId objectId,
+ TTransactionId transactionId)
+ : ObjectId(objectId)
+ , TransactionId(transactionId)
+{ }
+
+bool TVersionedObjectId::IsBranched() const
+{
+ return TransactionId.operator bool();
+}
+
+TVersionedObjectId TVersionedObjectId::FromString(TStringBuf str)
+{
+ TStringBuf objectToken, transactionToken;
+ str.Split(':', objectToken, transactionToken);
+
+ auto objectId = TObjectId::FromString(objectToken);
+ auto transactionId =
+ transactionToken.empty()
+ ? NullTransactionId
+ : TTransactionId::FromString(transactionToken);
+ return TVersionedObjectId(objectId, transactionId);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TVersionedObjectId& id, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%v:%v", id.ObjectId, id.TransactionId);
+}
+
+TString ToString(const TVersionedObjectId& id)
+{
+ return ToStringViaBuilder(id);
+}
+
+bool operator == (const TVersionedObjectId& lhs, const TVersionedObjectId& rhs)
+{
+ return memcmp(&lhs, &rhs, sizeof (TVersionedObjectId)) == 0;
+}
+
+bool operator != (const TVersionedObjectId& lhs, const TVersionedObjectId& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool operator < (const TVersionedObjectId& lhs, const TVersionedObjectId& rhs)
+{
+ return memcmp(&lhs, &rhs, sizeof (TVersionedObjectId)) < 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NObjectClient
+
diff --git a/yt/yt/client/object_client/public.h b/yt/yt/client/object_client/public.h
new file mode 100644
index 0000000000..4101bb0f46
--- /dev/null
+++ b/yt/yt/client/object_client/public.h
@@ -0,0 +1,412 @@
+#pragma once
+
+#include <library/cpp/yt/misc/enum.h>
+#include <library/cpp/yt/misc/guid.h>
+#include <library/cpp/yt/misc/hash.h>
+
+#include <yt/yt/client/election/public.h>
+
+#include <library/cpp/yt/misc/strong_typedef.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+#include <library/cpp/yt/string/string_builder.h>
+
+namespace NYT::NObjectClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((PrerequisiteCheckFailed) (1000))
+ ((InvalidObjectLifeStage) (1001))
+ ((CrossCellAdditionalPath) (1002))
+ ((CrossCellRevisionPrerequisitePath) (1003))
+ ((ForwardedRequestFailed) (1004))
+ ((CannotCacheMutatingRequest) (1005))
+ ((InvalidObjectType) (1006))
+ ((RequestInvolvesSequoia) (1007))
+ ((RequestInvolvesCypress) (1008))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Provides a globally unique identifier for an object.
+/*!
+ * TGuid consists of four 32-bit parts.
+ * For TObjectId, these parts have the following meaning:
+ *
+ * Part 0: some hash
+ * Part 1: bits 0..15: object type
+ * bits 16..31: cell id
+ * Part 2: the lower part of 64-bit sequential counter
+ * Part 3: the higher part of 64-bit sequential counter
+ */
+using TObjectId = TGuid;
+
+//! The all-zero id used to denote a non-existing object.
+constexpr TObjectId NullObjectId = {};
+
+//! Used to mark counters for well-known ids.
+constexpr ui64 WellKnownCounterMask = 0x8000000000000000;
+
+//! Used to mark counters for Sequoia objects.
+constexpr ui64 SequoiaCounterMask = 0x4000000000000000;
+
+using NElection::TCellId;
+using NElection::NullCellId;
+
+//! Identifies a particular cell of YT cluster.
+//! Must be globally unique to prevent object ids from colliding.
+YT_DEFINE_STRONG_TYPEDEF(TCellTag, ui16)
+
+//! The minimum valid cell tag.
+constexpr auto MinValidCellTag = TCellTag(0x0001);
+
+//! The maximum valid cell tag.
+constexpr auto MaxValidCellTag = TCellTag(0xf000);
+
+//! A sentinel cell tag indicating that the request does not need replication.
+constexpr auto NotReplicatedCellTagSentinel = TCellTag(0xf001);
+
+//! A sentinel cell tag representing the primary master.
+constexpr auto PrimaryMasterCellTagSentinel = TCellTag(0xf003);
+
+//! A sentinel cell tag meaning nothing.
+constexpr auto InvalidCellTag = TCellTag(0xf004);
+
+//! A static limit for the number of secondary master cells.
+constexpr int MaxSecondaryMasterCells = 48;
+
+using TCellTagList = TCompactVector<TCellTag, MaxSecondaryMasterCells + 1>;
+using TCellIdList = TCompactVector<TCellId, MaxSecondaryMasterCells + 1>;
+
+//! Currently at most one additional path is expected (source paths for Copy and Move verbs).
+constexpr int TypicalAdditionalPathCount = 1;
+
+//! Describes the runtime type of an object.
+DEFINE_ENUM(EObjectType,
+ // Does not represent any actual type.
+ ((Null) (0))
+
+ // The following represent non-versioned objects.
+ // These must be created by calling TMasterYPathProxy::CreateObjects.
+
+ // Transaction Manager stuff
+ ((Transaction) ( 1))
+ ((AtomicTabletTransaction) ( 2))
+ ((NonAtomicTabletTransaction) ( 3))
+ ((NestedTransaction) ( 4))
+ ((ExternalizedTransaction) ( 5))
+ ((ExternalizedNestedTransaction) ( 6))
+ ((UploadTransaction) ( 7))
+ ((UploadNestedTransaction) ( 8))
+ ((TransactionMap) (407))
+ ((TopmostTransactionMap) (418))
+ ((LockMap) (422))
+
+ // Chunk Manager stuff
+ ((Chunk) (100))
+ ((ErasureChunk) (102)) // erasure chunk as a whole
+ ((ErasureChunkPart_0) (103)) // erasure chunk parts, mnemonic names are for debugging convenience only
+ ((ErasureChunkPart_1) (104))
+ ((ErasureChunkPart_2) (105))
+ ((ErasureChunkPart_3) (106))
+ ((ErasureChunkPart_4) (107))
+ ((ErasureChunkPart_5) (108))
+ ((ErasureChunkPart_6) (109))
+ ((ErasureChunkPart_7) (110))
+ ((ErasureChunkPart_8) (111))
+ ((ErasureChunkPart_9) (112))
+ ((ErasureChunkPart_10) (113))
+ ((ErasureChunkPart_11) (114))
+ ((ErasureChunkPart_12) (115))
+ ((ErasureChunkPart_13) (116))
+ ((ErasureChunkPart_14) (117))
+ ((ErasureChunkPart_15) (118))
+ ((JournalChunk) (119))
+ ((Artifact) (121))
+ ((ChunkMap) (402))
+ ((LostChunkMap) (403))
+ ((LostVitalChunkMap) (413))
+ ((PrecariousChunkMap) (410))
+ ((PrecariousVitalChunkMap) (411))
+ ((OverreplicatedChunkMap) (404))
+ ((UnderreplicatedChunkMap) (405))
+ ((DataMissingChunkMap) (419))
+ ((ParityMissingChunkMap) (420))
+ ((OldestPartMissingChunkMap) (428))
+ ((QuorumMissingChunkMap) (424))
+ ((UnsafelyPlacedChunkMap) (120))
+ ((InconsistentlyPlacedChunkMap) (160))
+ ((UnexpectedOverreplicatedChunkMap) (434))
+ ((ReplicaTemporarilyUnavailableChunkMap) (436))
+ ((ForeignChunkMap) (122))
+ ((LocalLostChunkMap) (450))
+ ((LocalLostVitalChunkMap) (451))
+ ((LocalPrecariousChunkMap) (452))
+ ((LocalPrecariousVitalChunkMap) (453))
+ ((LocalOverreplicatedChunkMap) (454))
+ ((LocalUnderreplicatedChunkMap) (455))
+ ((LocalDataMissingChunkMap) (456))
+ ((LocalParityMissingChunkMap) (457))
+ ((LocalOldestPartMissingChunkMap) (458))
+ ((LocalQuorumMissingChunkMap) (459))
+ ((LocalUnsafelyPlacedChunkMap) (460))
+ ((LocalInconsistentlyPlacedChunkMap) (461))
+ ((LocalUnexpectedOverreplicatedChunkMap) (462))
+ ((LocalReplicaTemporarilyUnavailableChunkMap) (463))
+ ((ChunkList) (101))
+ ((ChunkListMap) (406))
+ ((ChunkView) (123))
+ ((ChunkViewMap) (430))
+ ((DomesticMedium) (408))
+ ((S3Medium) (435))
+ ((MediumMap) (409))
+ ((ErasureJournalChunk) (124)) // erasure journal chunk as a whole
+ ((ErasureJournalChunkPart_0) (125)) // erasure chunk parts, mnemonic names are for debugging convenience only
+ ((ErasureJournalChunkPart_1) (126))
+ ((ErasureJournalChunkPart_2) (127))
+ ((ErasureJournalChunkPart_3) (128))
+ ((ErasureJournalChunkPart_4) (129))
+ ((ErasureJournalChunkPart_5) (130))
+ ((ErasureJournalChunkPart_6) (131))
+ ((ErasureJournalChunkPart_7) (132))
+ ((ErasureJournalChunkPart_8) (133))
+ ((ErasureJournalChunkPart_9) (134))
+ ((ErasureJournalChunkPart_10) (135))
+ ((ErasureJournalChunkPart_11) (136))
+ ((ErasureJournalChunkPart_12) (137))
+ ((ErasureJournalChunkPart_13) (138))
+ ((ErasureJournalChunkPart_14) (139))
+ ((ErasureJournalChunkPart_15) (140))
+ ((ChunkLocation) (141))
+ ((ChunkLocationMap) (142))
+
+ // The following represent versioned objects (AKA Cypress nodes).
+ // These must be created by calling TCypressYPathProxy::Create.
+ // NB: When adding a new type, don't forget to update IsVersionedType.
+
+ // Auxiliary
+ ((Lock) (200))
+
+ // Static nodes
+ ((StringNode) (300))
+ ((Int64Node) (301))
+ ((Uint64Node) (306))
+ ((DoubleNode) (302))
+ ((MapNode) (303))
+ ((ListNode) (304))
+ ((BooleanNode) (305))
+
+ // Dynamic nodes
+ ((File) (400))
+ ((Table) (401))
+ ((Journal) (423))
+ ((Orchid) (412))
+ ((Link) (417))
+ ((Document) (421))
+ ((ReplicatedTable) (425))
+ ((ReplicationLogTable) (431))
+
+ ((AccessControlObject) (307))
+ ((AccessControlObjectNamespace) (432))
+ ((AccessControlObjectNamespaceMap) (433))
+
+ // Cypress shards
+ ((CypressShard) (11004))
+ ((CypressShardMap) (11005))
+
+ // Portals
+ ((PortalEntrance) (11000))
+ ((PortalExit) (11001))
+ ((PortalEntranceMap) (11002))
+ ((PortalExitMap) (11003))
+
+ // Grafting
+ ((Rootstock) (12000))
+ ((Scion) (12001))
+ ((RootstockMap) (12002))
+ ((ScionMap) (12003))
+
+ // Security Manager stuff
+ ((Account) (500))
+ ((AccountMap) (414))
+ ((AccountResourceUsageLease) (505))
+ ((AccountResourceUsageLeaseMap) (506))
+ ((User) (501))
+ ((UserMap) (415))
+ ((Group) (502))
+ ((GroupMap) (416))
+ ((NetworkProject) (503))
+ ((NetworkProjectMap) (426))
+ ((ProxyRole) (504))
+ ((HttpProxyRoleMap) (427))
+ ((RpcProxyRoleMap) (429))
+
+ // Table Manager stuff
+ ((MasterTableSchema) (1300))
+ ((MasterTableSchemaMap) (1301))
+ ((TableCollocation) (1302))
+
+ // Global stuff
+ // A mysterious creature representing the master as a whole.
+ ((Master) (600))
+ ((MasterCell) (601))
+ ((SysNode) (602))
+ // Next two types would end with 'a' and 'b' in hex.
+ ((AliceAvenueEndpoint) (618))
+ ((BobAvenueEndpoint) (619))
+
+ // Tablet Manager stuff
+ ((TabletCell) (700))
+ ((TabletCellNode) (701))
+ ((Tablet) (702))
+ ((TabletMap) (703))
+ ((TabletCellMap) (710))
+ ((SortedDynamicTabletStore) (704))
+ ((OrderedDynamicTabletStore) (708))
+ ((TabletPartition) (705))
+ ((TabletCellBundle) (706))
+ ((TabletCellBundleMap) (707))
+ ((TableReplica) (709))
+ ((TabletAction) (711))
+ ((TabletActionMap) (712))
+ ((Area) (713))
+ ((AreaMap) (714))
+ ((HunkStorage) (715))
+ ((HunkTablet) (716))
+
+ // Node Tracker stuff
+ ((Rack) (800))
+ ((RackMap) (801))
+ ((ClusterNode) (802))
+ ((ClusterNodeNode) (803))
+ ((LegacyClusterNodeMap) (804))
+ ((ClusterNodeMap) (807))
+ ((DataNodeMap) (808))
+ ((ExecNodeMap) (809))
+ ((TabletNodeMap) (810))
+ ((ChaosNodeMap) (811))
+ ((DataCenter) (805))
+ ((DataCenterMap) (806))
+ ((Host) (812))
+ ((HostMap) (813))
+
+ // Job Tracker stuff
+ ((SchedulerJob) (900))
+ ((MasterJob) (901))
+
+ // Scheduler
+ ((Operation) (1000))
+ ((SchedulerPool) (1001))
+ ((SchedulerPoolTree) (1002))
+ ((SchedulerPoolTreeMap) (1003))
+
+ // Object manager stuff
+ ((EstimatedCreationTimeMap) (1100))
+
+ // Chaos stuff
+ ((ChaosCell) (1200))
+ ((ChaosCellBundle) (1201))
+ ((ChaosCellMap) (1202))
+ ((ChaosCellBundleMap) (1203))
+ ((ReplicationCard) (1204))
+ ((ChaosTableReplica) (1205))
+ ((ChaosReplicatedTable) (1206))
+ ((ReplicationCardCollocation) (1207))
+
+ // Zookeeper stuff
+ ((ZookeeperShard) (1400))
+ ((ZookeeperShardMap) (1401))
+);
+
+//! A bit mask marking schema types.
+constexpr ui32 SchemaObjectTypeMask = 0x8000;
+
+// The range of erasure chunk part types.
+constexpr EObjectType MinErasureChunkPartType = EObjectType::ErasureChunkPart_0;
+constexpr EObjectType MaxErasureChunkPartType = EObjectType::ErasureChunkPart_15;
+
+// The range of erasure journal chunk part types.
+constexpr EObjectType MinErasureJournalChunkPartType = EObjectType::ErasureJournalChunkPart_0;
+constexpr EObjectType MaxErasureJournalChunkPartType = EObjectType::ErasureJournalChunkPart_15;
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TTransactionId = TObjectId;
+constexpr TTransactionId NullTransactionId = {};
+
+using TOperationId = TObjectId;
+using TJobId = TObjectId;
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Identifies a node possibly branched by a transaction.
+struct TVersionedObjectId
+{
+ //! Id of the node itself.
+ TObjectId ObjectId;
+
+ //! Id of the transaction that had branched the node.
+ //! #NullTransactionId if the node is not branched.
+ TTransactionId TransactionId;
+
+ //! Initializes a null instance.
+ /*!
+ * #NodeId is #NullObjectId, #TransactionId is #NullTransactionId.
+ */
+ TVersionedObjectId() = default;
+
+ //! Initializes an instance by given node. Sets #TransactionId to #NullTransactionId.
+ explicit TVersionedObjectId(TObjectId objectId);
+
+ //! Initializes an instance by given node and transaction ids.
+ TVersionedObjectId(TObjectId objectId, TTransactionId transactionId);
+
+ //! Checks that the id is branched, i.e. #TransactionId is not #NullTransactionId.
+ bool IsBranched() const;
+
+
+ static TVersionedObjectId FromString(TStringBuf str);
+};
+
+//! Formats id into a string (for debugging and logging purposes mainly).
+void FormatValue(TStringBuilderBase* builder, const TVersionedObjectId& id, TStringBuf spec);
+
+//! Converts id into a string (for debugging and logging purposes mainly).
+TString ToString(const TVersionedObjectId& id);
+
+//! Compares TVersionedNodeId s for equality.
+bool operator == (const TVersionedObjectId& lhs, const TVersionedObjectId& rhs);
+
+//! Compares TVersionedNodeId s for inequality.
+bool operator != (const TVersionedObjectId& lhs, const TVersionedObjectId& rhs);
+
+//! Compares TVersionedNodeId s for "less than".
+bool operator < (const TVersionedObjectId& lhs, const TVersionedObjectId& rhs);
+
+class TObjectServiceProxy;
+
+struct TDirectObjectIdHash;
+struct TDirectVersionedObjectIdHash;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NObjectClient
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <>
+struct THash<NYT::NObjectClient::TVersionedObjectId>
+{
+ size_t operator()(const NYT::NObjectClient::TVersionedObjectId& id) const
+ {
+ auto result = THash<NYT::NObjectClient::TObjectId>()(id.ObjectId);
+ HashCombine(result, id.TransactionId);
+ return result;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_DECLARE_PODTYPE(NYT::NObjectClient::TVersionedObjectId);
diff --git a/yt/yt/client/query_client/public.h b/yt/yt/client/query_client/public.h
new file mode 100644
index 0000000000..260e4d9224
--- /dev/null
+++ b/yt/yt/client/query_client/public.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NQueryClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+class TQueryStatistics;
+
+} // namespace NProto
+
+struct TQueryStatistics;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueryClient
+
diff --git a/yt/yt/client/query_client/query_builder.cpp b/yt/yt/client/query_client/query_builder.cpp
new file mode 100644
index 0000000000..b6658e1076
--- /dev/null
+++ b/yt/yt/client/query_client/query_builder.cpp
@@ -0,0 +1,159 @@
+#include "query_builder.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <util/string/join.h>
+#include <util/stream/str.h>
+
+namespace NYT::NQueryClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static std::vector<TString> Parenthesize(std::vector<TString> strings)
+{
+ for (auto& string : strings) {
+ string.prepend('(').append(')');
+ }
+ return strings;
+}
+
+void TQueryBuilder::SetSource(TString source)
+{
+ Source_ = std::move(source);
+}
+
+int TQueryBuilder::AddSelectExpression(TString expression)
+{
+ SelectEntries_.push_back(TEntryWithAlias{
+ std::move(expression),
+ std::nullopt,
+ });
+ return SelectEntries_.size() - 1;
+}
+
+int TQueryBuilder::AddSelectExpression(TString expression, TString alias)
+{
+ SelectEntries_.push_back(TEntryWithAlias{
+ std::move(expression),
+ std::move(alias),
+ });
+ return SelectEntries_.size() - 1;
+}
+
+void TQueryBuilder::AddWhereConjunct(TString expression)
+{
+ WhereConjuncts_.push_back(std::move(expression));
+}
+
+void TQueryBuilder::AddGroupByExpression(TString expression)
+{
+ GroupByEntries_.push_back(TEntryWithAlias{
+ std::move(expression),
+ std::nullopt
+ });
+}
+
+void TQueryBuilder::AddGroupByExpression(TString expression, TString alias)
+{
+ GroupByEntries_.push_back(TEntryWithAlias{
+ std::move(expression),
+ std::move(alias),
+ });
+}
+
+void TQueryBuilder::AddOrderByExpression(TString expression)
+{
+ OrderByEntries_.push_back(TOrderByEntry{
+ std::move(expression),
+ std::nullopt,
+ });
+}
+
+void TQueryBuilder::AddOrderByExpression(TString expression, std::optional<EOrderByDirection> direction)
+{
+ OrderByEntries_.push_back(TOrderByEntry{
+ std::move(expression),
+ direction,
+ });
+}
+
+void TQueryBuilder::AddOrderByAscendingExpression(TString expression)
+{
+ AddOrderByExpression(std::move(expression), EOrderByDirection::Ascending);
+}
+
+void TQueryBuilder::AddOrderByDescendingExpression(TString expression)
+{
+ AddOrderByExpression(std::move(expression), EOrderByDirection::Descending);
+}
+
+void TQueryBuilder::SetLimit(i64 limit)
+{
+ Limit_ = limit;
+}
+
+TString TQueryBuilder::Build()
+{
+ std::vector<TString> parts;
+ parts.reserve(8);
+
+ if (SelectEntries_.empty()) {
+ THROW_ERROR_EXCEPTION("Query must have at least one SELECT expression");
+ }
+ parts.push_back(JoinSeq(", ", SelectEntries_));
+
+ if (!Source_) {
+ THROW_ERROR_EXCEPTION("Source must be specified in query");
+ }
+ parts.push_back(Format("FROM [%v]", *Source_));
+
+ if (!WhereConjuncts_.empty()) {
+ parts.push_back("WHERE");
+ parts.push_back(JoinSeq(" AND ", Parenthesize(WhereConjuncts_)));
+ }
+
+ if (!OrderByEntries_.empty()) {
+ parts.push_back("ORDER BY");
+ parts.push_back(JoinSeq(", ", OrderByEntries_));
+ }
+
+ if (!GroupByEntries_.empty()) {
+ parts.push_back("GROUP BY");
+ parts.push_back(JoinSeq(", ", GroupByEntries_));
+ }
+
+ if (Limit_) {
+ parts.push_back(Format("LIMIT %v", *Limit_));
+ }
+
+ return JoinSeq(" ", parts);
+}
+
+void AppendToString(TString& dst, const TQueryBuilder::TEntryWithAlias& entry)
+{
+ TStringOutput output(dst);
+ if (entry.Expression == "*") {
+ output << "*";
+ return;
+ }
+ output << '(' << entry.Expression << ')';
+ if (entry.Alias) {
+ output << " AS " << *entry.Alias;
+ }
+}
+
+void AppendToString(TString& dst, const TQueryBuilder::TOrderByEntry& entry)
+{
+ TStringOutput output(dst);
+ output << '(' << entry.Expression << ')';
+ if (entry.Direction) {
+ TStringBuf directionString = (*entry.Direction == EOrderByDirection::Ascending)
+ ? "ASC"
+ : "DESC";
+ output << ' ' << directionString;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueryClient
diff --git a/yt/yt/client/query_client/query_builder.h b/yt/yt/client/query_client/query_builder.h
new file mode 100644
index 0000000000..1548e749a2
--- /dev/null
+++ b/yt/yt/client/query_client/query_builder.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include "public.h"
+
+#include <util/generic/string.h>
+
+namespace NYT::NQueryClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EOrderByDirection,
+ (Ascending)
+ (Descending)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TQueryBuilder
+{
+public:
+ void SetSource(TString source);
+
+ int AddSelectExpression(TString expression);
+ int AddSelectExpression(TString expression, TString alias);
+
+ void AddWhereConjunct(TString expression);
+
+ void AddGroupByExpression(TString expression);
+ void AddGroupByExpression(TString expression, TString alias);
+
+ void AddOrderByExpression(TString expression);
+ void AddOrderByExpression(TString expression, std::optional<EOrderByDirection> direction);
+
+ void AddOrderByAscendingExpression(TString expression);
+ void AddOrderByDescendingExpression(TString expression);
+
+ void SetLimit(i64 limit);
+
+ TString Build();
+
+private:
+ struct TEntryWithAlias
+ {
+ TString Expression;
+ std::optional<TString> Alias;
+ };
+
+ struct TOrderByEntry
+ {
+ TString Expression;
+ std::optional<EOrderByDirection> Direction;
+ };
+
+private:
+ std::optional<TString> Source_;
+ std::vector<TEntryWithAlias> SelectEntries_;
+ std::vector<TString> WhereConjuncts_;
+ std::vector<TOrderByEntry> OrderByEntries_;
+ std::vector<TEntryWithAlias> GroupByEntries_;
+ std::optional<i64> Limit_;
+
+private:
+ // We overload this functions to allow the corresponding JoinSeq().
+ friend void AppendToString(TString& dst, const TEntryWithAlias& entry);
+ friend void AppendToString(TString& dst, const TOrderByEntry& entry);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueryClient
diff --git a/yt/yt/client/query_client/query_statistics.cpp b/yt/yt/client/query_client/query_statistics.cpp
new file mode 100644
index 0000000000..ac9e1349ef
--- /dev/null
+++ b/yt/yt/client/query_client/query_statistics.cpp
@@ -0,0 +1,117 @@
+#include "query_statistics.h"
+
+#include <yt/yt_proto/yt/client/query_client/proto/query_statistics.pb.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NQueryClient {
+
+using NYT::ToProto;
+using NYT::FromProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TQueryStatistics::AddInnerStatistics(const TQueryStatistics& statistics)
+{
+ InnerStatistics.push_back(statistics);
+ IncompleteInput |= statistics.IncompleteInput;
+ IncompleteOutput |= statistics.IncompleteOutput;
+ MemoryUsage = std::max(MemoryUsage, statistics.MemoryUsage);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(NProto::TQueryStatistics* serialized, const TQueryStatistics& original)
+{
+ serialized->set_rows_read(original.RowsRead);
+ serialized->set_data_weight_read(original.DataWeightRead);
+ serialized->set_rows_written(original.RowsWritten);
+ serialized->set_sync_time(ToProto<i64>(original.SyncTime));
+ serialized->set_async_time(ToProto<i64>(original.AsyncTime));
+ serialized->set_execute_time(ToProto<i64>(original.ExecuteTime));
+ serialized->set_read_time(ToProto<i64>(original.ReadTime));
+ serialized->set_write_time(ToProto<i64>(original.WriteTime));
+ serialized->set_codegen_time(ToProto<i64>(original.CodegenTime));
+ serialized->set_wait_on_ready_event_time(ToProto<i64>(original.WaitOnReadyEventTime));
+ serialized->set_incomplete_input(original.IncompleteInput);
+ serialized->set_incomplete_output(original.IncompleteOutput);
+ serialized->set_memory_usage(original.MemoryUsage);
+ ToProto(serialized->mutable_inner_statistics(), original.InnerStatistics);
+}
+
+void FromProto(TQueryStatistics* original, const NProto::TQueryStatistics& serialized)
+{
+ original->RowsRead = serialized.rows_read();
+ original->DataWeightRead = serialized.data_weight_read();
+ original->RowsWritten = serialized.rows_written();
+ original->SyncTime = FromProto<TDuration>(serialized.sync_time());
+ original->AsyncTime = FromProto<TDuration>(serialized.async_time());
+ original->ExecuteTime = FromProto<TDuration>(serialized.execute_time());
+ original->ReadTime = FromProto<TDuration>(serialized.read_time());
+ original->WriteTime = FromProto<TDuration>(serialized.write_time());
+ original->CodegenTime = FromProto<TDuration>(serialized.codegen_time());
+ original->WaitOnReadyEventTime = FromProto<TDuration>(serialized.wait_on_ready_event_time());
+ original->IncompleteInput = serialized.incomplete_input();
+ original->IncompleteOutput = serialized.incomplete_output();
+ original->MemoryUsage = serialized.memory_usage();
+ FromProto(&original->InnerStatistics, serialized.inner_statistics());
+}
+
+TString ToString(const TQueryStatistics& stats)
+{
+ return Format(
+ "{"
+ "RowsRead: %v, DataWeightRead: %v, RowsWritten: %v, "
+ "SyncTime: %v, AsyncTime: %v, ExecuteTime: %v, ReadTime: %v, WriteTime: %v, CodegenTime: %v, "
+ "WaitOnReadyEventTime: %v, IncompleteInput: %v, IncompleteOutput: %v, MemoryUsage: %v"
+ "}",
+ stats.RowsRead,
+ stats.DataWeightRead,
+ stats.RowsWritten,
+ stats.SyncTime,
+ stats.AsyncTime,
+ stats.ExecuteTime,
+ stats.ReadTime,
+ stats.WriteTime,
+ stats.CodegenTime,
+ stats.WaitOnReadyEventTime,
+ stats.IncompleteInput,
+ stats.IncompleteOutput,
+ stats.MemoryUsage);
+}
+
+void Serialize(const TQueryStatistics& statistics, NYson::IYsonConsumer* consumer)
+{
+ NYTree::BuildYsonMapFragmentFluently(consumer)
+ .Item("rows_read").Value(statistics.RowsRead)
+ .Item("data_weight_read").Value(statistics.DataWeightRead)
+ .Item("rows_written").Value(statistics.RowsWritten)
+ .Item("sync_time").Value(statistics.SyncTime.MilliSeconds())
+ .Item("async_time").Value(statistics.AsyncTime.MilliSeconds())
+ .Item("execute_time").Value(statistics.ExecuteTime.MilliSeconds())
+ .Item("read_time").Value(statistics.ReadTime.MilliSeconds())
+ .Item("write_time").Value(statistics.WriteTime.MilliSeconds())
+ .Item("codegen_time").Value(statistics.CodegenTime.MilliSeconds())
+ .Item("wait_on_ready_event_time").Value(statistics.WaitOnReadyEventTime.MilliSeconds())
+ .Item("incomplete_input").Value(statistics.IncompleteInput)
+ .Item("incomplete_output").Value(statistics.IncompleteOutput)
+ .Item("memory_usage").Value(statistics.MemoryUsage)
+ .DoIf(!statistics.InnerStatistics.empty(), [&] (NYTree::TFluentMap fluent) {
+ fluent
+ .Item("inner_statistics").DoListFor(statistics.InnerStatistics, [=] (
+ NYTree::TFluentList fluent,
+ const TQueryStatistics& statistics)
+ {
+ fluent
+ .Item().DoMap([&] (NYTree::TFluentMap fluent) {
+ Serialize(statistics, fluent.GetConsumer());
+ });
+ });
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueryClient
diff --git a/yt/yt/client/query_client/query_statistics.h b/yt/yt/client/query_client/query_statistics.h
new file mode 100644
index 0000000000..daf81cddf0
--- /dev/null
+++ b/yt/yt/client/query_client/query_statistics.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/public.h>
+
+namespace NYT::NQueryClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TQueryStatistics
+{
+ i64 RowsRead = 0;
+ i64 DataWeightRead = 0;
+ i64 RowsWritten = 0;
+ TDuration SyncTime;
+ TDuration AsyncTime;
+ TDuration ExecuteTime;
+ TDuration ReadTime;
+ TDuration WriteTime;
+ TDuration CodegenTime;
+ TDuration WaitOnReadyEventTime;
+ bool IncompleteInput = false;
+ bool IncompleteOutput = false;
+ size_t MemoryUsage = 0;
+
+ std::vector<TQueryStatistics> InnerStatistics;
+
+ void AddInnerStatistics(const TQueryStatistics& statistics);
+};
+
+void ToProto(NProto::TQueryStatistics* serialized, const TQueryStatistics& original);
+void FromProto(TQueryStatistics* original, const NProto::TQueryStatistics& serialized);
+
+TString ToString(const TQueryStatistics& stat);
+
+void Serialize(const TQueryStatistics& statistics, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueryClient
diff --git a/yt/yt/client/query_tracker_client/CMakeLists.linux-aarch64.txt b/yt/yt/client/query_tracker_client/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..776ef69ad9
--- /dev/null
+++ b/yt/yt/client/query_tracker_client/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-client-query_tracker_client)
+target_compile_options(yt-client-query_tracker_client PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-client-query_tracker_client PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+)
+target_sources(yt-client-query_tracker_client PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/query_tracker_client/public.cpp
+)
diff --git a/yt/yt/client/query_tracker_client/CMakeLists.linux-x86_64.txt b/yt/yt/client/query_tracker_client/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..776ef69ad9
--- /dev/null
+++ b/yt/yt/client/query_tracker_client/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-client-query_tracker_client)
+target_compile_options(yt-client-query_tracker_client PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-client-query_tracker_client PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+)
+target_sources(yt-client-query_tracker_client PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/client/query_tracker_client/public.cpp
+)
diff --git a/yt/yt/client/query_tracker_client/CMakeLists.txt b/yt/yt/client/query_tracker_client/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/client/query_tracker_client/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/client/query_tracker_client/public.cpp b/yt/yt/client/query_tracker_client/public.cpp
new file mode 100644
index 0000000000..4df9bcaa18
--- /dev/null
+++ b/yt/yt/client/query_tracker_client/public.cpp
@@ -0,0 +1 @@
+#include "public.h"
diff --git a/yt/yt/client/query_tracker_client/public.h b/yt/yt/client/query_tracker_client/public.h
new file mode 100644
index 0000000000..56c3660c5b
--- /dev/null
+++ b/yt/yt/client/query_tracker_client/public.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <yt/yt/core/misc/guid.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+namespace NYT::NQueryTrackerClient {
+
+///////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((IncarnationMismatch) (3900))
+ ((QueryNotFound) (3901))
+ ((QueryResultNotFound) (3902))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TQueryId = TGuid;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_STRING_SERIALIZABLE_ENUM(EQueryEngine,
+ (Ql)
+ (Yql)
+ (Chyt)
+ (Mock)
+);
+
+DEFINE_STRING_SERIALIZABLE_ENUM(EQueryState,
+ (Draft)
+ (Pending)
+ (Running)
+ (Aborting)
+ (Aborted)
+ (Completing)
+ (Completed)
+ (Failing)
+ (Failed)
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueryTrackerClient
diff --git a/yt/yt/client/query_tracker_client/ya.make b/yt/yt/client/query_tracker_client/ya.make
new file mode 100644
index 0000000000..bf27067fd3
--- /dev/null
+++ b/yt/yt/client/query_tracker_client/ya.make
@@ -0,0 +1,13 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ public.cpp
+)
+
+PEERDIR(
+ yt/yt/core
+)
+
+END()
diff --git a/yt/yt/client/queue_client/common.cpp b/yt/yt/client/queue_client/common.cpp
new file mode 100644
index 0000000000..d65aeca7cd
--- /dev/null
+++ b/yt/yt/client/queue_client/common.cpp
@@ -0,0 +1,85 @@
+#include "common.h"
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <util/string/split.h>
+
+namespace NYT::NQueueClient {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NYPath;
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TCrossClusterReference::operator==(const TCrossClusterReference& other) const
+{
+ return Cluster == other.Cluster && Path == other.Path;
+}
+
+bool TCrossClusterReference::operator<(const TCrossClusterReference& other) const
+{
+ return std::tie(Cluster, Path) < std::tie(other.Cluster, other.Path);
+}
+
+TCrossClusterReference::operator TRichYPath() const
+{
+ TRichYPath result = Path;
+ result.SetCluster(Cluster);
+ return result;
+}
+
+TCrossClusterReference TCrossClusterReference::FromString(TStringBuf path)
+{
+ TCrossClusterReference result;
+ if (!StringSplitter(path).Split(':').Limit(2).TryCollectInto(&result.Cluster, &result.Path)) {
+ THROW_ERROR_EXCEPTION("Ill-formed cross-cluster reference %Qv", path);
+ }
+ if (result.Cluster.empty()) {
+ THROW_ERROR_EXCEPTION("The cluster component of cross-cluster reference %Qv cannot be empty", path);
+ }
+ if (result.Path.empty()) {
+ THROW_ERROR_EXCEPTION("The path component of cross-cluster reference %Qv cannot be empty", path);
+ }
+ return result;
+}
+
+TCrossClusterReference TCrossClusterReference::FromRichYPath(const TRichYPath& path)
+{
+ auto cluster = path.GetCluster();
+ if (!cluster) {
+ THROW_ERROR_EXCEPTION(
+ "Attempting to build cross-cluster reference from rich YPath %Qv without a cluster set",
+ path);
+ }
+ return {
+ .Cluster = *cluster,
+ .Path = path.GetPath(),
+ };
+}
+
+TString ToString(const TCrossClusterReference& queueRef)
+{
+ return Format("%v:%v", queueRef.Cluster, queueRef.Path);
+}
+
+void Serialize(const TCrossClusterReference& queueRef, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer).Value(ToString(queueRef));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
+
+size_t THash<NYT::NQueueClient::TCrossClusterReference>::operator()(const NYT::NQueueClient::TCrossClusterReference& crossClusterRef) const
+{
+ using NYT::HashCombine;
+
+ size_t result = 0;
+ HashCombine(result, crossClusterRef.Cluster);
+ HashCombine(result, crossClusterRef.Path);
+ return result;
+}
diff --git a/yt/yt/client/queue_client/common.h b/yt/yt/client/queue_client/common.h
new file mode 100644
index 0000000000..e9dc381311
--- /dev/null
+++ b/yt/yt/client/queue_client/common.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <yt/yt/core/misc/common.h>
+
+#include <yt/yt/core/ypath/public.h>
+
+#include <yt/yt/core/yson/public.h>
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TODO(achulkov2): Replace this with TRichYPath in YT-18038.
+struct TCrossClusterReference
+{
+ TString Cluster;
+ NYPath::TYPath Path;
+
+ bool operator ==(const TCrossClusterReference& other) const;
+ bool operator <(const TCrossClusterReference& other) const;
+
+ operator NYPath::TRichYPath() const;
+
+ static TCrossClusterReference FromString(TStringBuf path);
+ static TCrossClusterReference FromRichYPath(const NYPath::TRichYPath& path);
+};
+
+TString ToString(const TCrossClusterReference& queueRef);
+
+void Serialize(const TCrossClusterReference& queueRef, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
+
+template <>
+struct THash<NYT::NQueueClient::TCrossClusterReference>
+{
+ size_t operator()(const NYT::NQueueClient::TCrossClusterReference& crossClusterRef) const;
+};
diff --git a/yt/yt/client/queue_client/config.cpp b/yt/yt/client/queue_client/config.cpp
new file mode 100644
index 0000000000..0f7354f096
--- /dev/null
+++ b/yt/yt/client/queue_client/config.cpp
@@ -0,0 +1,62 @@
+#include "config.h"
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TPartitionReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_row_count", &TThis::MaxRowCount)
+ .Default(1000);
+ registrar.Parameter("max_data_weight", &TThis::MaxDataWeight)
+ .Default(16_MB);
+ registrar.Parameter("data_weight_per_row_hint", &TThis::DataWeightPerRowHint)
+ .Default();
+
+ registrar.Parameter("use_native_tablet_node_api", &TThis::UseNativeTabletNodeApi)
+ .Default(false);
+ registrar.Parameter("use_pull_consumer", &TThis::UsePullConsumer)
+ .Default(false);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->UsePullConsumer && !config->UseNativeTabletNodeApi) {
+ THROW_ERROR_EXCEPTION("PullConsumer can only be used with the native tablet node api for pulling rows");
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TQueueAutoTrimConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable", &TThis::Enable)
+ .Default(false);
+ registrar.Parameter("retained_rows", &TThis::RetainedRows)
+ .Default();
+ registrar.Parameter("retained_lifetime_duration", &TThis::RetainedLifetimeDuration)
+ .Default();
+
+ registrar.Postprocessor([] (TThis* trimConfig) {
+ if (!trimConfig->Enable) {
+ if (trimConfig->RetainedLifetimeDuration) {
+ THROW_ERROR_EXCEPTION("Autotrimming is disabled, option \"retained_lifetime_duration\" can only be used while autotrimming is enabled");
+ }
+ if (trimConfig->RetainedRows) {
+ THROW_ERROR_EXCEPTION("Autotrimming is disabled, option \"retained_rows\" can only be used while autotrimming is enabled");
+ }
+ }
+
+ if (trimConfig->RetainedLifetimeDuration && trimConfig->RetainedLifetimeDuration->GetValue() % TDuration::Seconds(1).GetValue() != 0) {
+ THROW_ERROR_EXCEPTION("The value of \"retained_lifetime_duration\" must be a multiple of 1000 (1 second)");
+ }
+ });
+}
+
+bool operator==(const TQueueAutoTrimConfig& lhs, const TQueueAutoTrimConfig& rhs)
+{
+ return lhs.Enable == rhs.Enable && lhs.RetainedRows == rhs.RetainedRows;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/config.h b/yt/yt/client/queue_client/config.h
new file mode 100644
index 0000000000..53c6a2dce0
--- /dev/null
+++ b/yt/yt/client/queue_client/config.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPartitionReaderConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ i64 MaxRowCount;
+ i64 MaxDataWeight;
+
+ //! If set, this value is used to compute the number of rows to read considering the given MaxDataWeight.
+ std::optional<i64> DataWeightPerRowHint;
+
+ bool UseNativeTabletNodeApi;
+ bool UsePullConsumer;
+
+ REGISTER_YSON_STRUCT(TPartitionReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TPartitionReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+//! Automatic trimming configuration for a single queue.
+//!
+//! All rows up to the smallest offset among vital consumers are considered trimmable.
+//! If no other setting in the config explicitly prohibits trimming these rows,
+//! they will be trimmed automatically by the responsible queue agent.
+//! This is not applicable if no vital consumers exist for a queue.
+// TODO(achulkov2): Add example of how multiple vital/non-vital consumers and the options below interact.
+class TQueueAutoTrimConfig
+ : public NYTree::TYsonStructLite
+{
+public:
+ //! If set to false, no automatic trimming is performed.
+ bool Enable;
+
+ //! If set, this number of rows is guaranteed to be kept in each partition.
+ std::optional<i64> RetainedRows;
+
+ //! If set, rows, that were created no more than this time ago, will be kept in each partition.
+ std::optional<TDuration> RetainedLifetimeDuration;
+
+ REGISTER_YSON_STRUCT_LITE(TQueueAutoTrimConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+bool operator==(const TQueueAutoTrimConfig& lhs, const TQueueAutoTrimConfig& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/consumer_client.cpp b/yt/yt/client/queue_client/consumer_client.cpp
new file mode 100644
index 0000000000..6380be7095
--- /dev/null
+++ b/yt/yt/client/queue_client/consumer_client.cpp
@@ -0,0 +1,582 @@
+#include "consumer_client.h"
+#include "private.h"
+
+#include <yt/yt/client/table_client/config.h>
+#include <yt/yt/client/table_client/comparator.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/helpers.h>
+#include <yt/yt/client/table_client/check_schema_compatibility.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+
+#include <yt/yt/client/api/rowset.h>
+#include <yt/yt/client/api/client.h>
+#include <yt/yt/client/api/transaction.h>
+
+#include <yt/yt/client/transaction_client/helpers.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <library/cpp/iterator/functools.h>
+
+#include <util/string/join.h>
+
+namespace NYT::NQueueClient {
+
+using namespace NApi;
+using namespace NConcurrency;
+using namespace NHiveClient;
+using namespace NTableClient;
+using namespace NTabletClient;
+using namespace NTransactionClient;
+using namespace NYPath;
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto& Logger = QueueClientLogger;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const TTableSchemaPtr YTConsumerTableSchema = New<TTableSchema>(std::vector<TColumnSchema>{
+ TColumnSchema("queue_cluster", EValueType::String, ESortOrder::Ascending).SetRequired(true),
+ TColumnSchema("queue_path", EValueType::String, ESortOrder::Ascending).SetRequired(true),
+ TColumnSchema("partition_index", EValueType::Uint64, ESortOrder::Ascending).SetRequired(true),
+ TColumnSchema("offset", EValueType::Uint64).SetRequired(true),
+}, /*strict*/ true, /*uniqueKeys*/ true);
+
+static const TTableSchemaPtr BigRTConsumerTableSchema = New<TTableSchema>(std::vector<TColumnSchema>{
+ TColumnSchema("ShardId", EValueType::Uint64, ESortOrder::Ascending),
+ TColumnSchema("Offset", EValueType::Uint64),
+}, /*strict*/ true, /*uniqueKeys*/ true);
+
+class TGenericConsumerClient
+ : public ISubConsumerClient
+{
+public:
+ TGenericConsumerClient(
+ TYPath path,
+ TUnversionedOwningRow rowPrefix,
+ TStringBuf partitionIndexColumnName,
+ TStringBuf offsetColumnName,
+ bool decrementOffset,
+ const TTableSchemaPtr& tableSchema)
+ : Path_(std::move(path))
+ , RowPrefix_(std::move(rowPrefix))
+ , PartitionIndexColumnName_(std::move(partitionIndexColumnName))
+ , OffsetColumnName_(std::move(offsetColumnName))
+ , TableSchema_(tableSchema)
+ , NameTable_(TNameTable::FromSchema(*TableSchema_))
+ , PartitionIndexColumnId_(NameTable_->GetId(PartitionIndexColumnName_))
+ , OffsetColumnId_(NameTable_->GetId(OffsetColumnName_))
+ , SubConsumerColumnFilter_{PartitionIndexColumnId_, OffsetColumnId_}
+ , DecrementOffset_(decrementOffset)
+ , SubConsumerSchema_(New<TTableSchema>(std::vector<TColumnSchema>{
+ TColumnSchema(TString(PartitionIndexColumnName_), EValueType::Uint64, ESortOrder::Ascending),
+ TColumnSchema(TString(OffsetColumnName_), EValueType::Uint64),
+ }, /*strict*/ true, /*uniqueKeys*/ true))
+ {
+ if (RowPrefix_.GetCount() == 0) {
+ RowPrefixCondition_ = "1 = 1";
+ } else {
+ TStringBuilder builder;
+ builder.AppendChar('(');
+ for (int index = 0; index < RowPrefix_.GetCount(); ++index) {
+ if (index != 0) {
+ builder.AppendString(", ");
+ }
+ builder.AppendFormat("[%v]", TableSchema_->Columns()[index].Name());
+ }
+ builder.AppendString(") = (");
+ for (int index = 0; index < RowPrefix_.GetCount(); ++index) {
+ if (index != 0) {
+ builder.AppendString(", ");
+ }
+ YT_VERIFY(RowPrefix_[index].Type == EValueType::String);
+ builder.AppendFormat("\"%v\"", RowPrefix_[index].AsStringBuf());
+ }
+ builder.AppendChar(')');
+ RowPrefixCondition_ = builder.Flush();
+ }
+ }
+
+ void Advance(
+ const ITransactionPtr& transaction,
+ int partitionIndex,
+ std::optional<i64> oldOffset,
+ i64 newOffset) const override
+ {
+ if (oldOffset) {
+ TUnversionedRowsBuilder keyRowsBuilder;
+ TUnversionedRowBuilder rowBuilder;
+ for (const auto& value : RowPrefix_) {
+ rowBuilder.AddValue(value);
+ }
+ rowBuilder.AddValue(MakeUnversionedUint64Value(partitionIndex, PartitionIndexColumnId_));
+ keyRowsBuilder.AddRow(rowBuilder.GetRow());
+
+ TVersionedLookupRowsOptions options;
+ options.RetentionConfig = New<TRetentionConfig>();
+ options.RetentionConfig->MaxDataVersions = 1;
+
+ auto partitionRowset = WaitFor(transaction->VersionedLookupRows(Path_, NameTable_, keyRowsBuilder.Build(), options))
+ .ValueOrThrow();
+ const auto& rows = partitionRowset->GetRows();
+
+ // XXX(max42): should we use name table from the rowset, or it coincides with our own name table?
+ auto offsetRowsetColumnId = partitionRowset->GetNameTable()->GetIdOrThrow(OffsetColumnName_);
+
+ THROW_ERROR_EXCEPTION_UNLESS(
+ std::ssize(partitionRowset->GetRows()) <= 1,
+ "The table for consumer %Qv should contain at most one row for partition %v when an old offset is specified",
+ Path_,
+ partitionIndex);
+
+ i64 currentOffset = 0;
+ TTimestamp offsetTimestamp = 0;
+ // If the key doesn't exist, or the offset value is null, the offset is -1 in BigRT terms and 0 in ours.
+ if (!rows.empty()) {
+ const auto& offsetValue = rows[0].Values()[0];
+ YT_VERIFY(offsetValue.Id == offsetRowsetColumnId);
+ offsetTimestamp = offsetValue.Timestamp;
+ if (offsetValue.Type != EValueType::Null) {
+ currentOffset = FromUnversionedValue<i64>(offsetValue);
+ if (DecrementOffset_) {
+ // We need to add 1, since BigRT stores the offset of the last read row.
+ ++currentOffset;
+ }
+ }
+
+ YT_LOG_DEBUG(
+ "Read current offset (Consumer: %v, PartitionIndex: %v, Offset: %v, Timestamp: %v)",
+ Path_,
+ partitionIndex,
+ currentOffset,
+ offsetTimestamp);
+ }
+
+ if (currentOffset != *oldOffset) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::ConsumerOffsetConflict,
+ "Offset conflict at partition %v of consumer %v: expected offset %v, found offset %v",
+ partitionIndex,
+ Path_,
+ *oldOffset,
+ currentOffset)
+ << TErrorAttribute("partition", partitionIndex)
+ << TErrorAttribute("consumer", Path_)
+ << TErrorAttribute("expected_offset", *oldOffset)
+ << TErrorAttribute("current_offset", currentOffset)
+ << TErrorAttribute("current_offset_timestamp", offsetTimestamp);
+ }
+ }
+
+ TUnversionedRowsBuilder rowsBuilder;
+ TUnversionedRowBuilder rowBuilder;
+ for (const auto& value : RowPrefix_) {
+ rowBuilder.AddValue(value);
+ }
+ rowBuilder.AddValue(MakeUnversionedUint64Value(partitionIndex, PartitionIndexColumnId_));
+ if (DecrementOffset_) {
+ if (newOffset >= 1) {
+ // We need to subtract 1, since BigRT stores the offset of the last read row.
+ rowBuilder.AddValue(MakeUnversionedUint64Value(newOffset - 1, OffsetColumnId_));
+ } else {
+ // For BigRT consumers we store 0 (in our terms) by storing null.
+ rowBuilder.AddValue(MakeUnversionedNullValue(OffsetColumnId_));
+ }
+ } else {
+ rowBuilder.AddValue(MakeUnversionedUint64Value(newOffset, OffsetColumnId_));
+ }
+ rowsBuilder.AddRow(rowBuilder.GetRow());
+
+ YT_LOG_DEBUG(
+ "Advancing consumer offset (Path: %v, Partition: %v, Offset: %v -> %v)",
+ Path_,
+ partitionIndex,
+ oldOffset,
+ newOffset);
+ transaction->WriteRows(Path_, NameTable_, rowsBuilder.Build());
+ }
+
+ TFuture<std::vector<TPartitionInfo>> CollectPartitions(
+ const IClientPtr& client,
+ int expectedPartitionCount,
+ bool withLastConsumeTime = false) const override
+ {
+ if (expectedPartitionCount <= 0) {
+ return MakeFuture(std::vector<TPartitionInfo>{});
+ }
+
+ auto selectQuery = Format(
+ "[%v], [%v] from [%v] where ([%v] between 0 and %v) and (%v)",
+ PartitionIndexColumnName_,
+ OffsetColumnName_,
+ Path_,
+ PartitionIndexColumnName_,
+ expectedPartitionCount - 1,
+ RowPrefixCondition_);
+ return BIND(
+ &TGenericConsumerClient::DoCollectPartitions,
+ MakeStrong(this),
+ client,
+ selectQuery,
+ withLastConsumeTime)
+ .AsyncVia(GetCurrentInvoker())
+ .Run()
+ .Apply(BIND([expectedPartitionCount] (const std::vector<TPartitionInfo>& partitionInfos) {
+ YT_VERIFY(std::ssize(partitionInfos) <= expectedPartitionCount);
+
+ std::vector<TPartitionInfo> result(expectedPartitionCount);
+ for (int partitionIndex = 0; partitionIndex < expectedPartitionCount; ++partitionIndex) {
+ result[partitionIndex] = {.PartitionIndex = partitionIndex, .NextRowIndex = 0};
+ }
+ for (const auto& partitionInfo : partitionInfos) {
+ result[partitionInfo.PartitionIndex] = partitionInfo;
+ }
+ return result;
+ }));
+ }
+
+ TFuture<std::vector<TPartitionInfo>> CollectPartitions(
+ const IClientPtr& client,
+ const std::vector<int>& partitionIndexes,
+ bool withLastConsumeTime) const override
+ {
+ auto selectQuery = Format(
+ "[%v], [%v] from [%v] where ([%v] in (%v)) and (%v)",
+ PartitionIndexColumnName_,
+ OffsetColumnName_,
+ Path_,
+ PartitionIndexColumnName_,
+ JoinSeq(",", partitionIndexes),
+ RowPrefixCondition_);
+ return BIND(
+ &TGenericConsumerClient::DoCollectPartitions,
+ MakeStrong(this),
+ client,
+ selectQuery,
+ withLastConsumeTime)
+ .AsyncVia(GetCurrentInvoker())
+ .Run()
+ .Apply(BIND([partitionIndexes] (const std::vector<TPartitionInfo>& partitionInfos) {
+ THashMap<int, TPartitionInfo> indexToPartitionInfo;
+ for (auto partitionIndex : partitionIndexes) {
+ indexToPartitionInfo[partitionIndex] = {
+ .PartitionIndex = partitionIndex,
+ .NextRowIndex = 0,
+ };
+ }
+ for (const auto& partitionInfo : partitionInfos) {
+ indexToPartitionInfo[partitionInfo.PartitionIndex] = partitionInfo;
+ }
+
+ std::vector<TPartitionInfo> result;
+ result.reserve(partitionIndexes.size());
+ for (auto partitionIndex : partitionIndexes) {
+ result.push_back(indexToPartitionInfo[partitionIndex]);
+ }
+
+ return result;
+ }));
+ }
+
+ TFuture<TCrossClusterReference> FetchTargetQueue(const IClientPtr& client) const override
+ {
+ return client->GetNode(Path_ + "/@target_queue")
+ .Apply(BIND([] (const TYsonString& ysonString) {
+ return TCrossClusterReference::FromString(ConvertTo<TString>(ysonString));
+ }));
+ }
+
+ TFuture<TPartitionStatistics> FetchPartitionStatistics(
+ const IClientPtr& client,
+ const TYPath& queue,
+ int partitionIndex) const override
+ {
+ return client->GetNode(queue + "/@tablets")
+ .Apply(BIND([queue, partitionIndex] (const TYsonString& ysonString) -> TPartitionStatistics {
+ auto tabletList = ConvertToNode(ysonString)->AsList();
+
+ for (const auto& tablet : tabletList->GetChildren()) {
+ const auto& tabletMapNode = tablet->AsMap();
+ auto tabletIndex = ConvertTo<i64>(tabletMapNode->FindChild("index"));
+
+ if (partitionIndex == tabletIndex) {
+ auto flushedDataWeight = ConvertTo<i64>(tabletMapNode->FindChild("statistics")->AsMap()->FindChild("uncompressed_data_size"));
+ auto flushedRowCount = ConvertTo<i64>(tabletMapNode->FindChild("flushed_row_count"));
+
+ return {.FlushedDataWeight = flushedDataWeight, .FlushedRowCount = flushedRowCount};
+ }
+ }
+
+ THROW_ERROR_EXCEPTION("Queue %Qv has no tablet with index %v", queue, partitionIndex);
+ }));
+ }
+
+private:
+ const TYPath Path_;
+ const TUnversionedOwningRow RowPrefix_;
+ //! A condition of form ([ColumnName0], [ColumnName1], ...) = (RowPrefix_[0], RowPrefix_[1], ...)
+ //! defining this subconsumer.
+ TString RowPrefixCondition_;
+ const TStringBuf PartitionIndexColumnName_;
+ const TStringBuf OffsetColumnName_;
+ const TTableSchemaPtr& TableSchema_;
+ const TNameTablePtr NameTable_;
+ const int PartitionIndexColumnId_;
+ const int OffsetColumnId_;
+ //! A column filter consisting of PartitionIndexColumnName_ and OffsetColumnName_.
+ const TColumnFilter SubConsumerColumnFilter_;
+
+ // COMPAT(achulkov2): Remove this once we drop support for legacy BigRT consumers.
+ //! Controls whether the offset is decremented before being written to the offset table.
+ //! BigRT stores the offset of the last read row, so for legacy BigRT consumers this option
+ //! should be set to true.
+ bool DecrementOffset_ = false;
+
+ TTableSchemaPtr SubConsumerSchema_;
+
+ std::vector<TPartitionInfo> DoCollectPartitions(
+ const IClientPtr& client,
+ const TString& selectQuery,
+ bool withLastConsumeTime) const
+ {
+ std::vector<TPartitionInfo> result;
+
+ YT_LOG_DEBUG("Collecting partitions (Query: %v)", selectQuery);
+
+ TSelectRowsOptions selectRowsOptions;
+ auto selectRowsResult = WaitFor(client->SelectRows(selectQuery, selectRowsOptions))
+ .ValueOrThrow();
+
+ // Note that after table construction table schema may have changed.
+ // We must be prepared for that.
+
+ auto partitionIndexRowsetColumnId =
+ selectRowsResult.Rowset->GetNameTable()->FindId(PartitionIndexColumnName_);
+ auto offsetRowsetColumnId = selectRowsResult.Rowset->GetNameTable()->FindId(OffsetColumnName_);
+
+ if (!partitionIndexRowsetColumnId || !offsetRowsetColumnId) {
+ THROW_ERROR_EXCEPTION(
+ "Table must have columns %Qv and %Qv",
+ PartitionIndexColumnName_,
+ OffsetColumnName_);
+ }
+
+ std::vector<ui64> partitionIndices;
+ for (auto row : selectRowsResult.Rowset->GetRows()) {
+
+ YT_VERIFY(row.GetCount() == 2);
+
+ const auto& partitionIndexValue = row[*partitionIndexRowsetColumnId];
+ if (partitionIndexValue.Type == EValueType::Null) {
+ // This is a weird row for partition Null. Just ignore it.
+ continue;
+ }
+ YT_VERIFY(partitionIndexValue.Type == EValueType::Uint64);
+
+ partitionIndices.push_back(FromUnversionedValue<ui64>(partitionIndexValue));
+
+ const auto& offsetValue = row[*offsetRowsetColumnId];
+
+ i64 offset;
+ if (offsetValue.Type == EValueType::Uint64) {
+ offset = FromUnversionedValue<i64>(offsetValue);
+ if (DecrementOffset_) {
+ ++offset;
+ }
+ } else if (offsetValue.Type == EValueType::Null) {
+ offset = 0;
+ } else {
+ YT_ABORT();
+ }
+
+ // NB: in BigRT offsets encode the last read row, while we operate with the first unread row.
+ result.emplace_back(TPartitionInfo{
+ .PartitionIndex = FromUnversionedValue<i64>(partitionIndexValue),
+ .NextRowIndex = offset,
+ });
+ }
+
+ if (!withLastConsumeTime) {
+ return result;
+ }
+
+ // Now do versioned lookups in order to obtain timestamps.
+
+ TUnversionedRowsBuilder builder;
+ for (ui64 partitionIndex : partitionIndices) {
+ TUnversionedRowBuilder rowBuilder;
+ for (const auto& value : RowPrefix_) {
+ rowBuilder.AddValue(value);
+ }
+ rowBuilder.AddValue(MakeUnversionedUint64Value(partitionIndex, PartitionIndexColumnId_));
+ builder.AddRow(rowBuilder.GetRow());
+ }
+
+ TVersionedLookupRowsOptions options;
+ // This allows easier detection of key set change during the query.
+ options.KeepMissingRows = true;
+ options.RetentionConfig = New<TRetentionConfig>();
+ options.RetentionConfig->MaxDataVersions = 1;
+
+ auto versionedRowset = WaitFor(client->VersionedLookupRows(
+ Path_,
+ NameTable_,
+ builder.Build(),
+ options))
+ .ValueOrThrow();
+
+ YT_VERIFY(versionedRowset->GetRows().size() == partitionIndices.size());
+
+ for (const auto& [index, versionedRow] : Enumerate(versionedRowset->GetRows())) {
+ if (versionedRow.GetWriteTimestampCount() < 1) {
+ THROW_ERROR_EXCEPTION("Partition set changed during collection");
+ }
+ auto timestamp = versionedRow.WriteTimestamps()[0];
+ result[index].LastConsumeTime = TimestampToInstant(timestamp).first;
+ }
+
+ return result;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISubConsumerClientPtr CreateBigRTConsumerClient(
+ const TYPath& path,
+ const TTableSchema& schema)
+{
+ if (!schema.IsUniqueKeys()) {
+ THROW_ERROR_EXCEPTION("Consumer schema must have unique keys, schema does not")
+ << TErrorAttribute("actual_schema", schema);
+ }
+
+ if (schema == *BigRTConsumerTableSchema) {
+ return New<TGenericConsumerClient>(
+ path,
+ TUnversionedOwningRow(),
+ "ShardId",
+ "Offset",
+ /*decrementOffset*/ true,
+ BigRTConsumerTableSchema);
+ } else {
+ THROW_ERROR_EXCEPTION("Table schema is not recognized as a valid BigRT consumer schema")
+ << TErrorAttribute("expected_schema", *BigRTConsumerTableSchema)
+ << TErrorAttribute("actual_schema", schema);
+ }
+}
+
+ISubConsumerClientPtr CreateBigRTConsumerClient(
+ const IClientPtr& client,
+ const TYPath& path)
+{
+ auto tableInfo = WaitFor(client->GetTableMountCache()->GetTableInfo(path))
+ .ValueOrThrow();
+ auto schema = tableInfo->Schemas[ETableSchemaKind::Primary];
+
+ return CreateBigRTConsumerClient(path, *schema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYTConsumerClient
+ : public IConsumerClient
+{
+public:
+ explicit TYTConsumerClient(TYPath path)
+ : Path_(std::move(path))
+ { }
+
+ ISubConsumerClientPtr GetSubConsumerClient(const TCrossClusterReference& queue) const override
+ {
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedStringValue(queue.Cluster, QueueClusterColumnId_));
+ builder.AddValue(MakeUnversionedStringValue(queue.Path, QueuePathColumnId_));
+ auto row = builder.FinishRow();
+
+ auto subConsumerClient = New<TGenericConsumerClient>(
+ Path_,
+ std::move(row),
+ "partition_index",
+ "offset",
+ /*decrementOffset*/ false,
+ YTConsumerTableSchema);
+
+ return subConsumerClient;
+ }
+
+private:
+ TYPath Path_;
+ static const TNameTablePtr NameTable_;
+ static const int QueueClusterColumnId_;
+ static const int QueuePathColumnId_;
+};
+
+const TNameTablePtr TYTConsumerClient::NameTable_ = TNameTable::FromSchema(*YTConsumerTableSchema);
+const int TYTConsumerClient::QueueClusterColumnId_ = TYTConsumerClient::NameTable_->GetId("queue_cluster");
+const int TYTConsumerClient::QueuePathColumnId_ = TYTConsumerClient::NameTable_->GetId("queue_path");
+
+////////////////////////////////////////////////////////////////////////////////
+
+IConsumerClientPtr CreateConsumerClient(
+ const TYPath& path,
+ const TTableSchema& schema)
+{
+ if (!schema.IsUniqueKeys()) {
+ THROW_ERROR_EXCEPTION("Consumer schema must have unique keys, schema does not")
+ << TErrorAttribute("actual_schema", schema);
+ }
+
+ if (schema == *YTConsumerTableSchema) {
+ return New<TYTConsumerClient>(path);
+ } else {
+ THROW_ERROR_EXCEPTION("Table schema is not recognized as a valid consumer schema")
+ << TErrorAttribute("expected_schema", *YTConsumerTableSchema)
+ << TErrorAttribute("actual_schema", schema);
+ }
+}
+
+IConsumerClientPtr CreateConsumerClient(
+ const IClientPtr& client,
+ const TYPath& path)
+{
+ auto tableInfo = WaitFor(client->GetTableMountCache()->GetTableInfo(path))
+ .ValueOrThrow();
+ auto schema = tableInfo->Schemas[ETableSchemaKind::Primary];
+
+ return CreateConsumerClient(path, *schema);
+}
+
+ISubConsumerClientPtr CreateSubConsumerClient(
+ const IClientPtr& client,
+ const TYPath& consumerPath,
+ TRichYPath queuePath)
+{
+ auto queueCluster = queuePath.GetCluster();
+ if (!queueCluster) {
+ if (auto clientCluster = client->GetClusterName()) {
+ queueCluster = *clientCluster;
+ }
+ }
+
+ if (!queueCluster) {
+ THROW_ERROR_EXCEPTION(
+ "Cannot create subconsumer for %Qv, could not infer cluster for queue %Qv from attributes or client",
+ consumerPath,
+ queuePath);
+ }
+
+ TCrossClusterReference queueRef;
+ queueRef.Cluster = *queueCluster;
+ queueRef.Path = queuePath.GetPath();
+
+ return CreateConsumerClient(client, consumerPath)->GetSubConsumerClient(queueRef);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/consumer_client.h b/yt/yt/client/queue_client/consumer_client.h
new file mode 100644
index 0000000000..5c239828fa
--- /dev/null
+++ b/yt/yt/client/queue_client/consumer_client.h
@@ -0,0 +1,123 @@
+#pragma once
+
+#include "public.h"
+#include "common.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/table_client/public.h>
+
+#include <yt/yt/client/ypath/public.h>
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPartitionInfo
+{
+ i64 PartitionIndex = -1;
+ // TODO(max42): rename into Offset, this seems like a concise name.
+ i64 NextRowIndex = -1;
+ //! Latest time instant the corresponding partition was consumed.
+ TInstant LastConsumeTime;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Interface representing a consumer table.
+struct ISubConsumerClient
+ : public TRefCounted
+{
+ //! Advance the offset for the given partition, setting it to a new value within the given transaction.
+ //!
+ //! If oldOffset is specified, the current offset is read (within the transaction) and compared with oldOffset.
+ //! If they are equal, the new offset is written, otherwise an exception is thrown.
+ virtual void Advance(
+ const NApi::ITransactionPtr& transaction,
+ int partitionIndex,
+ std::optional<i64> oldOffset,
+ i64 newOffset) const = 0;
+
+ //! Collect partition infos. If there are entries in the consumer table with partition
+ //! indices outside of range [0, expectedPartitionCount), they will be ignored.
+ virtual TFuture<std::vector<TPartitionInfo>> CollectPartitions(
+ const NApi::IClientPtr& client,
+ int expectedPartitionCount,
+ bool withLastConsumeTime = false) const = 0;
+
+ //! Collect partition infos.
+ //! Entries in the consumer table with partition indices not in the given vector will be ignored.
+ virtual TFuture<std::vector<TPartitionInfo>> CollectPartitions(
+ const NApi::IClientPtr& client,
+ const std::vector<int>& partitionIndexes,
+ bool withLastConsumeTime = false) const = 0;
+
+ //! Fetch and parse the target_queue attribute of this consumer.
+ virtual TFuture<TCrossClusterReference> FetchTargetQueue(const NApi::IClientPtr& client) const = 0;
+
+ struct TPartitionStatistics
+ {
+ i64 FlushedDataWeight = 0;
+ i64 FlushedRowCount = 0;
+ };
+
+ // TODO(achulkov2): This should probably handle multiple partition indexes at some point.
+ // TODO(achulkov2): Move this to a separate IQueueClient class?
+ //! Fetch relevant per-tablet statistics for queue.
+ virtual TFuture<TPartitionStatistics> FetchPartitionStatistics(
+ const NApi::IClientPtr& client,
+ const NYPath::TYPath& queue,
+ int partitionIndex) const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ISubConsumerClient)
+
+struct IConsumerClient
+ : public TRefCounted
+{
+ virtual ISubConsumerClientPtr GetSubConsumerClient(const TCrossClusterReference& queue) const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IConsumerClient)
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TODO(max42): get rid of the following two methods.
+// They are left as a temporary mean to keep API tests working.
+
+//! Creates a BigRT single-queue consumer client.
+ISubConsumerClientPtr CreateBigRTConsumerClient(
+ const NYPath::TYPath& path,
+ const NTableClient::TTableSchema& schema);
+
+//! Uses the table mount cache to fetch the consumer's schema and make
+//! sure the consumer actually has BigRT consumer schema.
+ISubConsumerClientPtr CreateBigRTConsumerClient(
+ const NApi::IClientPtr& client,
+ const NYPath::TYPath& path);
+
+//! Creates a native YT multi-queue consumer client.
+IConsumerClientPtr CreateConsumerClient(
+ const NYPath::TYPath& path,
+ const NTableClient::TTableSchema& schema);
+
+//! Uses the table mount cache to fetch the consumer's schema and
+//! make sure the consumer actually has YT consumer schema.
+IConsumerClientPtr CreateConsumerClient(
+ const NApi::IClientPtr& client,
+ const NYPath::TYPath& path);
+
+//! Uses the table mount cache to fetch the consumer's schema and
+//! make sure the consumer actually has YT consumer schema.
+//! Uses the given queue path to fetch the corresponding subconsumer.
+//! If no cluster is set for queue, it is inferred from the given client.
+ISubConsumerClientPtr CreateSubConsumerClient(
+ const NApi::IClientPtr& client,
+ const NYPath::TYPath& consumerPath,
+ NYPath::TRichYPath queuePath);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/partition_reader.cpp b/yt/yt/client/queue_client/partition_reader.cpp
new file mode 100644
index 0000000000..f9be907e40
--- /dev/null
+++ b/yt/yt/client/queue_client/partition_reader.cpp
@@ -0,0 +1,481 @@
+#include "partition_reader.h"
+#include "private.h"
+#include "queue_rowset.h"
+#include "common.h"
+#include "consumer_client.h"
+
+#include <yt/yt/client/api/transaction.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/core/profiling/timing.h>
+
+namespace NYT::NQueueClient {
+
+using namespace NApi;
+using namespace NConcurrency;
+using namespace NProfiling;
+using namespace NTableClient;
+using namespace NTransactionClient;
+using namespace NYPath;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPartitionReader
+ : public IPartitionReader
+{
+public:
+ TPartitionReader(
+ TPartitionReaderConfigPtr config,
+ IClientPtr client,
+ TYPath consumerPath,
+ int partitionIndex)
+ : Config_(std::move(config))
+ , Client_(std::move(client))
+ , ConsumerPath_(std::move(consumerPath))
+ , PartitionIndex_(partitionIndex)
+ , Logger(QueueClientLogger.WithTag("Consumer: %v, Partition: %v", ConsumerPath_, PartitionIndex_))
+ , RowBatchReadOptions_({Config_->MaxRowCount, Config_->MaxDataWeight, Config_->DataWeightPerRowHint})
+ , PullQueueOptions_({.UseNativeTabletNodeApi = Config_->UseNativeTabletNodeApi})
+ { }
+
+ TFuture<void> Open() override
+ {
+ return BIND(&TPartitionReader::DoOpen, MakeStrong(this))
+ .AsyncVia(GetCurrentInvoker())
+ .Run();
+ }
+
+ TFuture<IPersistentQueueRowsetPtr> Read() override
+ {
+ YT_VERIFY(Opened_);
+
+ return BIND(&TPartitionReader::DoRead, MakeStrong(this))
+ .AsyncVia(GetCurrentInvoker())
+ .Run();
+ }
+
+private:
+ const TPartitionReaderConfigPtr Config_;
+ const IClientPtr Client_;
+ TYPath ConsumerPath_;
+ int PartitionIndex_;
+ NLogging::TLogger Logger;
+
+ bool Opened_ = false;
+
+ TYPath QueuePath_;
+ i64 ApproximateDataWeightPerRow_ = 0;
+ ISubConsumerClientPtr ConsumerClient_;
+ TQueueRowBatchReadOptions RowBatchReadOptions_;
+ TPullQueueOptions PullQueueOptions_;
+
+ class TPersistentQueueRowset
+ : public IPersistentQueueRowset
+ {
+ public:
+ TPersistentQueueRowset(IQueueRowsetPtr rowset, TWeakPtr<TPartitionReader> partitionReader, i64 currentOffset)
+ : Rowset_(std::move(rowset))
+ , PartitionReader_(std::move(partitionReader))
+ , CurrentOffset_(currentOffset)
+ { }
+
+ const NTableClient::TTableSchemaPtr& GetSchema() const override
+ {
+ return Rowset_->GetSchema();
+ }
+
+ const NTableClient::TNameTablePtr& GetNameTable() const override
+ {
+ return Rowset_->GetNameTable();
+ }
+
+ TSharedRange<NTableClient::TUnversionedRow> GetRows() const override
+ {
+ return Rowset_->GetRows();
+ }
+
+ i64 GetStartOffset() const override
+ {
+ return Rowset_->GetStartOffset();
+ }
+
+ i64 GetFinishOffset() const override
+ {
+ return Rowset_->GetFinishOffset();
+ }
+
+ void Commit(const NApi::ITransactionPtr& transaction) override
+ {
+ YT_VERIFY(transaction);
+
+ if (auto partitionReader = PartitionReader_.Lock()) {
+ // TODO(achulkov2): Check that this is the first uncommitted batch returned to the user and crash otherwise.
+ // Will be much easier to do once we figure out how prefetching & batch storage will look like.
+
+ // TODO(achulkov2): Mark this batch as committed in the partition reader.
+
+ transaction->AdvanceConsumer(
+ partitionReader->ConsumerPath_,
+ partitionReader->PartitionIndex_,
+ CurrentOffset_,
+ GetFinishOffset());
+ } else {
+ THROW_ERROR_EXCEPTION("Partition reader destroyed");
+ }
+ }
+
+ private:
+ IQueueRowsetPtr Rowset_;
+ TWeakPtr<TPartitionReader> PartitionReader_;
+ i64 CurrentOffset_;
+ };
+
+ IPersistentQueueRowsetPtr DoRead()
+ {
+ YT_LOG_DEBUG("Reading rowset");
+ TWallTimer timer;
+
+ if (!Config_->DataWeightPerRowHint && ApproximateDataWeightPerRow_) {
+ RowBatchReadOptions_.DataWeightPerRowHint = ApproximateDataWeightPerRow_;
+ }
+
+ auto currentOffset = FetchCurrentOffset();
+
+ // TODO(achulkov2): Log the options in the RPC method (via SetRequestInfo) instead?
+ YT_LOG_DEBUG(
+ "Pulling from queue (Offset: %v, MaxRowCount: %v, MaxDataWeight: %v, DataWeightPerRowHint: %v)",
+ currentOffset,
+ RowBatchReadOptions_.MaxRowCount,
+ RowBatchReadOptions_.MaxDataWeight,
+ RowBatchReadOptions_.DataWeightPerRowHint);
+ auto rowset = WaitFor(Client_->PullQueue(
+ QueuePath_,
+ currentOffset,
+ PartitionIndex_,
+ RowBatchReadOptions_,
+ PullQueueOptions_))
+ .ValueOrThrow();
+
+ HandleRowset(rowset);
+
+ YT_LOG_DEBUG("Rowset read (WallTime: %v)", timer.GetElapsedTime());
+
+ return New<TPersistentQueueRowset>(rowset, MakeWeak(this), currentOffset);
+
+ }
+
+ void HandleRowset(const IQueueRowsetPtr& rowset)
+ {
+ // TODO(achulkov2): When prefetching is implemented we'll have some sort of struct for holding the batch + stats.
+ // TODO(achulkov2): Don't burn CPU here and get the data weight from PullQueue somehow.
+ auto dataWeight = static_cast<i64>(GetDataWeight(rowset->GetRows()));
+ i64 rowCount = std::ssize(rowset->GetRows());
+
+ RecomputeApproximateDataWeightPerRow(dataWeight, rowCount);
+
+ YT_LOG_DEBUG(
+ "Rowset obtained (RowCount: %v, DataWeight: %v, StartOffset: %v, FinishOffset: %v)",
+ rowCount,
+ dataWeight,
+ rowset->GetStartOffset(),
+ rowset->GetFinishOffset());
+ }
+
+ void RecomputeApproximateDataWeightPerRow(i64 dataWeight, i64 rowCount)
+ {
+ if (rowCount == 0) {
+ return;
+ }
+
+ auto newDataWeightPerRowHint = dataWeight / rowCount;
+
+ if (ApproximateDataWeightPerRow_) {
+ // TODO(achulkov2): Anything better? Variable coefficient?
+ ApproximateDataWeightPerRow_ = (ApproximateDataWeightPerRow_ + newDataWeightPerRowHint) / 2;
+ } else {
+ ApproximateDataWeightPerRow_ = newDataWeightPerRowHint;
+ }
+
+ YT_LOG_DEBUG("Recomputed approximate data weight per row (ApproximateDataWeightPerRow: %v)", ApproximateDataWeightPerRow_);
+ }
+
+ // NB: Can throw.
+ i64 FetchCurrentOffset() const
+ {
+ TWallTimer timer;
+
+ std::vector<int> partitionIndexesToFetch{PartitionIndex_};
+ auto partitions = WaitFor(ConsumerClient_->CollectPartitions(Client_, partitionIndexesToFetch))
+ .ValueOrThrow();
+
+ YT_VERIFY(partitions.size() <= 1);
+
+ i64 currentOffset = 0;
+
+ if (!partitions.empty()) {
+ YT_VERIFY(partitions[0].PartitionIndex == PartitionIndex_);
+ currentOffset = partitions[0].NextRowIndex;
+ }
+
+ YT_LOG_DEBUG("Fetched current offset (Offset: %v, WallTime: %v)", currentOffset, timer.GetElapsedTime());
+ return currentOffset;
+ }
+
+ void DoOpen()
+ {
+ YT_LOG_DEBUG("Opening partition reader");
+
+ ConsumerClient_ = CreateBigRTConsumerClient(Client_, ConsumerPath_);
+
+ QueuePath_ = WaitFor(ConsumerClient_->FetchTargetQueue(Client_))
+ .ValueOrThrow().Path;
+
+ Logger.AddTag("Queue: %v", QueuePath_);
+
+ auto partitionStatistics = WaitFor(ConsumerClient_->FetchPartitionStatistics(Client_, QueuePath_, PartitionIndex_))
+ .ValueOrThrow();
+
+ RecomputeApproximateDataWeightPerRow(partitionStatistics.FlushedDataWeight, partitionStatistics.FlushedRowCount);
+
+ Opened_ = true;
+
+ YT_LOG_DEBUG("Partition reader opened");
+ }
+};
+
+IPartitionReaderPtr CreatePartitionReader(
+ TPartitionReaderConfigPtr config,
+ IClientPtr client,
+ TYPath path,
+ int partitionIndex)
+{
+ return New<TPartitionReader>(std::move(config), std::move(client), std::move(path), partitionIndex);
+}
+
+class TMultiQueueConsumerPartitionReader
+ : public IPartitionReader
+{
+public:
+ TMultiQueueConsumerPartitionReader(
+ TPartitionReaderConfigPtr config,
+ IClientPtr client,
+ TRichYPath consumerPath,
+ TRichYPath queuePath,
+ int partitionIndex)
+ : Config_(std::move(config))
+ , Client_(std::move(client))
+ , ConsumerPath_(std::move(consumerPath))
+ , QueuePath_(std::move(queuePath))
+ , PartitionIndex_(partitionIndex)
+ , RowBatchReadOptions_({Config_->MaxRowCount, Config_->MaxDataWeight, Config_->DataWeightPerRowHint})
+ , Logger(QueueClientLogger.WithTag("Consumer: %v, Queue: %v, Partition: %v", ConsumerPath_, QueuePath_, PartitionIndex_))
+ {
+ PullConsumerOptions_.UseNativeTabletNodeApi = Config_->UseNativeTabletNodeApi;
+ }
+
+ TFuture<void> Open() override
+ {
+ return BIND(&TMultiQueueConsumerPartitionReader::DoOpen, MakeStrong(this))
+ .AsyncVia(GetCurrentInvoker())
+ .Run();
+ }
+
+ TFuture<IPersistentQueueRowsetPtr> Read() override
+ {
+ YT_VERIFY(Opened_);
+
+ return BIND(&TMultiQueueConsumerPartitionReader::DoRead, MakeStrong(this))
+ .AsyncVia(GetCurrentInvoker())
+ .Run();
+ }
+
+private:
+ const TPartitionReaderConfigPtr Config_;
+ const IClientPtr Client_;
+ const TRichYPath ConsumerPath_;
+ const TRichYPath QueuePath_;
+ const int PartitionIndex_;
+ const TQueueRowBatchReadOptions RowBatchReadOptions_;
+ const NLogging::TLogger Logger;
+ TPullConsumerOptions PullConsumerOptions_;
+
+ ISubConsumerClientPtr ConsumerClient_;
+ bool Opened_ = false;
+
+ class TPersistentQueueRowset
+ : public IPersistentQueueRowset
+ {
+ public:
+ TPersistentQueueRowset(IQueueRowsetPtr rowset, TWeakPtr<TMultiQueueConsumerPartitionReader> partitionReader, i64 currentOffset)
+ : Rowset_(std::move(rowset))
+ , PartitionReader_(std::move(partitionReader))
+ , CurrentOffset_(currentOffset)
+ { }
+
+ const NTableClient::TTableSchemaPtr& GetSchema() const override
+ {
+ return Rowset_->GetSchema();
+ }
+
+ const NTableClient::TNameTablePtr& GetNameTable() const override
+ {
+ return Rowset_->GetNameTable();
+ }
+
+ TSharedRange<NTableClient::TUnversionedRow> GetRows() const override
+ {
+ return Rowset_->GetRows();
+ }
+
+ i64 GetStartOffset() const override
+ {
+ return Rowset_->GetStartOffset();
+ }
+
+ i64 GetFinishOffset() const override
+ {
+ return Rowset_->GetFinishOffset();
+ }
+
+ void Commit(const NApi::ITransactionPtr& transaction) override
+ {
+ YT_VERIFY(transaction);
+
+ if (auto partitionReader = PartitionReader_.Lock()) {
+ // TODO(achulkov2): Check that this is the first uncommitted batch returned to the user and crash otherwise.
+ // Will be much easier to do once we figure out how prefetching & batch storage will look like.
+
+ // TODO(achulkov2): Mark this batch as committed in the partition reader.
+
+ transaction->AdvanceConsumer(
+ partitionReader->ConsumerPath_.GetPath(),
+ partitionReader->QueuePath_,
+ partitionReader->PartitionIndex_,
+ CurrentOffset_,
+ GetFinishOffset());
+ } else {
+ THROW_ERROR_EXCEPTION("Partition reader destroyed");
+ }
+ }
+
+ private:
+ IQueueRowsetPtr Rowset_;
+ TWeakPtr<TMultiQueueConsumerPartitionReader> PartitionReader_;
+ i64 CurrentOffset_;
+ };
+
+ IPersistentQueueRowsetPtr DoRead()
+ {
+ YT_LOG_DEBUG("Reading rowset");
+ TWallTimer timer;
+
+ auto currentOffset = FetchCurrentOffset();
+
+ YT_LOG_DEBUG(
+ "Pulling from queue (Offset: %v, MaxRowCount: %v, MaxDataWeight: %v, DataWeightPerRowHint: %v)",
+ currentOffset,
+ RowBatchReadOptions_.MaxRowCount,
+ RowBatchReadOptions_.MaxDataWeight,
+ RowBatchReadOptions_.DataWeightPerRowHint);
+ auto asyncRowset = (Config_->UsePullConsumer
+ ? Client_->PullConsumer(
+ ConsumerPath_,
+ QueuePath_,
+ currentOffset,
+ PartitionIndex_,
+ RowBatchReadOptions_,
+ PullConsumerOptions_)
+ : Client_->PullQueue(
+ QueuePath_,
+ currentOffset,
+ PartitionIndex_,
+ RowBatchReadOptions_,
+ PullConsumerOptions_));
+ auto rowset = WaitFor(asyncRowset)
+ .ValueOrThrow();
+
+ HandleRowset(rowset);
+
+ YT_LOG_DEBUG("Rowset read (WallTime: %v)", timer.GetElapsedTime());
+
+ return New<TPersistentQueueRowset>(rowset, MakeWeak(this), currentOffset);
+
+ }
+
+ void HandleRowset(const IQueueRowsetPtr& rowset)
+ {
+ // TODO(achulkov2): When prefetching is implemented we'll have some sort of struct for holding the batch + stats.
+ // TODO(achulkov2): Don't burn CPU here and get the data weight from PullQueue somehow.
+ auto dataWeight = static_cast<i64>(GetDataWeight(rowset->GetRows()));
+ i64 rowCount = std::ssize(rowset->GetRows());
+
+ YT_LOG_DEBUG(
+ "Rowset obtained (RowCount: %v, DataWeight: %v, StartOffset: %v, FinishOffset: %v)",
+ rowCount,
+ dataWeight,
+ rowset->GetStartOffset(),
+ rowset->GetFinishOffset());
+ }
+
+ // NB: Can throw.
+ i64 FetchCurrentOffset() const
+ {
+ TWallTimer timer;
+
+ std::vector<int> partitionIndexesToFetch{PartitionIndex_};
+ auto partitions = WaitFor(ConsumerClient_->CollectPartitions(Client_, partitionIndexesToFetch))
+ .ValueOrThrow();
+
+ YT_VERIFY(partitions.size() <= 1);
+
+ i64 currentOffset = 0;
+
+ if (!partitions.empty()) {
+ YT_VERIFY(partitions[0].PartitionIndex == PartitionIndex_);
+ currentOffset = partitions[0].NextRowIndex;
+ }
+
+ YT_LOG_DEBUG("Fetched current offset (Offset: %v, WallTime: %v)", currentOffset, timer.GetElapsedTime());
+ return currentOffset;
+ }
+
+ void DoOpen()
+ {
+ YT_LOG_DEBUG("Opening partition reader");
+
+ auto queueCluster = QueuePath_.GetCluster();
+ if (!queueCluster) {
+ THROW_ERROR_EXCEPTION("Queue cluster must be specified");
+ }
+
+ ConsumerClient_ = CreateConsumerClient(Client_, ConsumerPath_.GetPath())->GetSubConsumerClient({
+ .Cluster = *queueCluster,
+ .Path = QueuePath_.GetPath(),
+ });
+
+ Opened_ = true;
+
+ YT_LOG_DEBUG("Partition reader opened");
+ }
+};
+
+IPartitionReaderPtr CreateMultiQueueConsumerPartitionReader(
+ TPartitionReaderConfigPtr config,
+ IClientPtr client,
+ TRichYPath consumerPath,
+ TRichYPath queuePath,
+ int partitionIndex)
+{
+ return New<TMultiQueueConsumerPartitionReader>(
+ std::move(config),
+ std::move(client),
+ std::move(consumerPath),
+ std::move(queuePath),
+ partitionIndex);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/partition_reader.h b/yt/yt/client/queue_client/partition_reader.h
new file mode 100644
index 0000000000..019c7189c6
--- /dev/null
+++ b/yt/yt/client/queue_client/partition_reader.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "config.h"
+#include "public.h"
+
+#include <yt/yt/client/api/client.h>
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IPersistentQueueRowset
+ : public IQueueRowset
+{
+ //! Stages offset advancement GetStartOffset() -> GetFinishOffset() in the given transaction.
+ virtual void Commit(const NApi::ITransactionPtr& transaction) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IPersistentQueueRowset)
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IPartitionReader
+ : public TRefCounted
+{
+ virtual TFuture<void> Open() = 0;
+ virtual TFuture<IPersistentQueueRowsetPtr> Read() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IPartitionReader)
+
+IPartitionReaderPtr CreatePartitionReader(
+ TPartitionReaderConfigPtr config,
+ NApi::IClientPtr client,
+ NYPath::TYPath path,
+ int partitionIndex);
+
+IPartitionReaderPtr CreateMultiQueueConsumerPartitionReader(
+ TPartitionReaderConfigPtr config,
+ NApi::IClientPtr client,
+ NYPath::TRichYPath consumerPath,
+ NYPath::TRichYPath queuePath,
+ int partitionIndex);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/private.h b/yt/yt/client/queue_client/private.h
new file mode 100644
index 0000000000..9b88a00d1e
--- /dev/null
+++ b/yt/yt/client/queue_client/private.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger QueueClientLogger("QueueClient");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/public.h b/yt/yt/client/queue_client/public.h
new file mode 100644
index 0000000000..ffc1805416
--- /dev/null
+++ b/yt/yt/client/queue_client/public.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <yt/yt/core/misc/common.h>
+#include <yt/yt/core/misc/error_code.h>
+#include <yt/yt/core/misc/ref_counted.h>
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((ConsumerOffsetConflict) (3100))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(IQueueRowset)
+
+DECLARE_REFCOUNTED_STRUCT(IPersistentQueueRowset)
+
+DECLARE_REFCOUNTED_STRUCT(IConsumerClient)
+DECLARE_REFCOUNTED_STRUCT(ISubConsumerClient)
+
+DECLARE_REFCOUNTED_STRUCT(IPartitionReader)
+DECLARE_REFCOUNTED_CLASS(TPartitionReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/queue_rowset.cpp b/yt/yt/client/queue_client/queue_rowset.cpp
new file mode 100644
index 0000000000..08c33a5fa9
--- /dev/null
+++ b/yt/yt/client/queue_client/queue_rowset.cpp
@@ -0,0 +1,110 @@
+#include "queue_rowset.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NQueueClient {
+
+using namespace NApi;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void VerifyRowBatchReadOptions(const TQueueRowBatchReadOptions& options)
+{
+ if (options.MaxRowCount <= 0) {
+ THROW_ERROR_EXCEPTION("MaxRowCount is non-positive");
+ }
+ if (options.MaxDataWeight <= 0) {
+ THROW_ERROR_EXCEPTION("MaxDataWeight is non-positive");
+ }
+ if (options.DataWeightPerRowHint && *options.DataWeightPerRowHint <= 0) {
+ THROW_ERROR_EXCEPTION("DataWeightPerRowHint is non-positive");
+ }
+}
+
+i64 ComputeRowsToRead(const TQueueRowBatchReadOptions& options)
+{
+ VerifyRowBatchReadOptions(options);
+
+ auto rowsToRead = options.MaxRowCount;
+ if (options.DataWeightPerRowHint) {
+ auto rowsToReadWithHint = options.MaxDataWeight / *options.DataWeightPerRowHint;
+ rowsToRead = std::min(rowsToRead, rowsToReadWithHint);
+ }
+
+ rowsToRead = std::max<i64>(rowsToRead, 1);
+
+ return rowsToRead;
+}
+
+i64 GetStartOffset(const IUnversionedRowsetPtr& rowset)
+{
+ const auto& nameTable = rowset->GetNameTable();
+
+ YT_VERIFY(!rowset->GetRows().Empty());
+
+ auto rowIndexColumnId = nameTable->GetIdOrThrow("$row_index");
+ auto startOffsetValue = rowset->GetRows()[0][rowIndexColumnId];
+ THROW_ERROR_EXCEPTION_UNLESS(
+ startOffsetValue.Type == EValueType::Int64,
+ "Incorrect type %Qlv for $row_index column, %Qlv expected; only ordered dynamic tables are supported as queues",
+ startOffsetValue.Type,
+ EValueType::Int64);
+
+ return startOffsetValue.Data.Int64;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TQueueRowset
+ : public IQueueRowset
+{
+public:
+ TQueueRowset(IUnversionedRowsetPtr rowset, i64 startOffset)
+ : Rowset_(std::move(rowset))
+ , StartOffset_(startOffset)
+ { }
+
+ const TTableSchemaPtr& GetSchema() const override
+ {
+ return Rowset_->GetSchema();
+ }
+
+ const TNameTablePtr& GetNameTable() const override
+ {
+ return Rowset_->GetNameTable();
+ }
+
+ TSharedRange<TUnversionedRow> GetRows() const override
+ {
+ return Rowset_->GetRows();
+ }
+
+ i64 GetStartOffset() const override
+ {
+ return StartOffset_;
+ }
+
+ i64 GetFinishOffset() const override
+ {
+ return StartOffset_ + std::ssize(GetRows());
+ }
+
+private:
+ const IUnversionedRowsetPtr Rowset_;
+ const i64 StartOffset_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IQueueRowsetPtr CreateQueueRowset(IUnversionedRowsetPtr rowset, i64 startOffset)
+{
+ return New<TQueueRowset>(std::move(rowset), startOffset);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/queue_client/queue_rowset.h b/yt/yt/client/queue_client/queue_rowset.h
new file mode 100644
index 0000000000..33cd30efee
--- /dev/null
+++ b/yt/yt/client/queue_client/queue_rowset.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/rowset.h>
+
+#include <yt/yt/client/table_client/public.h>
+
+namespace NYT::NQueueClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TQueueRowBatchReadOptions
+{
+ i64 MaxRowCount = 1000;
+ //! Currently only used if DataWeightPerRowHint is set.
+ i64 MaxDataWeight = 16_MB;
+
+ //! If set, this value is used to compute the number of rows to read considering the given MaxDataWeight.
+ std::optional<i64> DataWeightPerRowHint;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void VerifyRowBatchReadOptions(const TQueueRowBatchReadOptions& options);
+
+i64 ComputeRowsToRead(const TQueueRowBatchReadOptions& options);
+
+i64 GetStartOffset(const NApi::IUnversionedRowsetPtr& rowset);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IQueueRowset
+ : public NApi::IUnversionedRowset
+{
+ virtual i64 GetStartOffset() const = 0;
+ virtual i64 GetFinishOffset() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IQueueRowset)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IQueueRowsetPtr CreateQueueRowset(NApi::IUnversionedRowsetPtr rowset, i64 startOffset);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NQueueClient
diff --git a/yt/yt/client/scheduler/operation_cache.cpp b/yt/yt/client/scheduler/operation_cache.cpp
new file mode 100644
index 0000000000..4b7a197d29
--- /dev/null
+++ b/yt/yt/client/scheduler/operation_cache.cpp
@@ -0,0 +1,40 @@
+#include "operation_cache.h"
+#include "private.h"
+
+#include <yt/yt/client/api/client.h>
+
+namespace NYT::NScheduler {
+
+using namespace NApi;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOperationCache::TOperationCache(
+ TAsyncExpiringCacheConfigPtr config,
+ THashSet<TString> attributes,
+ NApi::IClientPtr client,
+ NProfiling::TProfiler profiler)
+ : TAsyncExpiringCache(
+ std::move(config),
+ SchedulerLogger.WithTag("Cache: Operation"),
+ std::move(profiler))
+ , Attributes_(std::move(attributes))
+ , Client_(std::move(client))
+{ }
+
+TFuture<TYsonString> TOperationCache::DoGet(const TOperationIdOrAlias& key, bool /*isPeriodicUpdate*/) noexcept
+{
+ auto options = TGetOperationOptions {
+ .Attributes = Attributes_,
+ .IncludeRuntime = true
+ };
+
+ return Client_->GetOperation(key, options).Apply(BIND([] (const TOperation& operation) {
+ return NYson::ConvertToYsonString(operation);
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NScheduler
diff --git a/yt/yt/client/scheduler/operation_cache.h b/yt/yt/client/scheduler/operation_cache.h
new file mode 100644
index 0000000000..884905635d
--- /dev/null
+++ b/yt/yt/client/scheduler/operation_cache.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/scheduler/operation_id_or_alias.h>
+
+#include <yt/yt/core/misc/async_expiring_cache.h>
+
+namespace NYT::NScheduler {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! This cache is able to store cached results `GetOperation` for given set of attributes.
+class TOperationCache
+ : public TAsyncExpiringCache<TOperationIdOrAlias, NYson::TYsonString>
+{
+public:
+ TOperationCache(
+ TAsyncExpiringCacheConfigPtr config,
+ THashSet<TString> attributes,
+ NApi::IClientPtr client,
+ NProfiling::TProfiler profiler = {});
+
+private:
+ const THashSet<TString> Attributes_;
+ const NApi::IClientPtr Client_;
+
+ TFuture<NYson::TYsonString> DoGet(
+ const TOperationIdOrAlias& key,
+ bool isPeriodicUpdate) noexcept override;
+};
+
+DEFINE_REFCOUNTED_TYPE(TOperationCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NScheduler
diff --git a/yt/yt/client/scheduler/operation_id_or_alias-inl.h b/yt/yt/client/scheduler/operation_id_or_alias-inl.h
new file mode 100644
index 0000000000..260795ebff
--- /dev/null
+++ b/yt/yt/client/scheduler/operation_id_or_alias-inl.h
@@ -0,0 +1,57 @@
+#ifndef OPERATION_ID_OR_ALIAS_INL_H_
+#error "Direct inclusion of this file is not allowed, include operation_id_or_alias.h"
+// For the sake of sane code completion.
+#include "operation_id_or_alias.h"
+#endif
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <library/cpp/yt/misc/variant.h>
+
+namespace NYT::NScheduler {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TProtoClass>
+void FromProto(TOperationIdOrAlias* operationIdOrAlias, const TProtoClass& enclosingProtoMessage)
+{
+ using NYT::FromProto;
+
+ switch (enclosingProtoMessage.operation_id_or_alias_case()) {
+ case TProtoClass::OperationIdOrAliasCase::kOperationId: {
+ operationIdOrAlias->Payload = FromProto<TOperationId>(enclosingProtoMessage.operation_id());
+ break;
+ }
+ case TProtoClass::OperationIdOrAliasCase::kOperationAlias: {
+ const auto& operationAlias = enclosingProtoMessage.operation_alias();
+ if (!operationAlias.StartsWith(OperationAliasPrefix)) {
+ THROW_ERROR_EXCEPTION("Operation alias should start with %Qv", OperationAliasPrefix)
+ << TErrorAttribute("operation_alias", operationAlias);
+ }
+ operationIdOrAlias->Payload = operationAlias;
+ break;
+ }
+ case TProtoClass::OperationIdOrAliasCase::OPERATION_ID_OR_ALIAS_NOT_SET: {
+ THROW_ERROR_EXCEPTION("None of operation id and operation alias is set in oneof OperationIdOrAlias proto");
+ }
+ }
+}
+
+template <class TProtoClassPtr>
+void ToProto(TProtoClassPtr enclosingProtoMessage, const TOperationIdOrAlias& operationIdOrAlias)
+{
+ using NYT::ToProto;
+
+ Visit(operationIdOrAlias.Payload,
+ [&] (const TOperationId& operationId) {
+ ToProto(enclosingProtoMessage->mutable_operation_id(), operationId);
+ },
+ [&] (const TString& alias) {
+ enclosingProtoMessage->set_operation_alias(alias);
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NScheduler
diff --git a/yt/yt/client/scheduler/operation_id_or_alias.cpp b/yt/yt/client/scheduler/operation_id_or_alias.cpp
new file mode 100644
index 0000000000..4a64364815
--- /dev/null
+++ b/yt/yt/client/scheduler/operation_id_or_alias.cpp
@@ -0,0 +1,62 @@
+#include "operation_id_or_alias.h"
+
+namespace NYT::NScheduler {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString OperationAliasPrefix("*");
+
+TOperationIdOrAlias::TOperationIdOrAlias(TOperationId id)
+ : Payload(id)
+{ }
+
+TOperationIdOrAlias::TOperationIdOrAlias(TString alias)
+ : Payload(std::move(alias))
+{ }
+
+bool TOperationIdOrAlias::operator ==(const TOperationIdOrAlias& other) const
+{
+ return Payload == other.Payload;
+}
+
+TOperationIdOrAlias TOperationIdOrAlias::FromString(TString operationIdOrAlias)
+{
+ if (!operationIdOrAlias.empty() && operationIdOrAlias[0] == '*') {
+ return TOperationIdOrAlias(operationIdOrAlias);
+ } else {
+ return TOperationIdOrAlias(TOperationId::FromString(operationIdOrAlias));
+ }
+}
+
+void FormatValue(TStringBuilderBase* builder, const TOperationIdOrAlias& operationIdOrAlias, TStringBuf /*format*/)
+{
+ Visit(operationIdOrAlias.Payload,
+ [&] (const TString& alias) {
+ builder->AppendFormat("%v", alias);
+ },
+ [&] (const TOperationId& operationId) {
+ builder->AppendFormat("%v", operationId);
+ });
+}
+
+TString ToString(const TOperationIdOrAlias& operationIdOrAlias)
+{
+ return ToStringViaBuilder(operationIdOrAlias);
+}
+
+TOperationIdOrAlias::operator size_t() const
+{
+ size_t result = 0;
+ Visit(Payload,
+ [&] (const TString& alias) {
+ HashCombine(result, alias);
+ },
+ [&] (const TOperationId& operationId) {
+ HashCombine(result, operationId);
+ });
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NScheduler
diff --git a/yt/yt/client/scheduler/operation_id_or_alias.h b/yt/yt/client/scheduler/operation_id_or_alias.h
new file mode 100644
index 0000000000..db1d7bf956
--- /dev/null
+++ b/yt/yt/client/scheduler/operation_id_or_alias.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "public.h"
+
+#include <variant>
+
+namespace NYT::NScheduler {
+
+////////////////////////////////////////////////////////////////////////////////
+
+extern const TString OperationAliasPrefix;
+
+// This wrapper is needed for ADL in FromProto/ToProto to work.
+struct TOperationIdOrAlias
+{
+ TOperationIdOrAlias() = default;
+ TOperationIdOrAlias(TOperationId id);
+ TOperationIdOrAlias(TString alias);
+
+ static TOperationIdOrAlias FromString(TString operationIdOrAlias);
+
+ std::variant<TOperationId, TString> Payload;
+
+ bool operator ==(const TOperationIdOrAlias& other) const;
+
+ operator size_t() const;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TOperationIdOrAlias& operationIdOrAlias, TStringBuf format);
+TString ToString(const TOperationIdOrAlias& operationIdOrAlias);
+
+// NB: TOperationIdOrAlias corresponds to a oneof group of fields in proto representation,
+// so we use an enclosing proto message object to properly serialize or deserialize it.
+template <class TProtoClass>
+void FromProto(TOperationIdOrAlias* operationIdOrAlias, const TProtoClass& enclosingProtoMessage);
+
+template <class TProtoClassPtr>
+void ToProto(TProtoClassPtr enclosingProtoMessage, const TOperationIdOrAlias& operationIdOrAlias);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NScheduler
+
+#define OPERATION_ID_OR_ALIAS_INL_H_
+#include "operation_id_or_alias-inl.h"
+#undef OPERATION_ID_OR_ALIAS_INL_H_
diff --git a/yt/yt/client/scheduler/private.h b/yt/yt/client/scheduler/private.h
new file mode 100644
index 0000000000..736361c878
--- /dev/null
+++ b/yt/yt/client/scheduler/private.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NScheduler {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger SchedulerLogger("Scheduler");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NScheduler
+
diff --git a/yt/yt/client/scheduler/public.h b/yt/yt/client/scheduler/public.h
new file mode 100644
index 0000000000..f7189b971d
--- /dev/null
+++ b/yt/yt/client/scheduler/public.h
@@ -0,0 +1,159 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/client/job_tracker_client/public.h>
+
+namespace NYT::NScheduler {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using NJobTrackerClient::TJobId;
+using NJobTrackerClient::TOperationId;
+
+DEFINE_ENUM(EOperationType,
+ (Map)
+ (Merge)
+ (Erase)
+ (Sort)
+ (Reduce)
+ (MapReduce)
+ (RemoteCopy)
+ (JoinReduce)
+ (Vanilla)
+);
+
+DEFINE_ENUM(EOperationState,
+ (None)
+ (Starting)
+ (Orphaned)
+ (WaitingForAgent)
+ (Initializing)
+ (Preparing)
+ (Materializing)
+ (ReviveInitializing)
+ (Reviving)
+ (RevivingJobs)
+ (Pending)
+ (Running)
+ (Completing)
+ (Completed)
+ (Aborting)
+ (Aborted)
+ (Failing)
+ (Failed)
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((NoSuchOperation) (200))
+ ((InvalidOperationState) (201))
+ ((TooManyOperations) (202))
+ ((NoSuchJob) (203))
+ ((AgentRevoked) (204))
+ ((OperationFailedOnJobRestart) (210))
+ ((OperationFailedWithInconsistentLocking) (211))
+ ((OperationControllerCrashed) (212))
+ ((TestingError) (213))
+ ((PoolTreesAreUnspecified) (214))
+ ((MaxFailedJobsLimitExceeded) (215))
+ ((OperationFailedToPrepare) (216))
+ ((WatcherHandlerFailed) (217))
+ ((MasterDisconnected) (218))
+ ((NoSuchJobShell) (219))
+);
+
+DEFINE_ENUM(EUnavailableChunkAction,
+ (Fail)
+ (Skip)
+ (Wait)
+);
+
+DEFINE_ENUM(ESchemaInferenceMode,
+ (Auto)
+ (FromInput)
+ (FromOutput)
+);
+
+DEFINE_ENUM(EAbortReason,
+ ((None) ( 0))
+ ((Scheduler) ( 1))
+ ((FailedChunks) ( 2))
+ ((ResourceOverdraft) ( 3))
+ ((Other) ( 4))
+ ((Preemption) ( 5))
+ ((UserRequest) ( 6))
+ ((NodeOffline) ( 7))
+ ((WaitingTimeout) ( 8))
+ ((AccountLimitExceeded) ( 9))
+ ((GetSpecFailed) ( 10))
+ ((Unknown) ( 11))
+ ((RevivalConfirmationTimeout) ( 12))
+ ((IntermediateChunkLimitExceeded) ( 13))
+ ((NodeBanned) ( 14))
+ ((SpeculativeRunWon) ( 15))
+ ((SpeculativeRunLost) ( 16))
+ ((ChunkMappingInvalidated) ( 17))
+ ((NodeWithZeroUserSlots) ( 18))
+ ((NodeSchedulingSegmentChanged) ( 19))
+ ((NodeFairShareTreeChanged) ( 20))
+ ((JobOnUnexpectedNode) ( 21))
+ ((ShallowMergeFailed) ( 22))
+ ((InconsistentJobState) ( 23))
+ // COMPAT(pogorelov)
+ ((JobStatisticsWaitTimeout) ( 24))
+ ((OperationFailed) ( 25))
+ ((JobRevivalDisabled) ( 26))
+ ((BannedInTentativeTree) ( 27))
+ ((Vanished) ( 28))
+ ((Unconfirmed) ( 29))
+ ((Suspended) ( 30))
+ ((ProbingRunWon) ( 31))
+ ((ProbingRunLost) ( 32))
+ ((ProbingToUnsuccessfulJob) ( 33))
+ ((JobProxyFailed) ( 34))
+ ((InterruptionTimeout) ( 35))
+ ((NodeResourceOvercommit) ( 36))
+ ((ProbingCompetitorResultLost) ( 37))
+ ((SpeculativeCompetitorResultLost) ( 38))
+ ((JobTreatmentFailed) ( 39))
+ ((JobTreatmentResultLost) ( 40))
+ ((JobTreatmentToUnsuccessfulJob) ( 41))
+ ((JobTreatmentRunLost) ( 42))
+ ((JobTreatmentRunWon) ( 43))
+ ((OperationCompleted) ( 44))
+ ((OperationAborted) ( 45))
+ ((OperationFinished) ( 46))
+ ((JobMemoryThrashing) ( 47))
+ ((InterruptionUnsupported) ( 48))
+ ((SchedulingFirst) (100))
+ ((SchedulingTimeout) (101))
+ ((SchedulingResourceOvercommit) (102))
+ ((SchedulingOperationSuspended) (103))
+ ((SchedulingJobSpecThrottling) (104))
+ ((SchedulingOther) (105))
+ ((SchedulingOperationDisabled) (106))
+ ((SchedulingOperationIsNotAlive) (107))
+ ((SchedulingLast) (199))
+);
+
+DEFINE_ENUM(EInterruptReason,
+ ((None) (0))
+ ((Preemption) (1))
+ ((UserRequest) (2))
+ ((JobSplit) (3))
+ ((Unknown) (4))
+ ((JobsDisabledOnNode) (5))
+);
+
+DEFINE_ENUM(EAutoMergeMode,
+ (Disabled)
+ (Relaxed)
+ (Economy)
+ (Manual)
+);
+
+DECLARE_REFCOUNTED_CLASS(TOperationCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NScheduler
diff --git a/yt/yt/client/security_client/access_control.cpp b/yt/yt/client/security_client/access_control.cpp
new file mode 100644
index 0000000000..4df05b77fb
--- /dev/null
+++ b/yt/yt/client/security_client/access_control.cpp
@@ -0,0 +1,64 @@
+#include "access_control.h"
+
+namespace NYT::NSecurityClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr TStringBuf AccessControlObjectNamespacePath = "//sys/access_control_object_namespaces/%lv";
+static constexpr TStringBuf AccessControlObjectPath = "//sys/access_control_object_namespaces/%lv/%lv";
+static constexpr TStringBuf AccessControlObjectPrincipalPath = "//sys/access_control_object_namespaces/%lv/%lv/principal";
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAccessControlObjectDescriptor::TAccessControlObjectDescriptor(
+ EAccessControlObjectNamespace accessControlObjectNamespace,
+ EAccessControlObject accessControlObject)
+ : Namespace_(accessControlObjectNamespace)
+ , Name_(accessControlObject)
+{ }
+
+TString TAccessControlObjectDescriptor::GetPath() const
+{
+ return Format(
+ AccessControlObjectPath,
+ Namespace_,
+ Name_);
+}
+
+TString TAccessControlObjectDescriptor::GetPrincipalPath() const
+{
+ return Format(
+ AccessControlObjectPrincipalPath,
+ Namespace_,
+ Name_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAccessControlObjectDescriptor GetAccessControlObjectDescriptor(
+ EAccessControlObject accessControlObjectName)
+{
+ auto iter = AccessControlObjects.find(accessControlObjectName);
+ YT_VERIFY(!iter.IsEnd());
+
+ return iter->second;
+}
+
+TString GetAccessControlObjectNamespacePath(EAccessControlObjectNamespace accessControlObjectNamespace)
+{
+ return Format(AccessControlObjectNamespacePath, accessControlObjectNamespace);
+}
+
+TString GetAccessControlObjectNamespaceName(EAccessControlObjectNamespace accessControlObjectNamespace)
+{
+ return Format("%lv", accessControlObjectNamespace);
+}
+
+TString GetAccessControlObjectName(EAccessControlObject accessControlObject)
+{
+ return Format("%lv", accessControlObject);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
diff --git a/yt/yt/client/security_client/access_control.h b/yt/yt/client/security_client/access_control.h
new file mode 100644
index 0000000000..5dd74a0144
--- /dev/null
+++ b/yt/yt/client/security_client/access_control.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/core/ytree/helpers.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+namespace NYT::NSecurityClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAccessControlObjectDescriptor
+{
+public:
+ DEFINE_BYVAL_RO_PROPERTY(EAccessControlObjectNamespace, Namespace);
+ DEFINE_BYVAL_RO_PROPERTY(EAccessControlObject, Name);
+
+public:
+ TAccessControlObjectDescriptor(
+ EAccessControlObjectNamespace accessControlObjectNamespace,
+ EAccessControlObject accessControlObject);
+
+ TString GetPath() const;
+ TString GetPrincipalPath() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define ACCESS_CONTROL_ENTRY(namespace, name) \
+ {(name), TAccessControlObjectDescriptor((namespace), (name))}
+
+const std::vector<EAccessControlObjectNamespace> AccessControlObjectNamespaces = {
+ EAccessControlObjectNamespace::AdminCommands
+};
+
+const THashMap<EAccessControlObject, TAccessControlObjectDescriptor> AccessControlObjects = {
+ ACCESS_CONTROL_ENTRY(EAccessControlObjectNamespace::AdminCommands, EAccessControlObject::DisableChunkLocations),
+ ACCESS_CONTROL_ENTRY(EAccessControlObjectNamespace::AdminCommands, EAccessControlObject::DestroyChunkLocations),
+ ACCESS_CONTROL_ENTRY(EAccessControlObjectNamespace::AdminCommands, EAccessControlObject::ResurrectChunkLocations),
+ ACCESS_CONTROL_ENTRY(EAccessControlObjectNamespace::AdminCommands, EAccessControlObject::BuildSnapshot),
+ ACCESS_CONTROL_ENTRY(EAccessControlObjectNamespace::AdminCommands, EAccessControlObject::BuildMasterSnapshot),
+ ACCESS_CONTROL_ENTRY(EAccessControlObjectNamespace::AdminCommands, EAccessControlObject::SwitchLeader),
+ ACCESS_CONTROL_ENTRY(EAccessControlObjectNamespace::AdminCommands, EAccessControlObject::RequestReboot)
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAccessControlObjectDescriptor GetAccessControlObjectDescriptor(EAccessControlObject accessControlObjectName);
+
+TString GetAccessControlObjectNamespacePath(EAccessControlObjectNamespace accessControlObjectNamespace);
+
+TString GetAccessControlObjectNamespaceName(EAccessControlObjectNamespace accessControlObjectNamespace);
+
+TString GetAccessControlObjectName(EAccessControlObject accessControlObject);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
diff --git a/yt/yt/client/security_client/acl.cpp b/yt/yt/client/security_client/acl.cpp
new file mode 100644
index 0000000000..69db3d80cc
--- /dev/null
+++ b/yt/yt/client/security_client/acl.cpp
@@ -0,0 +1,201 @@
+#include "acl.h"
+
+#include <yt/yt/core/yson/pull_parser_deserialize.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/serialize.h>
+
+namespace NYT::NSecurityClient {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSerializableAccessControlEntry::TSerializableAccessControlEntry() = default;
+
+TSerializableAccessControlEntry::TSerializableAccessControlEntry(
+ ESecurityAction action,
+ std::vector<TString> subjects,
+ EPermissionSet permissions,
+ EAceInheritanceMode inheritanceMode)
+ : Action(action)
+ , Subjects(std::move(subjects))
+ , Permissions(permissions)
+ , InheritanceMode(inheritanceMode)
+{ }
+
+bool operator == (const TSerializableAccessControlEntry& lhs, const TSerializableAccessControlEntry& rhs)
+{
+ return
+ lhs.Action == rhs.Action &&
+ lhs.Subjects == rhs.Subjects &&
+ lhs.Permissions == rhs.Permissions &&
+ lhs.InheritanceMode == rhs.InheritanceMode &&
+ lhs.Columns == rhs.Columns &&
+ lhs.Vital == rhs.Vital;
+}
+
+bool operator != (const TSerializableAccessControlEntry& lhs, const TSerializableAccessControlEntry& rhs)
+{
+ return !(lhs == rhs);
+}
+
+// NB(levysotsky): We don't use TYsonStruct here
+// because we want to mirror the TAccessControlList structure,
+// and a vector of TYsonStruct-s cannot be declared (as it has no move constructor).
+void Serialize(const TSerializableAccessControlEntry& ace, NYson::IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("action").Value(ace.Action)
+ .Item("subjects").Value(ace.Subjects)
+ .Item("permissions").Value(ace.Permissions)
+ // TODO(max42): YT-16347.
+ // Do not serialize this field by default
+ .Item("inheritance_mode").Value(ace.InheritanceMode)
+ .OptionalItem("columns", ace.Columns)
+ .OptionalItem("vital", ace.Vital)
+ .EndMap();
+}
+
+static void EnsureCorrect(const TSerializableAccessControlEntry& ace)
+{
+ if (ace.Action == ESecurityAction::Undefined) {
+ THROW_ERROR_EXCEPTION("%Qlv action is not allowed",
+ ESecurityAction::Undefined);
+ }
+
+ // Currently, we allow empty permissions with columns. They seem to be no-op.
+ bool onlyReadOrEmpty = None(ace.Permissions & ~EPermission::Read);
+ if (ace.Columns && !onlyReadOrEmpty) {
+ THROW_ERROR_EXCEPTION("ACE specifying columns may contain only %Qlv permission; found %Qlv",
+ EPermission::Read,
+ ace.Permissions);
+ }
+
+ bool hasRegisterQueueConsumer = Any(ace.Permissions & EPermission::RegisterQueueConsumer);
+ bool onlyRegisterQueueConsumer = ace.Permissions == EPermission::RegisterQueueConsumer;
+
+ if (hasRegisterQueueConsumer && !ace.Vital) {
+ THROW_ERROR_EXCEPTION("Permission %Qlv requires vitality to be specified",
+ EPermission::RegisterQueueConsumer);
+ }
+ if (ace.Vital && !onlyRegisterQueueConsumer) {
+ THROW_ERROR_EXCEPTION("ACE specifying vitality must contain a single %Qlv permission; found %Qlv",
+ EPermission::RegisterQueueConsumer,
+ ace.Permissions);
+ }
+}
+
+void Deserialize(TSerializableAccessControlEntry& ace, NYTree::INodePtr node)
+{
+ using NYTree::Deserialize;
+
+ auto mapNode = node->AsMap();
+
+ Deserialize(ace.Action, mapNode->GetChildOrThrow("action"));
+ Deserialize(ace.Subjects, mapNode->GetChildOrThrow("subjects"));
+ Deserialize(ace.Permissions, mapNode->GetChildOrThrow("permissions"));
+ if (auto inheritanceModeNode = mapNode->FindChild("inheritance_mode")) {
+ Deserialize(ace.InheritanceMode, inheritanceModeNode);
+ } else {
+ ace.InheritanceMode = EAceInheritanceMode::ObjectAndDescendants;
+ }
+ if (auto columnsNode = mapNode->FindChild("columns")) {
+ Deserialize(ace.Columns, columnsNode);
+ } else {
+ ace.Columns.reset();
+ }
+ if (auto vitalNode = mapNode->FindChild("vital")) {
+ Deserialize(ace.Vital, vitalNode);
+ } else {
+ ace.Vital.reset();
+ }
+ EnsureCorrect(ace);
+}
+
+void Deserialize(TSerializableAccessControlEntry& ace, NYson::TYsonPullParserCursor* cursor)
+{
+ auto HasAction = false;
+ auto HasSubjects = false;
+ auto HasPermissions = false;
+ ace.InheritanceMode = EAceInheritanceMode::ObjectAndDescendants;
+ cursor->ParseMap([&] (TYsonPullParserCursor* cursor) {
+ auto key = cursor->GetCurrent().UncheckedAsString();
+ if (key == TStringBuf("action")) {
+ cursor->Next();
+ HasAction = true;
+ Deserialize(ace.Action, cursor);
+ } else if (key == TStringBuf("subjects")) {
+ cursor->Next();
+ HasSubjects = true;
+ Deserialize(ace.Subjects, cursor);
+ } else if (key == TStringBuf("permissions")) {
+ cursor->Next();
+ HasPermissions = true;
+ Deserialize(ace.Permissions, cursor);
+ } else if (key == TStringBuf("inheritance_mode")) {
+ cursor->Next();
+ Deserialize(ace.InheritanceMode, cursor);
+ } else if (key == TStringBuf("columns")) {
+ cursor->Next();
+ Deserialize(ace.Columns, cursor);
+ } else if (key == TStringBuf("vital")) {
+ cursor->Next();
+ Deserialize(ace.Vital, cursor);
+ } else {
+ cursor->Next();
+ cursor->SkipComplexValue();
+ }
+ });
+ if (!(HasAction && HasSubjects && HasPermissions)) {
+ THROW_ERROR_EXCEPTION("Error parsing ACE: \"action\", \"subject\" and \"permissions\" fields are required");
+ }
+ EnsureCorrect(ace);
+}
+
+void TSerializableAccessControlEntry::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+
+ Persist(context, Action);
+ Persist(context, Subjects);
+ Persist(context, Permissions);
+ Persist(context, InheritanceMode);
+ // NB: Columns and Vital are not persisted since this method is intended only for use in controller.
+}
+
+bool operator == (const TSerializableAccessControlList& lhs, const TSerializableAccessControlList& rhs)
+{
+ return lhs.Entries == rhs.Entries;
+}
+
+bool operator != (const TSerializableAccessControlList& lhs, const TSerializableAccessControlList& rhs)
+{
+ return !(lhs == rhs);
+}
+
+void Serialize(const TSerializableAccessControlList& acl, NYson::IYsonConsumer* consumer)
+{
+ NYTree::Serialize(acl.Entries, consumer);
+}
+
+void Deserialize(TSerializableAccessControlList& acl, NYTree::INodePtr node)
+{
+ NYTree::Deserialize(acl.Entries, node);
+}
+
+void TSerializableAccessControlList::Persist(const TStreamPersistenceContext& context)
+{
+ NYT::Persist(context, Entries);
+}
+
+void Deserialize(TSerializableAccessControlList& acl, NYson::TYsonPullParserCursor* cursor)
+{
+ Deserialize(acl.Entries, cursor);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
diff --git a/yt/yt/client/security_client/acl.h b/yt/yt/client/security_client/acl.h
new file mode 100644
index 0000000000..04ea66221a
--- /dev/null
+++ b/yt/yt/client/security_client/acl.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/permission.h>
+
+#include <vector>
+
+namespace NYT::NSecurityClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSerializableAccessControlEntry
+{
+ ESecurityAction Action = ESecurityAction::Undefined;
+ std::vector<TString> Subjects;
+ NYTree::EPermissionSet Permissions;
+ EAceInheritanceMode InheritanceMode = EAceInheritanceMode::ObjectAndDescendants;
+ std::optional<std::vector<TString>> Columns;
+ std::optional<bool> Vital;
+
+ TSerializableAccessControlEntry(
+ ESecurityAction action,
+ std::vector<TString> subjects,
+ NYTree::EPermissionSet permissions,
+ EAceInheritanceMode inheritanceMode = EAceInheritanceMode::ObjectAndDescendants);
+
+ // Use only for deserialization.
+ TSerializableAccessControlEntry();
+
+ // Used only for persistence in operation controller. Does not work with Columns and Vital fields.
+ void Persist(const TStreamPersistenceContext& context);
+};
+
+bool operator == (const TSerializableAccessControlEntry& lhs, const TSerializableAccessControlEntry& rhs);
+bool operator != (const TSerializableAccessControlEntry& lhs, const TSerializableAccessControlEntry& rhs);
+
+void Serialize(const TSerializableAccessControlEntry& ace, NYson::IYsonConsumer* consumer);
+void Deserialize(TSerializableAccessControlEntry& ace, NYTree::INodePtr node);
+void Deserialize(TSerializableAccessControlEntry& ace, NYson::TYsonPullParserCursor* cursor);
+
+struct TSerializableAccessControlList
+{
+ std::vector<TSerializableAccessControlEntry> Entries;
+
+ void Persist(const TStreamPersistenceContext& context);
+};
+
+bool operator == (const TSerializableAccessControlList& lhs, const TSerializableAccessControlList& rhs);
+bool operator != (const TSerializableAccessControlList& lhs, const TSerializableAccessControlList& rhs);
+
+void Serialize(const TSerializableAccessControlList& acl, NYson::IYsonConsumer* consumer);
+void Deserialize(TSerializableAccessControlList& acl, NYTree::INodePtr node);
+void Deserialize(TSerializableAccessControlList& acl, NYson::TYsonPullParserCursor* cursor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
diff --git a/yt/yt/client/security_client/helpers.cpp b/yt/yt/client/security_client/helpers.cpp
new file mode 100644
index 0000000000..1ac9b7c11d
--- /dev/null
+++ b/yt/yt/client/security_client/helpers.cpp
@@ -0,0 +1,81 @@
+#include "helpers.h"
+
+#include <yt/yt/core/ypath/token.h>
+
+#include <yt/yt/core/ytree/permission.h>
+
+namespace NYT::NSecurityClient {
+
+using namespace NYPath;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYPath GetUserPath(const TString& name)
+{
+ return "//sys/users/" + ToYPathLiteral(name);
+}
+
+TYPath GetGroupPath(const TString& name)
+{
+ return "//sys/groups/" + ToYPathLiteral(name);
+}
+
+TYPath GetAccountPath(const TString& name)
+{
+ return "//sys/accounts/" + ToYPathLiteral(name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ESecurityAction CheckPermissionsByAclAndSubjectClosure(
+ const TSerializableAccessControlList& acl,
+ const THashSet<TString>& subjectClosure,
+ NYTree::EPermissionSet permissions)
+{
+ NYTree::EPermissionSet allowedPermissions = {};
+ NYTree::EPermissionSet deniedPermissions = {};
+
+ for (const auto& ace : acl.Entries) {
+ if (ace.Action != NSecurityClient::ESecurityAction::Allow && ace.Action != NSecurityClient::ESecurityAction::Deny) {
+ THROW_ERROR_EXCEPTION("Action %Qv is not supported", FormatEnum(ace.Action));
+ }
+ for (const auto& aceSubject : ace.Subjects) {
+ if (subjectClosure.contains(aceSubject)) {
+ if (ace.Action == NSecurityClient::ESecurityAction::Allow) {
+ allowedPermissions |= ace.Permissions;
+ } else {
+ deniedPermissions |= ace.Permissions;
+ }
+ }
+ }
+ }
+
+ return (allowedPermissions & ~deniedPermissions & permissions) == permissions
+ ? ESecurityAction::Allow
+ : ESecurityAction::Deny;
+}
+
+void ValidateSecurityTag(const TSecurityTag& tag)
+{
+ if (tag.empty()) {
+ THROW_ERROR_EXCEPTION("Security tag cannot be empty");
+ }
+ if (tag.length() > MaxSecurityTagLength) {
+ THROW_ERROR_EXCEPTION("Security tag %Qv is too long: %v > %v",
+ tag,
+ tag.length(),
+ MaxSecurityTagLength);
+ }
+}
+
+void ValidateSecurityTags(const std::vector<TSecurityTag>& tags)
+{
+ for (const auto& tag : tags) {
+ ValidateSecurityTag(tag);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
+
diff --git a/yt/yt/client/security_client/helpers.h b/yt/yt/client/security_client/helpers.h
new file mode 100644
index 0000000000..8f361b3aaa
--- /dev/null
+++ b/yt/yt/client/security_client/helpers.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "acl.h"
+#include "public.h"
+
+#include <yt/yt/client/ypath/public.h>
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NSecurityClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NYPath::TYPath GetUserPath(const TString& name);
+NYPath::TYPath GetGroupPath(const TString& name);
+NYPath::TYPath GetAccountPath(const TString& name);
+
+////////////////////////////////////////////////////////////////////////////////
+
+ESecurityAction CheckPermissionsByAclAndSubjectClosure(
+ const TSerializableAccessControlList& acl,
+ const THashSet<TString>& subjectClosure,
+ NYTree::EPermissionSet permissions);
+
+void ValidateSecurityTag(const TSecurityTag& tag);
+void ValidateSecurityTags(const std::vector<TSecurityTag>& tags);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
+
diff --git a/yt/yt/client/security_client/public.cpp b/yt/yt/client/security_client/public.cpp
new file mode 100644
index 0000000000..08fecc7570
--- /dev/null
+++ b/yt/yt/client/security_client/public.cpp
@@ -0,0 +1,37 @@
+#include "public.h"
+
+namespace NYT::NSecurityClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString RootAccountName("root");
+const TString RootAccountCypressPath("//sys/account_tree");
+const TString TmpAccountName("tmp");
+const TString SysAccountName("sys");
+const TString IntermediateAccountName("intermediate");
+const TString ChunkWiseAccountingMigrationAccountName("chunk_wise_accounting_migration");
+const TString SequoiaAccountName("sequoia");
+
+const TString GuestUserName("guest");
+const TString JobUserName("job");
+const TString SchedulerUserName("scheduler");
+const TString ReplicatorUserName("replicator");
+const TString OwnerUserName("owner");
+const TString FileCacheUserName("file_cache");
+const TString OperationsCleanerUserName("operations_cleaner");
+const TString OperationsClientUserName("operations_client");
+const TString TabletCellChangeloggerUserName("tablet_cell_changelogger");
+const TString TabletCellSnapshotterUserName("tablet_cell_snapshotter");
+const TString TableMountInformerUserName("table_mount_informer");
+const TString AlienCellSynchronizerUserName("alien_cell_synchronizer");
+const TString QueueAgentUserName("queue_agent");
+const TString YqlAgentUserName("yql_agent");
+const TString TabletBalancerUserName("tablet_balancer");
+
+const TString EveryoneGroupName("everyone");
+const TString UsersGroupName("users");
+const TString SuperusersGroupName("superusers");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
diff --git a/yt/yt/client/security_client/public.h b/yt/yt/client/security_client/public.h
new file mode 100644
index 0000000000..ffda3af757
--- /dev/null
+++ b/yt/yt/client/security_client/public.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NSecurityClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TAccountId = NObjectClient::TObjectId;
+using TSubjectId = NObjectClient::TObjectId;
+using TUserId = NObjectClient::TObjectId;
+using TGroupId = NObjectClient::TObjectId;
+using TNetworkProjectId = NObjectClient::TObjectId;
+using TProxyRoleId = NObjectClient::TObjectId;
+using TAccountResourceUsageLeaseId = NObjectClient::TObjectId;
+
+extern const TString RootAccountName;
+extern const TString RootAccountCypressPath;
+extern const TString TmpAccountName;
+extern const TString SysAccountName;
+extern const TString IntermediateAccountName;
+extern const TString ChunkWiseAccountingMigrationAccountName;
+extern const TString SequoiaAccountName;
+
+using NRpc::RootUserName;
+extern const TString GuestUserName;
+extern const TString JobUserName;
+extern const TString SchedulerUserName;
+extern const TString FileCacheUserName;
+extern const TString OperationsCleanerUserName;
+extern const TString OperationsClientUserName;
+extern const TString TabletCellChangeloggerUserName;
+extern const TString TabletCellSnapshotterUserName;
+extern const TString TableMountInformerUserName;
+extern const TString AlienCellSynchronizerUserName;
+extern const TString QueueAgentUserName;
+extern const TString YqlAgentUserName;
+extern const TString TabletBalancerUserName;
+
+extern const TString EveryoneGroupName;
+extern const TString UsersGroupName;
+extern const TString SuperusersGroupName;
+extern const TString ReplicatorUserName;
+extern const TString OwnerUserName;
+
+using TSecurityTag = TString;
+constexpr int MaxSecurityTagLength = 128;
+
+DEFINE_ENUM(ESecurityAction,
+ ((Undefined)(0)) // Intermediate state, used internally.
+ ((Allow) (1)) // Let'em go!
+ ((Deny) (2)) // No way!
+);
+
+DEFINE_ENUM(EAceInheritanceMode,
+ ((ObjectAndDescendants) (0)) // ACE applies both to the object itself and its descendants.
+ ((ObjectOnly) (1)) // ACE applies to the object only.
+ ((DescendantsOnly) (2)) // ACE applies to descendants only.
+ ((ImmediateDescendantsOnly)(3)) // ACE applies to immediate (direct) descendants only.
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((AuthenticationError) (900))
+ ((AuthorizationError) (901))
+ ((AccountLimitExceeded) (902))
+ ((UserBanned) (903))
+ ((RequestQueueSizeLimitExceeded)(904))
+ ((NoSuchAccount) (905))
+ ((NoSuchSubject) (907))
+ ((SafeModeEnabled) (906))
+ ((AlreadyPresentInGroup) (908))
+);
+
+// NB: Changing this list requires reign promotion.
+DEFINE_ENUM(EProxyKind,
+ ((Http) (1))
+ ((Rpc) (2))
+);
+
+DEFINE_ENUM(EAccessControlObjectNamespace,
+ (AdminCommands)
+);
+
+DEFINE_ENUM(EAccessControlObject,
+ (DisableChunkLocations)
+ (DestroyChunkLocations)
+ (ResurrectChunkLocations)
+ (BuildSnapshot)
+ (BuildMasterSnapshot)
+ (SwitchLeader)
+ (RequestReboot)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSecurityClient
+
diff --git a/yt/yt/client/table_client/adapters.cpp b/yt/yt/client/table_client/adapters.cpp
new file mode 100644
index 0000000000..23028fc71c
--- /dev/null
+++ b/yt/yt/client/table_client/adapters.cpp
@@ -0,0 +1,234 @@
+#include "adapters.h"
+#include "row_batch.h"
+
+#include <yt/yt/client/api/table_writer.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+#include <yt/yt/core/concurrency/throughput_throttler.h>
+#include <yt/yt/core/concurrency/periodic_yielder.h>
+
+namespace NYT::NTableClient {
+
+using namespace NApi;
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TApiFromSchemalessWriterAdapter
+ : public NApi::ITableWriter
+{
+public:
+ explicit TApiFromSchemalessWriterAdapter(IUnversionedWriterPtr underlyingWriter)
+ : UnderlyingWriter_(std::move(underlyingWriter))
+ { }
+
+ bool Write(TRange<NTableClient::TUnversionedRow> rows) override
+ {
+ return UnderlyingWriter_->Write(rows);
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ return UnderlyingWriter_->GetReadyEvent();
+ }
+
+ TFuture<void> Close() override
+ {
+ return UnderlyingWriter_->Close();
+ }
+
+ const NTableClient::TNameTablePtr& GetNameTable() const override
+ {
+ return UnderlyingWriter_->GetNameTable();
+ }
+
+ const NTableClient::TTableSchemaPtr& GetSchema() const override
+ {
+ return UnderlyingWriter_->GetSchema();
+ }
+
+private:
+ const IUnversionedWriterPtr UnderlyingWriter_;
+};
+
+NApi::ITableWriterPtr CreateApiFromSchemalessWriterAdapter(
+ IUnversionedWriterPtr underlyingWriter)
+{
+ return New<TApiFromSchemalessWriterAdapter>(std::move(underlyingWriter));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemalessApiFromWriterAdapter
+ : public IUnversionedWriter
+{
+public:
+ explicit TSchemalessApiFromWriterAdapter(NApi::ITableWriterPtr underlyingWriter)
+ : UnderlyingWriter_(std::move(underlyingWriter))
+ { }
+
+ bool Write(TRange<NTableClient::TUnversionedRow> rows) override
+ {
+ return UnderlyingWriter_->Write(rows);
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ return UnderlyingWriter_->GetReadyEvent();
+ }
+
+ TFuture<void> Close() override
+ {
+ return UnderlyingWriter_->Close();
+ }
+
+ const NTableClient::TNameTablePtr& GetNameTable() const override
+ {
+ return UnderlyingWriter_->GetNameTable();
+ }
+
+ const NTableClient::TTableSchemaPtr& GetSchema() const override
+ {
+ return UnderlyingWriter_->GetSchema();
+ }
+
+private:
+ const NApi::ITableWriterPtr UnderlyingWriter_;
+};
+
+IUnversionedWriterPtr CreateSchemalessFromApiWriterAdapter(
+ NApi::ITableWriterPtr underlyingWriter)
+{
+ return New<TSchemalessApiFromWriterAdapter>(std::move(underlyingWriter));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void PipeReaderToWriter(
+ const ITableReaderPtr& reader,
+ const IUnversionedRowsetWriterPtr& writer,
+ const TPipeReaderToWriterOptions& options)
+{
+ TPeriodicYielder yielder(TDuration::Seconds(1));
+
+ TRowBatchReadOptions readOptions{
+ .MaxRowsPerRead = options.BufferRowCount,
+ .MaxDataWeightPerRead = options.BufferDataWeight
+ };
+ while (auto batch = reader->Read(readOptions)) {
+ yielder.TryYield();
+
+ if (batch->IsEmpty()) {
+ WaitFor(reader->GetReadyEvent())
+ .ThrowOnError();
+ continue;
+ }
+
+ auto rows = batch->MaterializeRows();
+
+ if (options.ValidateValues) {
+ for (auto row : rows) {
+ for (const auto& value : row) {
+ ValidateStaticValue(value);
+ }
+ }
+ }
+
+ if (options.Throttler) {
+ i64 dataWeight = 0;
+ for (auto row : rows) {
+ dataWeight += GetDataWeight(row);
+ }
+ WaitFor(options.Throttler->Throttle(dataWeight))
+ .ThrowOnError();
+ }
+
+ if (!rows.empty() && options.PipeDelay) {
+ TDelayedExecutor::WaitForDuration(options.PipeDelay);
+ }
+
+ if (!writer->Write(rows)) {
+ WaitFor(writer->GetReadyEvent())
+ .ThrowOnError();
+ }
+ }
+
+ WaitFor(writer->Close())
+ .ThrowOnError();
+}
+
+void PipeReaderToWriterByBatches(
+ const ITableReaderPtr& reader,
+ const NFormats::ISchemalessFormatWriterPtr& writer,
+ const TRowBatchReadOptions& options)
+{
+ TPeriodicYielder yielder(TDuration::Seconds(1));
+
+ while (auto batch = reader->Read(options)) {
+ yielder.TryYield();
+
+ if (batch->IsEmpty()) {
+ WaitFor(reader->GetReadyEvent())
+ .ThrowOnError();
+ continue;
+ }
+
+ if (!writer->WriteBatch(batch)) {
+ WaitFor(writer->GetReadyEvent())
+ .ThrowOnError();
+ }
+ }
+
+ WaitFor(writer->Close())
+ .ThrowOnError();
+}
+
+void PipeInputToOutput(
+ IInputStream* input,
+ IOutputStream* output,
+ i64 bufferBlockSize)
+{
+ struct TWriteBufferTag { };
+ TBlob buffer(GetRefCountedTypeCookie<TWriteBufferTag>(), bufferBlockSize, /*initializeStorage*/ false);
+
+ TPeriodicYielder yielder(TDuration::Seconds(1));
+
+ while (true) {
+ yielder.TryYield();
+
+ size_t length = input->Read(buffer.Begin(), buffer.Size());
+ if (length == 0) {
+ break;
+ }
+
+ output->Write(buffer.Begin(), length);
+ }
+
+ output->Finish();
+}
+
+void PipeInputToOutput(
+ const NConcurrency::IAsyncInputStreamPtr& input,
+ IOutputStream* output,
+ i64 bufferBlockSize)
+{
+ struct TWriteBufferTag { };
+ auto buffer = TSharedMutableRef::Allocate<TWriteBufferTag>(bufferBlockSize, {.InitializeStorage = false});
+
+ while (true) {
+ auto length = WaitFor(input->Read(buffer))
+ .ValueOrThrow();
+
+ if (length == 0) {
+ break;
+ }
+
+ output->Write(buffer.Begin(), length);
+ }
+
+ output->Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/adapters.h b/yt/yt/client/table_client/adapters.h
new file mode 100644
index 0000000000..b2baeb3486
--- /dev/null
+++ b/yt/yt/client/table_client/adapters.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "public.h"
+#include "unversioned_writer.h"
+
+#include <yt/yt/client/api/table_reader.h>
+
+#include <yt/yt/client/formats/format.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+IUnversionedWriterPtr CreateSchemalessFromApiWriterAdapter(
+ NApi::ITableWriterPtr underlyingWriter);
+
+NApi::ITableWriterPtr CreateApiFromSchemalessWriterAdapter(
+ IUnversionedWriterPtr underlyingWriter);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPipeReaderToWriterOptions
+{
+ i64 BufferRowCount = 10000;
+ i64 BufferDataWeight = 16_MB;
+ bool ValidateValues = false;
+ NConcurrency::IThroughputThrottlerPtr Throttler;
+ // Used only for testing.
+ TDuration PipeDelay;
+};
+
+void PipeReaderToWriter(
+ const NApi::ITableReaderPtr& reader,
+ const IUnversionedRowsetWriterPtr& writer,
+ const TPipeReaderToWriterOptions& options);
+
+void PipeReaderToWriterByBatches(
+ const NApi::ITableReaderPtr& reader,
+ const NFormats::ISchemalessFormatWriterPtr& writer,
+ const TRowBatchReadOptions& options);
+
+void PipeInputToOutput(
+ IInputStream* input,
+ IOutputStream* output,
+ i64 bufferBlockSize);
+
+void PipeInputToOutput(
+ const NConcurrency::IAsyncInputStreamPtr& input,
+ IOutputStream* output,
+ i64 bufferBlockSize);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/blob_reader.cpp b/yt/yt/client/table_client/blob_reader.cpp
new file mode 100644
index 0000000000..f40e28f031
--- /dev/null
+++ b/yt/yt/client/table_client/blob_reader.cpp
@@ -0,0 +1,219 @@
+#include "blob_reader.h"
+
+#include "name_table.h"
+#include "schema.h"
+
+#include <yt/yt/client/table_client/comparator.h>
+#include <yt/yt/client/table_client/row_batch.h>
+#include <yt/yt/client/table_client/unversioned_reader.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+namespace NYT::NTableClient {
+
+using namespace NApi;
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString TBlobTableSchema::PartIndexColumn = "part_index";
+const TString TBlobTableSchema::DataColumn = "data";
+
+TTableSchemaPtr TBlobTableSchema::ToTableSchema() const
+{
+ auto columns = BlobIdColumns;
+ for (auto& idColumn : columns) {
+ idColumn.SetSortOrder(ESortOrder::Ascending);
+ }
+ columns.emplace_back(PartIndexColumn, EValueType::Int64);
+ columns.back().SetSortOrder(ESortOrder::Ascending);
+ columns.emplace_back(DataColumn, EValueType::String);
+ return New<TTableSchema>(
+ std::move(columns),
+ true, // strict
+ true); // uniqueKeys
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EColumnType,
+ ((PartIndex) (0))
+ ((Data) (1))
+);
+
+class TBlobTableReader
+ : public IAsyncZeroCopyInputStream
+{
+public:
+ TBlobTableReader(
+ ITableReaderPtr reader,
+ const std::optional<TString>& partIndexColumnName,
+ const std::optional<TString>& dataColumnName,
+ i64 startPartIndex,
+ const std::optional<i64>& offset,
+ const std::optional<i64>& partSize)
+ : Reader_(std::move(reader))
+ , PartIndexColumnName_(partIndexColumnName ? *partIndexColumnName : TBlobTableSchema::PartIndexColumn)
+ , DataColumnName_(dataColumnName ? *dataColumnName : TBlobTableSchema::DataColumn)
+ , Offset_(offset.value_or(0))
+ , PartSize_(partSize)
+ , PreviousPartSize_(partSize)
+ , NextPartIndex_(startPartIndex)
+ {
+ ColumnIndex_[EColumnType::PartIndex] = Reader_->GetNameTable()->GetIdOrRegisterName(PartIndexColumnName_);
+ ColumnIndex_[EColumnType::Data] = Reader_->GetNameTable()->GetIdOrRegisterName(DataColumnName_);
+ }
+
+ TFuture<TSharedRef> Read() override
+ {
+ if (!Batch_ || Index_ >= Batch_->GetRowCount()) {
+ Index_ = 0;
+
+ NTableClient::TRowBatchReadOptions options{
+ .MaxRowsPerRead = 1
+ };
+ Batch_ = Reader_->Read(options);
+
+ if (!Batch_) {
+ return MakeFuture<TSharedRef>(TSharedRef());
+ }
+
+ if (Batch_->IsEmpty()) {
+ return Reader_->GetReadyEvent().Apply(
+ BIND(&TBlobTableReader::Read, MakeStrong(this)));
+ }
+ }
+ return MakeFuture(ProcessRow());
+ }
+
+private:
+ const ITableReaderPtr Reader_;
+ const TString PartIndexColumnName_;
+ const TString DataColumnName_;
+
+ i64 Offset_;
+ std::optional<i64> PartSize_;
+ std::optional<i64> PreviousPartSize_;
+
+ IUnversionedRowBatchPtr Batch_;
+ i64 Index_ = 0;
+ i64 NextPartIndex_;
+
+ TEnumIndexedVector<EColumnType, std::optional<size_t>> ColumnIndex_;
+
+ TSharedRef ProcessRow()
+ {
+ auto row = Batch_->MaterializeRows()[Index_++];
+ auto value = GetDataAndValidateRow(row);
+
+ auto holder = MakeSharedRangeHolder(Reader_);
+ auto result = TSharedRef(value.Data.String, value.Length, std::move(holder));
+ if (Offset_ > 0) {
+ if (Offset_ > std::ssize(result)) {
+ THROW_ERROR_EXCEPTION("Offset is out of bounds")
+ << TErrorAttribute("offset", Offset_)
+ << TErrorAttribute("part_size", result.Size())
+ << TErrorAttribute("part_index", NextPartIndex_ - 1);
+ }
+ result = result.Slice(result.Begin() + Offset_, result.End());
+ Offset_ = 0;
+ }
+ return result;
+ }
+
+ TUnversionedValue GetAndValidateValue(
+ TUnversionedRow row,
+ const TString& name,
+ EColumnType columnType,
+ EValueType expectedType)
+ {
+ auto columnIndex = ColumnIndex_[columnType];
+ if (!columnIndex) {
+ THROW_ERROR_EXCEPTION("Column %Qv not found", name);
+ }
+
+ TUnversionedValue columnValue;
+ bool found = false;
+ // NB: It is impossible to determine column index fast in schemaless reader.
+ for (const auto& value : row) {
+ if (value.Id == *columnIndex) {
+ columnValue = value;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ THROW_ERROR_EXCEPTION("Column %Qv not found", name);
+ }
+
+ if (columnValue.Type != expectedType) {
+ THROW_ERROR_EXCEPTION("Column %Qv must be of type %Qlv but has type %Qlv",
+ name,
+ expectedType,
+ columnValue.Type);
+ }
+
+ return columnValue;
+ }
+
+ TUnversionedValue GetDataAndValidateRow(TUnversionedRow row)
+ {
+ auto partIndexValue = GetAndValidateValue(row, PartIndexColumnName_, EColumnType::PartIndex, EValueType::Int64);
+ auto partIndex = partIndexValue.Data.Int64;
+
+ if (partIndex != NextPartIndex_) {
+ THROW_ERROR_EXCEPTION("Values of column %Qv must be consecutive but values %v and %v violate this property",
+ PartIndexColumnName_,
+ NextPartIndex_,
+ partIndex);
+ }
+
+ NextPartIndex_ = partIndex + 1;
+
+ auto value = GetAndValidateValue(row, DataColumnName_, EColumnType::Data, EValueType::String);
+
+ auto isPreviousPartWrong = PartSize_ && *PreviousPartSize_ != *PartSize_;
+ auto isCurrentPartWrong = PartSize_ && value.Length > *PartSize_;
+ if (isPreviousPartWrong || isCurrentPartWrong) {
+ i64 actualSize;
+ i64 wrongPartIndex;
+ if (isPreviousPartWrong) {
+ actualSize = *PreviousPartSize_;
+ wrongPartIndex = partIndex - 1;
+ } else {
+ actualSize = value.Length;
+ wrongPartIndex = partIndex;
+ }
+
+ THROW_ERROR_EXCEPTION("Inconsistent part size")
+ << TErrorAttribute("expected_size", *PartSize_)
+ << TErrorAttribute("actual_size", actualSize)
+ << TErrorAttribute("part_index", wrongPartIndex);
+ }
+ PreviousPartSize_ = value.Length;
+ return value;
+ }
+};
+
+IAsyncZeroCopyInputStreamPtr CreateBlobTableReader(
+ ITableReaderPtr reader,
+ const std::optional<TString>& partIndexColumnName,
+ const std::optional<TString>& dataColumnName,
+ i64 startPartIndex,
+ const std::optional<i64>& offset,
+ const std::optional<i64>& partSize)
+{
+ return New<TBlobTableReader>(
+ std::move(reader),
+ partIndexColumnName,
+ dataColumnName,
+ startPartIndex,
+ offset,
+ partSize);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/blob_reader.h b/yt/yt/client/table_client/blob_reader.h
new file mode 100644
index 0000000000..71f8810341
--- /dev/null
+++ b/yt/yt/client/table_client/blob_reader.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/api/table_reader.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBlobTableSchema
+{
+ // Names of special blob columns.
+ static const TString PartIndexColumn;
+ static const TString DataColumn;
+
+ // Do not specify anything except name and value
+ // type in all column schemas.
+ std::vector<TColumnSchema> BlobIdColumns;
+
+ TTableSchemaPtr ToTableSchema() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+NConcurrency::IAsyncZeroCopyInputStreamPtr CreateBlobTableReader(
+ NApi::ITableReaderPtr reader,
+ const std::optional<TString>& partIndexColumnName,
+ const std::optional<TString>& dataColumnName,
+ i64 startPartIndex,
+ const std::optional<i64>& offset = std::nullopt,
+ const std::optional<i64>& partSize = std::nullopt);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/check_schema_compatibility.cpp b/yt/yt/client/table_client/check_schema_compatibility.cpp
new file mode 100644
index 0000000000..1541aaa777
--- /dev/null
+++ b/yt/yt/client/table_client/check_schema_compatibility.cpp
@@ -0,0 +1,235 @@
+#include "check_schema_compatibility.h"
+#include "logical_type.h"
+#include "schema.h"
+#include "comparator.h"
+
+#include <yt/yt/client/complex_types/check_type_compatibility.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::pair<ESchemaCompatibility, TError> CheckTableSchemaCompatibilityImpl(
+ const TTableSchema& inputSchema,
+ const TTableSchema& outputSchema,
+ bool ignoreSortOrder)
+{
+ // If output schema is strict, check that input columns are subset of output columns.
+ if (outputSchema.GetStrict()) {
+ if (!inputSchema.GetStrict()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Incompatible strictness: input schema is not strict while output schema is"),
+ };
+ }
+
+ for (const auto& inputColumn : inputSchema.Columns()) {
+ if (!outputSchema.FindColumn(inputColumn.Name())) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Column %v is found in input schema but is missing in output schema",
+ inputColumn.GetDiagnosticNameString()),
+ };
+ }
+ }
+ }
+
+ auto result = std::pair(ESchemaCompatibility::FullyCompatible, TError());
+
+ // Check that columns are the same.
+ for (const auto& outputColumn : outputSchema.Columns()) {
+ const auto* inputColumn = inputSchema.FindColumn(outputColumn.Name());
+ const auto* deletedColumn = inputSchema.FindDeletedColumn(outputColumn.StableName());
+
+ if (deletedColumn) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Column %v in output schema was deleted in the input schema",
+ outputColumn.GetDiagnosticNameString()),
+ };
+ }
+
+ if (inputColumn) {
+ if (inputColumn->StableName() != outputColumn.StableName()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Column %Qv has stable name %Qv in input and %Qv in output schema",
+ inputColumn->Name(),
+ inputColumn->StableName().Get(),
+ outputColumn.StableName().Get())
+ };
+ }
+
+ auto currentTypeCompatibility = NComplexTypes::CheckTypeCompatibility(
+ inputColumn->LogicalType(), outputColumn.LogicalType());
+
+ if (currentTypeCompatibility.first < result.first) {
+ result = {
+ currentTypeCompatibility.first,
+ TError("Column %v input type is incompatible with output type",
+ inputColumn->GetDiagnosticNameString())
+ << currentTypeCompatibility.second
+ };
+ }
+
+ if (result.first == ESchemaCompatibility::Incompatible) {
+ break;
+ }
+
+ if (outputColumn.Expression() && inputColumn->Expression() != outputColumn.Expression()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Column %v expression mismatch",
+ inputColumn->GetDiagnosticNameString()),
+ };
+ }
+ if (outputColumn.Aggregate() && inputColumn->Aggregate() != outputColumn.Aggregate()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Column %v aggregate mismatch",
+ inputColumn->GetDiagnosticNameString()),
+ };
+ }
+ } else if (outputColumn.Expression()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Unexpected computed column %v in output schema",
+ outputColumn.GetDiagnosticNameString()),
+ };
+ } else if (!inputSchema.GetStrict()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Column %v is present in output schema and is missing in non-strict input schema",
+ outputColumn.GetDiagnosticNameString()),
+ };
+ } else if (outputColumn.Required()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Required column %v is present in output schema and is missing in input schema",
+ outputColumn.GetDiagnosticNameString()),
+ };
+ }
+ }
+
+ for (const auto& deletedOutputColumn : outputSchema.DeletedColumns()) {
+ const auto& stableName = deletedOutputColumn.StableName();
+ const auto* inputColumn = inputSchema.FindColumnByStableName(stableName);
+ const auto* deletedColumn = inputSchema.FindDeletedColumn(stableName);
+
+ if (!inputColumn && !deletedColumn) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Deleted column \"%v\" is missing in the input schema",
+ deletedOutputColumn.StableName().Get())
+ };
+ }
+ }
+
+ for (const auto& deletedInputColumn : inputSchema.DeletedColumns()) {
+ const auto& stableName = deletedInputColumn.StableName();
+ const auto* deletedOutputColumn = outputSchema.FindDeletedColumn(stableName);
+ if (!deletedOutputColumn) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Deleted column \"%v\" must be deleted in the output schema",
+ deletedInputColumn.StableName().Get())
+ };
+ }
+ }
+
+ // Check that we don't lose complex types.
+ // We never want to teleport complex types to schemaless part of the chunk because we want to change their type from
+ // EValueType::Composite to EValueType::Any.
+ if (!outputSchema.GetStrict()) {
+ for (const auto& inputColumn : inputSchema.Columns()) {
+ if (!IsV3Composite(inputColumn.LogicalType())) {
+ continue;
+ }
+ if (!outputSchema.FindColumn(inputColumn.Name())) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Column %v of input schema with complex type %Qv is missing in strict part of output schema",
+ inputColumn.GetDiagnosticNameString(),
+ *inputColumn.LogicalType()),
+ };
+ }
+ }
+ }
+
+ if (ignoreSortOrder) {
+ return result;
+ }
+
+ // Check that output key columns form a prefix of input key columns.
+ int cmp = outputSchema.GetKeyColumnCount() - inputSchema.GetKeyColumnCount();
+ if (cmp > 0) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Output key columns are wider than input key columns"),
+ };
+ }
+
+ if (outputSchema.GetUniqueKeys()) {
+ if (!inputSchema.GetUniqueKeys()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Input schema \"unique_keys\" attribute is false"),
+ };
+ }
+ if (cmp != 0) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Input key columns are wider than output key columns"),
+ };
+ }
+ }
+
+ auto inputKeySchema = inputSchema.ToKeys();
+ auto outputKeySchema = outputSchema.ToKeys();
+
+ for (int index = 0; index < outputKeySchema->GetColumnCount(); ++index) {
+ const auto& inputColumn = inputKeySchema->Columns()[index];
+ const auto& outputColumn = outputKeySchema->Columns()[index];
+ if (inputColumn.StableName() != outputColumn.StableName()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Key columns do not match: input column %v, output column %v",
+ inputColumn.GetDiagnosticNameString(),
+ outputColumn.GetDiagnosticNameString())
+ };
+ }
+ if (inputColumn.SortOrder() != outputColumn.SortOrder()) {
+ return {
+ ESchemaCompatibility::Incompatible,
+ TError("Sort order of column %v does not match: input sort order %Qlv, output sort order %Qlv",
+ inputColumn.GetDiagnosticNameString(),
+ inputColumn.SortOrder(),
+ outputColumn.SortOrder())
+ };
+ }
+ }
+
+ return result;
+}
+
+std::pair<ESchemaCompatibility, TError> CheckTableSchemaCompatibility(
+ const TTableSchema& inputSchema,
+ const TTableSchema& outputSchema,
+ bool ignoreSortOrder)
+{
+ auto result = CheckTableSchemaCompatibilityImpl(inputSchema, outputSchema, ignoreSortOrder);
+ if (result.first != ESchemaCompatibility::FullyCompatible) {
+ result.second = TError(NTableClient::EErrorCode::IncompatibleSchemas, "Table schemas are incompatible")
+ << result.second
+ << TErrorAttribute("input_table_schema", inputSchema)
+ << TErrorAttribute("output_table_schema", outputSchema);
+ }
+ return result;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/check_schema_compatibility.h b/yt/yt/client/table_client/check_schema_compatibility.h
new file mode 100644
index 0000000000..0dac7cb20b
--- /dev/null
+++ b/yt/yt/client/table_client/check_schema_compatibility.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "public.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Validates that values from table with inputSchema also match outputSchema.
+//
+// Result pair contains following elements:
+// 1. Level of compatibility of the given schemas.
+// 2. If schemas are fully compatible error is empty otherwise it contains description
+// of incompatibility.
+std::pair<ESchemaCompatibility, TError> CheckTableSchemaCompatibility(
+ const TTableSchema& inputSchema,
+ const TTableSchema& outputSchema,
+ bool ignoreSortOrder);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/chunk_stripe_statistics.cpp b/yt/yt/client/table_client/chunk_stripe_statistics.cpp
new file mode 100644
index 0000000000..829220c548
--- /dev/null
+++ b/yt/yt/client/table_client/chunk_stripe_statistics.cpp
@@ -0,0 +1,68 @@
+#include "chunk_stripe_statistics.h"
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkStripeStatistics::Persist(const NTableClient::TPersistenceContext& context)
+{
+ using NYT::Persist;
+ Persist(context, ChunkCount);
+ Persist(context, DataWeight);
+ Persist(context, RowCount);
+ Persist(context, ValueCount);
+ Persist(context, MaxBlockSize);
+}
+
+TChunkStripeStatistics operator + (
+ const TChunkStripeStatistics& lhs,
+ const TChunkStripeStatistics& rhs)
+{
+ TChunkStripeStatistics result;
+ result.ChunkCount = lhs.ChunkCount + rhs.ChunkCount;
+ result.DataWeight = lhs.DataWeight + rhs.DataWeight;
+ result.RowCount = lhs.RowCount + rhs.RowCount;
+ result.ValueCount = lhs.ValueCount + rhs.ValueCount;
+ result.MaxBlockSize = std::max(lhs.MaxBlockSize, rhs.MaxBlockSize);
+ return result;
+}
+
+TChunkStripeStatistics& operator += (
+ TChunkStripeStatistics& lhs,
+ const TChunkStripeStatistics& rhs)
+{
+ lhs.ChunkCount += rhs.ChunkCount;
+ lhs.DataWeight += rhs.DataWeight;
+ lhs.RowCount += rhs.RowCount;
+ lhs.ValueCount += rhs.ValueCount;
+ lhs.MaxBlockSize = std::max(lhs.MaxBlockSize, rhs.MaxBlockSize);
+ return lhs;
+}
+
+TChunkStripeStatisticsVector AggregateStatistics(
+ const TChunkStripeStatisticsVector& statistics)
+{
+ TChunkStripeStatistics sum;
+ for (const auto& stat : statistics) {
+ sum += stat;
+ }
+ return TChunkStripeStatisticsVector(1, sum);
+}
+
+void Serialize(const TChunkStripeStatistics& statistics, NYson::IYsonConsumer* consumer)
+{
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("chunk_count").Value(statistics.ChunkCount)
+ .Item("data_weight").Value(statistics.DataWeight)
+ .Item("row_count").Value(statistics.RowCount)
+ .OptionalItem("value_count", statistics.ValueCount)
+ .OptionalItem("max_block_size", statistics.MaxBlockSize)
+ .EndMap();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/chunk_stripe_statistics.h b/yt/yt/client/table_client/chunk_stripe_statistics.h
new file mode 100644
index 0000000000..b517ab765e
--- /dev/null
+++ b/yt/yt/client/table_client/chunk_stripe_statistics.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "public.h"
+#include "serialize.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TChunkStripeStatistics
+{
+ int ChunkCount = 0;
+ i64 DataWeight = 0;
+ i64 RowCount = 0;
+ i64 ValueCount = 0;
+ i64 MaxBlockSize = 0;
+
+ void Persist(const TPersistenceContext& context);
+};
+
+TChunkStripeStatistics operator + (
+ const TChunkStripeStatistics& lhs,
+ const TChunkStripeStatistics& rhs);
+
+TChunkStripeStatistics& operator += (
+ TChunkStripeStatistics& lhs,
+ const TChunkStripeStatistics& rhs);
+
+using TChunkStripeStatisticsVector = TCompactVector<TChunkStripeStatistics, 1>;
+
+//! Adds up input statistics and returns a single-item vector with the sum.
+TChunkStripeStatisticsVector AggregateStatistics(
+ const TChunkStripeStatisticsVector& statistics);
+
+void Serialize(const TChunkStripeStatistics& statistics, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/column_rename_descriptor.cpp b/yt/yt/client/table_client/column_rename_descriptor.cpp
new file mode 100644
index 0000000000..ca9c9313cf
--- /dev/null
+++ b/yt/yt/client/table_client/column_rename_descriptor.cpp
@@ -0,0 +1,53 @@
+#include "column_rename_descriptor.h"
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/yson/consumer.h>
+#include <yt/yt/core/yson/parser.h>
+
+#include <yt/yt/core/ytree/node.h>
+#include <yt/yt/core/ytree/serialize.h>
+
+namespace NYT::NTableClient {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Deserialize(TColumnRenameDescriptors& value, INodePtr node)
+{
+ auto mapNode = node->AsMap();
+ value.clear();
+ for (const auto& [key, child] : mapNode->GetChildren()) {
+ value.push_back(TColumnRenameDescriptor());
+ value.back().OriginalName = key;
+ Deserialize(value.back().NewName, child);
+ }
+}
+
+void Serialize(const TColumnRenameDescriptors& value, IYsonConsumer* consumer)
+{
+ consumer->OnBeginMap();
+ for (const auto& descriptor : value) {
+ consumer->OnKeyedItem(descriptor.OriginalName);
+ consumer->OnStringScalar(descriptor.NewName);
+ }
+ consumer->OnEndMap();
+}
+
+void ToProto(NProto::TColumnRenameDescriptor* protoDescriptor, const TColumnRenameDescriptor& descriptor)
+{
+ protoDescriptor->set_original_name(descriptor.OriginalName);
+ protoDescriptor->set_new_name(descriptor.NewName);
+}
+
+void FromProto(TColumnRenameDescriptor* descriptor, const NProto::TColumnRenameDescriptor& protoDescriptor)
+{
+ descriptor->OriginalName = protoDescriptor.original_name();
+ descriptor->NewName = protoDescriptor.new_name();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/column_rename_descriptor.h b/yt/yt/client/table_client/column_rename_descriptor.h
new file mode 100644
index 0000000000..8fc277e54d
--- /dev/null
+++ b/yt/yt/client/table_client/column_rename_descriptor.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TColumnRenameDescriptor
+{
+ TString OriginalName;
+ TString NewName;
+};
+
+void Deserialize(TColumnRenameDescriptors& value, NYTree::INodePtr node);
+void Serialize(const TColumnRenameDescriptors& value, NYson::IYsonConsumer* consumer);
+
+void ToProto(NProto::TColumnRenameDescriptor* protoDescriptor, const TColumnRenameDescriptor& descriptor);
+void FromProto(TColumnRenameDescriptor* descriptor, const NProto::TColumnRenameDescriptor& protoDescriptor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/column_sort_schema.cpp b/yt/yt/client/table_client/column_sort_schema.cpp
new file mode 100644
index 0000000000..af14339f88
--- /dev/null
+++ b/yt/yt/client/table_client/column_sort_schema.cpp
@@ -0,0 +1,153 @@
+#include "column_sort_schema.h"
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/misc/serialize.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NTableClient {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TColumnSortSchema::Persist(const TStreamPersistenceContext& context)
+{
+ using NYT::Persist;
+
+ Persist(context, Name);
+ Persist(context, SortOrder);
+}
+
+void Serialize(const TColumnSortSchema& schema, IYsonConsumer* consumer)
+{
+ // COMPAT(gritukan): Serializing columns with ESortOrder::Ascending as map node
+ // will end up with a disaster during 21.1 -> 20.3 CA rollback. Remove this code
+ // when 21.1 will be stable.
+ if (schema.SortOrder == ESortOrder::Ascending) {
+ consumer->OnStringScalar(schema.Name);
+ } else {
+ BuildYsonFluently(consumer).BeginMap()
+ .Item("name").Value(schema.Name)
+ .Item("sort_order").Value(schema.SortOrder)
+ .EndMap();
+ }
+}
+
+void Deserialize(TColumnSortSchema& schema, INodePtr node)
+{
+ if (node->GetType() == ENodeType::Map) {
+ auto mapNode = node->AsMap();
+ Deserialize(schema.Name, mapNode->GetChildOrThrow("name"));
+ Deserialize(schema.SortOrder, mapNode->GetChildOrThrow("sort_order"));
+ } else if (node->GetType() == ENodeType::String) {
+ Deserialize(schema.Name, node);
+ schema.SortOrder = ESortOrder::Ascending;
+ } else {
+ THROW_ERROR_EXCEPTION("Unexpected type of column sort schema node; expected \"string\" or \"map\", %Qv found",
+ node->GetType());
+ }
+}
+
+void Deserialize(TColumnSortSchema& schema, TYsonPullParserCursor* cursor)
+{
+ Deserialize(schema, ExtractTo<INodePtr>(cursor));
+}
+
+bool operator == (const TColumnSortSchema& lhs, const TColumnSortSchema& rhs)
+{
+ return lhs.Name == rhs.Name && lhs.SortOrder == rhs.SortOrder;
+}
+
+bool operator != (const TColumnSortSchema& lhs, const TColumnSortSchema& rhs)
+{
+ return !(lhs == rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateSortColumns(const std::vector<TColumnSortSchema>& columns)
+{
+ ValidateKeyColumnCount(columns.size());
+
+ THashSet<TString> names;
+ for (const auto& column : columns) {
+ if (!names.insert(column.Name).second) {
+ THROW_ERROR_EXCEPTION("Duplicate sort column name %Qv",
+ column.Name);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(
+ NProto::TSortColumnsExt* protoSortColumns,
+ const TSortColumns& sortColumns)
+{
+ for (const auto& sortColumn : sortColumns) {
+ protoSortColumns->add_names(sortColumn.Name);
+ protoSortColumns->add_sort_orders(static_cast<int>(sortColumn.SortOrder));
+ }
+}
+
+void FromProto(
+ TSortColumns* sortColumns,
+ const NProto::TSortColumnsExt& protoSortColumns)
+{
+ YT_VERIFY(protoSortColumns.names_size() == protoSortColumns.sort_orders_size());
+ for (int columnIndex = 0; columnIndex < protoSortColumns.names_size(); ++columnIndex) {
+ TColumnSortSchema sortColumn{
+ .Name = protoSortColumns.names(columnIndex),
+ .SortOrder = CheckedEnumCast<ESortOrder>(protoSortColumns.sort_orders(columnIndex))
+ };
+ sortColumns->push_back(sortColumn);
+ }
+}
+
+void FormatValue(TStringBuilderBase* builder, const TSortColumns& sortColumns, TStringBuf /* format */)
+{
+ builder->AppendFormat("{ColumnNames: %v, Comparator: %v}",
+ GetColumnNames(sortColumns),
+ GetComparator(sortColumns));
+}
+
+TString ToString(const TSortColumns& sortColumns)
+{
+ return ToStringViaBuilder(sortColumns);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyColumns GetColumnNames(const TSortColumns& sortColumns)
+{
+ TKeyColumns keyColumns;
+ keyColumns.reserve(sortColumns.size());
+ for (const auto& sortColumn : sortColumns) {
+ keyColumns.push_back(sortColumn.Name);
+ }
+
+ return keyColumns;
+}
+
+std::vector<ESortOrder> GetSortOrders(const TSortColumns& sortColumns)
+{
+ std::vector<ESortOrder> sortOrders;
+ sortOrders.reserve(sortColumns.size());
+ for (const auto& sortColumn : sortColumns) {
+ sortOrders.push_back(sortColumn.SortOrder);
+ }
+
+ return sortOrders;
+}
+
+TComparator GetComparator(const TSortColumns& sortColumns)
+{
+ return TComparator(GetSortOrders(sortColumns));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/column_sort_schema.h b/yt/yt/client/table_client/column_sort_schema.h
new file mode 100644
index 0000000000..cb9e7fc114
--- /dev/null
+++ b/yt/yt/client/table_client/column_sort_schema.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "public.h"
+#include "comparator.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TColumnSortSchema
+{
+ TString Name;
+ ESortOrder SortOrder;
+
+ void Persist(const TStreamPersistenceContext& context);
+};
+
+void Serialize(const TColumnSortSchema& schema, NYson::IYsonConsumer* consumer);
+void Deserialize(TColumnSortSchema& schema, NYTree::INodePtr node);
+void Deserialize(TColumnSortSchema& schema, NYson::TYsonPullParserCursor* cursor);
+
+bool operator == (const TColumnSortSchema& lhs, const TColumnSortSchema& rhs);
+bool operator != (const TColumnSortSchema& lhs, const TColumnSortSchema& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateSortColumns(const std::vector<TColumnSortSchema>& columns);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(
+ NProto::TSortColumnsExt* protoSortColumns,
+ const TSortColumns& sortColumns);
+
+void FromProto(
+ TSortColumns* sortColumns,
+ const NProto::TSortColumnsExt& protoSortColumns);
+
+void FormatValue(TStringBuilderBase* builder, const TSortColumns& key, TStringBuf format);
+TString ToString(const TSortColumns& key);
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyColumns GetColumnNames(const TSortColumns& sortColumns);
+
+std::vector<ESortOrder> GetSortOrders(const TSortColumns& sortColumns);
+
+TComparator GetComparator(const TSortColumns& sortColumns);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/columnar-inl.h b/yt/yt/client/table_client/columnar-inl.h
new file mode 100644
index 0000000000..4d05349d61
--- /dev/null
+++ b/yt/yt/client/table_client/columnar-inl.h
@@ -0,0 +1,378 @@
+#ifndef COLUMNAR_INL_H_
+#error "Direct inclusion of this file is not allowed, include columnar.h"
+// For the sake of sane code completion.
+#include "columnar.h"
+#endif
+
+#include <library/cpp/yt/coding/zig_zag.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline i64 GetBitmapByteSize(i64 bitCount)
+{
+ return (bitCount + 7) / 8;
+}
+
+inline i64 DecodeStringOffset(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ i64 index)
+{
+ YT_ASSERT(index >= 0 && index <= static_cast<i64>(offsets.Size()));
+ return index == 0
+ ? 0
+ : static_cast<ui32>(avgLength * index + ZigZagDecode64(offsets[index - 1]));
+}
+
+inline std::pair<i64, i64> DecodeStringRange(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ i64 index)
+{
+ if (Y_UNLIKELY(index == 0)) {
+ return {
+ 0,
+ static_cast<i64>(avgLength + ZigZagDecode64(offsets[0]))
+ };
+ } else {
+ auto avgLengthTimesIndex = static_cast<ui32>(avgLength * index);
+ return {
+ static_cast<i64>(avgLengthTimesIndex + ZigZagDecode64(offsets[index - 1])),
+ static_cast<i64>(avgLengthTimesIndex + avgLength + ZigZagDecode64(offsets[index]))
+ };
+ }
+}
+
+namespace {
+
+template <
+ bool WithDictionary,
+ bool WithBaseValue,
+ bool WithZigZag,
+ class T,
+ template <
+ bool WithBaseValue_,
+ bool WithZigZag_,
+ class T_
+ >
+ class TValueDecoder,
+ class TGetter,
+ class TConsumer
+>
+void DecodeVectorRleImpl(
+ i64 startIndex,
+ i64 endIndex,
+ ui64 baseValue,
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TGetter getter,
+ TConsumer consumer)
+{
+ auto startRleIndex = TranslateRleStartIndex(rleIndexes, startIndex);
+ auto currentIndex = startIndex;
+ auto currentRleIndex = startRleIndex;
+ T currentDecodedValue;
+ i64 thresholdIndex = -1;
+ while (true) {
+ if (currentIndex >= thresholdIndex) {
+ if (currentIndex >= endIndex) {
+ break;
+ }
+ decltype(getter(0)) currentValue;
+ if constexpr(WithDictionary) {
+ auto dictionaryIndex = dictionaryIndexes[currentRleIndex];
+ if (dictionaryIndex == 0) {
+ currentValue = {};
+ } else {
+ currentValue = getter(dictionaryIndex - 1);
+ }
+ } else {
+ currentValue = getter(currentRleIndex);
+ }
+ currentDecodedValue = TValueDecoder<WithBaseValue, WithZigZag, T>::Run(currentValue, baseValue);
+ ++currentRleIndex;
+ thresholdIndex = currentRleIndex < static_cast<i64>(rleIndexes.Size())
+ ? std::min(static_cast<i64>(rleIndexes[currentRleIndex]), endIndex)
+ : endIndex;
+ }
+
+ if (currentIndex + 4 <= thresholdIndex) {
+ // Unrolled loop.
+ consumer(currentDecodedValue);
+ consumer(currentDecodedValue);
+ consumer(currentDecodedValue);
+ consumer(currentDecodedValue);
+ currentIndex += 4;
+ } else {
+ consumer(currentDecodedValue);
+ ++currentIndex;
+ }
+ }
+}
+
+template <
+ bool WithDictionary,
+ bool WithBaseValue,
+ bool WithZigZag,
+ class T,
+ template <
+ bool WithBaseValue_,
+ bool WithZigZag_,
+ class T_
+ >
+ class TValueDecoder,
+ class TGetter,
+ class TConsumer
+>
+void DecodeVectorDirectImpl(
+ i64 startIndex,
+ i64 endIndex,
+ ui64 baseValue,
+ TRange<ui32> dictionaryIndexes,
+ TGetter getter,
+ TConsumer consumer)
+{
+ for (i64 index = startIndex; index < endIndex; ++index) {
+ decltype(getter(0)) value;
+ if constexpr(WithDictionary) {
+ auto dictionaryIndex = dictionaryIndexes[index];
+ if (dictionaryIndex == 0) {
+ value = {};
+ } else {
+ value = getter(dictionaryIndex - 1);
+ }
+ } else {
+ value = getter(index);
+ }
+ auto decodedValue = TValueDecoder<WithBaseValue, WithZigZag, T>::Run(value, baseValue);
+ consumer(decodedValue);
+ }
+}
+
+template <
+ bool WithRle,
+ bool WithDictionary,
+ bool WithBaseValue,
+ bool WithZigZag,
+ class T,
+ template <
+ bool WithBaseValue_,
+ bool WithZigZag_,
+ class T_
+ >
+ class TValueDecoder,
+ class TGetter,
+ class TConsumer
+>
+void DecodeVectorImpl(
+ i64 startIndex,
+ i64 endIndex,
+ ui64 baseValue,
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TGetter getter,
+ TConsumer consumer)
+{
+ if constexpr(WithRle) {
+ DecodeVectorRleImpl<WithDictionary, WithBaseValue, WithZigZag, T, TValueDecoder>(
+ startIndex,
+ endIndex,
+ baseValue,
+ dictionaryIndexes,
+ rleIndexes,
+ std::forward<TGetter>(getter),
+ std::forward<TConsumer>(consumer));
+ } else {
+ DecodeVectorDirectImpl<WithDictionary, WithBaseValue, WithZigZag, T, TValueDecoder>(
+ startIndex,
+ endIndex,
+ baseValue,
+ dictionaryIndexes,
+ std::forward<TGetter>(getter),
+ std::forward<TConsumer>(consumer));
+ }
+}
+
+template <
+ bool WithBaseValue,
+ bool WithZigZag,
+ class T
+>
+inline T DecodeIntegerValueImpl(
+ ui64 value,
+ ui64 baseValue)
+{
+ if constexpr(WithBaseValue) {
+ value += baseValue;
+ }
+ if constexpr(WithZigZag) {
+ value = static_cast<ui64>(ZigZagDecode64(value));
+ }
+ return static_cast<T>(value);
+}
+
+template <
+ bool WithBaseValue,
+ bool WithZigZag,
+ class T
+>
+struct TIntegerValueDecoder
+{
+ static T Run(ui64 value, ui64 baseValue)
+ {
+ return DecodeIntegerValueImpl<WithBaseValue, WithZigZag, T>(value, baseValue);
+ }
+};
+
+template <
+ bool WithBaseValue,
+ bool WithZigZag,
+ class T
+>
+struct TIdentityValueDecoder
+{
+ template <class V>
+ static V Run(V value, ui64 /*baseValue*/)
+ {
+ return value;
+ }
+};
+
+template <
+ class T,
+ template <
+ bool WithBaseValue_,
+ bool WithZigZag_,
+ class T_
+ >
+ class TValueDecoder,
+ class TGetter,
+ class TConsumer
+>
+void DecodeVector(
+ i64 startIndex,
+ i64 endIndex,
+ ui64 baseValue,
+ bool zigZagEncoded,
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TGetter getter,
+ TConsumer consumer)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(!rleIndexes || rleIndexes[0] == 0);
+
+ #define XX_0(...) \
+ DecodeVectorImpl<__VA_ARGS__, T, TValueDecoder>( \
+ startIndex, \
+ endIndex, \
+ baseValue, \
+ dictionaryIndexes, \
+ rleIndexes, \
+ std::forward<TGetter>(getter), \
+ std::forward<TConsumer>(consumer));
+
+ #define XX_1(...) \
+ if (zigZagEncoded) { \
+ XX_0(__VA_ARGS__, true) \
+ } else { \
+ XX_0(__VA_ARGS__, false) \
+ }
+
+ #define XX_2(...) \
+ if (baseValue != 0) { \
+ XX_1(__VA_ARGS__, true) \
+ } else { \
+ XX_1(__VA_ARGS__, false) \
+ }
+
+ #define XX_3(...) \
+ if (dictionaryIndexes) { \
+ XX_2(__VA_ARGS__, true) \
+ } else { \
+ XX_2(__VA_ARGS__, false) \
+ }
+
+ #define XX_4() \
+ if (rleIndexes) { \
+ XX_3(true) \
+ } else { \
+ XX_3(false) \
+ }
+
+ XX_4()
+
+ #undef XX_0
+ #undef XX_1
+ #undef XX_2
+ #undef XX_3
+ #undef XX_4
+}
+
+} // namespace
+
+template <
+ class TFetcher,
+ class TConsumer
+>
+void DecodeIntegerVector(
+ i64 startIndex,
+ i64 endIndex,
+ ui64 baseValue,
+ bool zigZagEncoded,
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TFetcher fetcher,
+ TConsumer consumer)
+{
+ DecodeVector<ui64, TIntegerValueDecoder>(
+ startIndex,
+ endIndex,
+ baseValue,
+ zigZagEncoded,
+ dictionaryIndexes,
+ rleIndexes,
+ std::forward<TFetcher>(fetcher),
+ std::forward<TConsumer>(consumer));
+}
+
+template <
+ class T,
+ class TFetcher,
+ class TConsumer
+>
+void DecodeRawVector(
+ i64 startIndex,
+ i64 endIndex,
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TFetcher fetcher,
+ TConsumer consumer)
+{
+ DecodeVector<T, TIdentityValueDecoder>(
+ startIndex,
+ endIndex,
+ 0,
+ false,
+ dictionaryIndexes,
+ rleIndexes,
+ std::forward<TFetcher>(fetcher),
+ std::forward<TConsumer>(consumer));
+}
+
+template <class T>
+T DecodeIntegerValue(
+ ui64 value,
+ ui64 baseValue,
+ bool zigZagEncoded)
+{
+ return zigZagEncoded
+ ? DecodeIntegerValueImpl<true, true, T>(value, baseValue)
+ : DecodeIntegerValueImpl<true, false, T>(value, baseValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/columnar.cpp b/yt/yt/client/table_client/columnar.cpp
new file mode 100644
index 0000000000..cce7b42c90
--- /dev/null
+++ b/yt/yt/client/table_client/columnar.cpp
@@ -0,0 +1,838 @@
+#include "columnar.h"
+
+#include <yt/yt/client/table_client/row_base.h>
+
+#include <yt/yt/library/numeric/algorithm_helpers.h>
+
+#include <util/system/cpu_id.h>
+
+#if defined(__clang__) && defined(__x86_64__)
+# include <immintrin.h>
+#endif
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+ui64 SafeReadQword(const ui64* ptr, const char* end)
+{
+ if (reinterpret_cast<const char*>(ptr) >= end) {
+ return 0;
+ }
+ ui64 qword = 0;
+ ::memcpy(&qword, ptr, std::min<size_t>(sizeof(ui64), end - reinterpret_cast<const char*>(ptr)));
+ return qword;
+}
+
+void SafeWriteQword(ui64* ptr, char* end, ui64 qword)
+{
+ if (reinterpret_cast<char*>(ptr) >= end) {
+ return;
+ }
+ ::memcpy(ptr, &qword, std::min<size_t>(sizeof(ui64), end - reinterpret_cast<char*>(ptr)));
+}
+
+template <bool Negate, class T>
+auto MaybeNegateValue(T value)
+{
+ if constexpr(Negate) {
+ value = ~value;
+ }
+ return value;
+}
+
+template <class T, bool Negate>
+std::tuple<const T*, T*> MaybeNegateAndCopyValues(
+ const void* beginInput,
+ const void* endInput,
+ void* output)
+{
+ const auto* currentTypedInput = static_cast<const T*>(beginInput);
+ const auto* endTypedInput = static_cast<const T*>(endInput);
+ auto* currentTypedOutput = static_cast<T*>(output);
+ while (currentTypedInput < endTypedInput) {
+ *currentTypedOutput++ = MaybeNegateValue<Negate>(*currentTypedInput++);
+ }
+ return {currentTypedInput, currentTypedOutput};
+}
+
+template <bool Negate>
+void CopyBitmapRangeToBitmapImpl(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(endIndex <= std::ssize(bitmap) * 8);
+ YT_VERIFY(endIndex - startIndex <= std::ssize(dst) * 8);
+
+ auto bitCount = endIndex - startIndex;
+ auto byteCount = GetBitmapByteSize(bitCount);
+
+ auto startQwordIndexRem = startIndex & 63;
+ auto startQwordIndexQuot = startIndex >> 6;
+
+ auto endQwordIndexQuot = endIndex >> 6;
+
+ const auto* beginQwordInput = reinterpret_cast<const ui64*>(bitmap.Begin()) + startQwordIndexQuot;
+ const auto* endQwordInput = beginQwordInput + endQwordIndexQuot - startQwordIndexQuot;
+ const auto* currentQwordInput = beginQwordInput;
+ auto* currentQwordOutput = reinterpret_cast<ui64*>(dst.Begin());
+
+ auto qwordShift = startQwordIndexRem;
+ auto qwordCoshift = 64 - startQwordIndexRem;
+
+ if (qwordShift == 0) {
+ const auto* beginByteInput = reinterpret_cast<const ui8*>(beginQwordInput);
+ const auto* endByteInput = beginByteInput + byteCount;
+ auto* beginByteOutput = reinterpret_cast<ui8*>(dst.Begin());
+ if constexpr(Negate) {
+ std::tie(currentQwordInput, currentQwordOutput) = MaybeNegateAndCopyValues<ui64, Negate>(
+ currentQwordInput,
+ endQwordInput - 1,
+ currentQwordOutput);
+ MaybeNegateAndCopyValues<ui8, Negate>(
+ currentQwordInput,
+ endByteInput,
+ currentQwordOutput);
+ } else {
+ ::memcpy(beginByteOutput, beginByteInput, byteCount);
+ }
+ return;
+ }
+
+ auto buildOutputQWord = [&] (ui64 qword1, ui64 qword2) {
+ qword1 >>= qwordShift;
+ qword2 &= (1ULL << qwordShift) - 1;
+ qword2 <<= qwordCoshift;
+ return MaybeNegateValue<Negate>(qword1 | qword2);
+ };
+
+ // Head
+ while (currentQwordInput < endQwordInput - 1) {
+ auto qword1 = currentQwordInput[0];
+ auto qword2 = currentQwordInput[1];
+ *currentQwordOutput = buildOutputQWord(qword1, qword2);
+ ++currentQwordInput;
+ ++currentQwordOutput;
+ }
+
+ // Tail
+ while (currentQwordInput <= endQwordInput) {
+ auto qword1 = SafeReadQword(currentQwordInput, bitmap.End());
+ auto qword2 = SafeReadQword(currentQwordInput + 1, bitmap.End());
+ SafeWriteQword(currentQwordOutput, dst.End(), buildOutputQWord(qword1, qword2));
+ ++currentQwordInput;
+ ++currentQwordOutput;
+ }
+}
+
+bool GetBit(TRef bitmap, i64 index)
+{
+ return (bitmap[index >> 3] & (1U << (index & 7))) != 0;
+}
+
+void SetBit(TMutableRef bitmap, i64 index, bool value)
+{
+ auto& byte = bitmap[index >> 3];
+ auto mask = (1U << (index & 7));
+ if (value) {
+ byte |= mask;
+ } else {
+ byte &= ~mask;
+ }
+}
+
+template <class F>
+void BuildBitmapFromRleImpl(
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ F valueFetcher,
+ TMutableRef dst)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(std::ssize(dst) * 8 >= endIndex - startIndex);
+ YT_VERIFY(rleIndexes[0] == 0);
+
+ auto startRleIndex = TranslateRleStartIndex(rleIndexes, startIndex);
+ auto currentInputIndex = startRleIndex;
+ auto currentIndex = startIndex;
+ auto currentRleIndex = startRleIndex;
+ i64 thresholdIndex = -1;
+ i64 currentOutputIndex = 0;
+
+ while (currentIndex < endIndex) {
+ #define XX1(currentBoolValue, fastpastCheck) \
+ while (currentIndex < thresholdIndex) { \
+ if (fastpastCheck && (currentOutputIndex & 7) == 0 && currentIndex + 8 <= thresholdIndex) { \
+ auto* currentByteOutput = reinterpret_cast<ui8*>(dst.Begin()) + (currentOutputIndex >> 3); \
+ auto currentByteValue = currentBoolValue ? ~static_cast<ui8>(0) : 0; \
+ while (currentIndex + 8 <= thresholdIndex) { \
+ *currentByteOutput++ = currentByteValue; \
+ currentOutputIndex += 8; \
+ currentIndex += 8; \
+ } \
+ } else { \
+ SetBit(dst, currentOutputIndex, currentBoolValue); \
+ ++currentIndex; \
+ ++currentOutputIndex; \
+ } \
+ }
+
+ #define XX2(currentBoolValue) \
+ if (thresholdIndex - currentIndex >= 16) { \
+ XX1(currentBoolValue, true) \
+ } else { \
+ XX1(currentBoolValue, false) \
+ }
+
+ ++currentRleIndex;
+ thresholdIndex = currentRleIndex < std::ssize(rleIndexes)
+ ? std::min(static_cast<i64>(rleIndexes[currentRleIndex]), endIndex)
+ : endIndex;
+ if (valueFetcher(currentInputIndex++)) {
+ XX2(true)
+ } else {
+ XX2(false)
+ }
+
+ #undef XX1
+ #undef XX2
+ }
+}
+
+template <typename F, typename TByte>
+void BuildBytemapFromRleImpl(
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ F valueFetcher,
+ TMutableRange<TByte> dst)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(std::ssize(dst) == endIndex - startIndex);
+ YT_VERIFY(rleIndexes[0] == 0);
+
+ auto startRleIndex = TranslateRleStartIndex(rleIndexes, startIndex);
+ auto currentInputIndex = startRleIndex;
+ auto currentIndex = startIndex;
+ auto currentRleIndex = startRleIndex;
+ bool currentBoolValue;
+ i64 thresholdIndex = -1;
+ i64 currentOutputIndex = 0;
+ while (true) {
+ if (currentIndex >= thresholdIndex) {
+ if (currentIndex >= endIndex) {
+ break;
+ }
+ ++currentRleIndex;
+ thresholdIndex = std::min(
+ endIndex,
+ currentRleIndex < std::ssize(rleIndexes) ? static_cast<i64>(rleIndexes[currentRleIndex]) : Max<i64>());
+ currentBoolValue = valueFetcher(currentInputIndex++);
+ }
+ if ((currentOutputIndex & 7) == 0 && currentIndex + 8 <= thresholdIndex) {
+ auto* currentQwordOutput = reinterpret_cast<ui64*>(dst.Begin()) + (currentOutputIndex >> 3);
+ auto currentQwordValue = currentBoolValue ? 0x0101010101010101ULL : 0ULL;
+ while (currentIndex + 8 <= thresholdIndex) {
+ *currentQwordOutput++ = currentQwordValue;
+ currentOutputIndex += 8;
+ currentIndex += 8;
+ }
+ } else {
+ dst[currentOutputIndex++] = static_cast<TByte>(currentBoolValue);
+ ++currentIndex;
+ }
+ }
+}
+
+#if defined(__clang__) && defined(__x86_64__)
+
+template <typename TByte>
+__attribute__((target("bmi2")))
+void DecodeBytemapFromBitmapImplBmi2(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst)
+{
+ auto index = startIndex;
+ while (index < endIndex) {
+ if ((index & 7) == 0 && index + 8 <= endIndex) {
+ const auto* currentInput = bitmap.Begin() + (index >> 3);
+ const auto* endInput = bitmap.Begin() + (endIndex >> 3);
+ auto* currentOutput = reinterpret_cast<ui64*>(dst.Begin() + (index - startIndex));
+ while (currentInput < endInput) {
+ // Cf. https://stackoverflow.com/questions/52098873/how-to-efficiently-convert-an-8-bit-bitmap-to-array-of-0-1-integers-with-x86-sim
+ constexpr ui64 Mask = 0x0101010101010101;
+ *currentOutput++ = __builtin_ia32_pdep_di(*currentInput++, Mask);
+ }
+ index = (endIndex & ~7);
+ } else {
+ dst[index - startIndex] = GetBit(bitmap, index);
+ ++index;
+ }
+ }
+}
+
+#endif
+
+template <typename TByte>
+void DecodeBytemapFromBitmapImplNoBmi2(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst)
+{
+ for (auto index = startIndex; index < endIndex; ++index) {
+ dst[index - startIndex] = GetBit(bitmap, index);
+ }
+}
+
+} // namespace
+
+void BuildValidityBitmapFromDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRef dst)
+{
+ YT_VERIFY(std::ssize(dst) >= GetBitmapByteSize(std::ssize(dictionaryIndexes)));
+
+ const auto* beginInput = dictionaryIndexes.Begin();
+ const auto* endInput = dictionaryIndexes.End();
+ const auto* endHeadInput = endInput - dictionaryIndexes.Size() % 8;
+ const auto* currentInput = beginInput;
+ auto* currentOutput = reinterpret_cast<ui8*>(dst.Begin());
+
+ // Head
+ while (currentInput < endHeadInput) {
+ ui8 result = 0;
+#define XX(shift) if (currentInput[shift] != 0) result |= (1U << shift);
+ XX(0)
+ XX(1)
+ XX(2)
+ XX(3)
+ XX(4)
+ XX(5)
+ XX(6)
+ XX(7)
+#undef XX
+ *currentOutput++ = result;
+ currentInput += 8;
+ }
+
+ if (currentInput == endInput) {
+ return;
+ }
+
+ // Tail
+ {
+ ui8 mask = 1;
+ ui8 result = 0;
+ while (currentInput < endInput) {
+ if (*currentInput++ != 0) {
+ result |= mask;
+ }
+ mask <<= 1;
+ }
+ *currentOutput++ = result;
+ }
+}
+
+void BuildValidityBitmapFromRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst)
+{
+ YT_VERIFY(rleIndexes.size() == dictionaryIndexes.size());
+
+ BuildBitmapFromRleImpl(
+ rleIndexes,
+ startIndex,
+ endIndex,
+ [&] (i64 inputIndex) { return dictionaryIndexes[inputIndex] != 0; },
+ dst);
+}
+
+template <typename TByte>
+void BuildNullBytemapFromDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRange<TByte> dst)
+{
+ YT_VERIFY(dst.Size() == dictionaryIndexes.Size());
+
+ const auto* beginInput = dictionaryIndexes.Begin();
+ const auto* endInput = dictionaryIndexes.End();
+ const auto* currentInput = beginInput;
+ auto* currentOutput = dst.Begin();
+ while (currentInput < endInput) {
+ *currentOutput++ = static_cast<TByte>(*currentInput++ == 0);
+ }
+}
+
+template <typename TByte>
+void BuildNullBytemapFromRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst)
+{
+ YT_VERIFY(rleIndexes.size() == dictionaryIndexes.size());
+
+ BuildBytemapFromRleImpl(
+ rleIndexes,
+ startIndex,
+ endIndex,
+ [&] (i64 inputIndex) { return dictionaryIndexes[inputIndex] == 0; },
+ dst);
+}
+
+void BuildDictionaryIndexesFromDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRange<ui32> dst)
+{
+ YT_VERIFY(dst.Size() == dictionaryIndexes.Size());
+
+ const auto* beginInput = dictionaryIndexes.Begin();
+ const auto* endInput = dictionaryIndexes.End();
+ const auto* currentInput = beginInput;
+ auto* currentOutput = dst.Begin();
+ while (currentInput < endInput) {
+ // NB: null becomes FFFFFFFF.
+ *currentOutput++ = (*currentInput++) - 1;
+ }
+}
+
+void BuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui32> dst)
+{
+ auto* currentOutput = dst.Begin();
+ DecodeRawVector<ui32>(
+ startIndex,
+ endIndex,
+ {},
+ rleIndexes,
+ [&] (auto index) {
+ return dictionaryIndexes[index];
+ },
+ [&] (auto value) {
+ *currentOutput++ = value - 1;
+ });
+ YT_VERIFY(currentOutput == dst.End());
+}
+
+void BuildIotaDictionaryIndexesFromRleIndexes(
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui32> dst)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(endIndex - startIndex == std::ssize(dst));
+ YT_VERIFY(rleIndexes[0] == 0);
+
+ auto startRleIndex = TranslateRleStartIndex(rleIndexes, startIndex);
+ auto* currentOutput = dst.Begin();
+ auto currentIndex = startIndex;
+ auto currentRleIndex = startRleIndex;
+ auto currentValue = static_cast<ui32>(-1);
+ i64 thresholdIndex = -1;
+ while (true) {
+ if (currentIndex >= thresholdIndex) {
+ if (currentIndex >= endIndex) {
+ break;
+ }
+ ++currentRleIndex;
+ thresholdIndex = currentRleIndex < static_cast<i64>(rleIndexes.Size())
+ ? std::min(static_cast<i64>(rleIndexes[currentRleIndex]), endIndex)
+ : endIndex;
+ ++currentValue;
+ }
+ *currentOutput++ = currentValue;
+ ++currentIndex;
+ }
+}
+
+i64 CountNullsInDictionaryIndexesWithZeroNull(TRange<ui32> dictionaryIndexes)
+{
+ const auto* beginInput = dictionaryIndexes.Begin();
+ const auto* endInput = dictionaryIndexes.End();
+ const auto* currentInput = beginInput;
+ i64 result = 0;
+ while (currentInput < endInput) {
+ if (*currentInput++ == 0) {
+ ++result;
+ }
+ }
+ return result;
+}
+
+i64 CountNullsInRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(rleIndexes[0] == 0);
+
+ auto startRleIndex = TranslateRleStartIndex(rleIndexes, startIndex);
+ const auto* currentInput = dictionaryIndexes.Begin() + startRleIndex;
+ auto currentIndex = startIndex;
+ auto currentRleIndex = startRleIndex;
+ i64 result = 0;
+ while (currentIndex < endIndex) {
+ ++currentRleIndex;
+ auto thresholdIndex = currentRleIndex < std::ssize(rleIndexes) ? static_cast<i64>(rleIndexes[currentRleIndex]) : Max<i64>();
+ auto currentValue = *currentInput++;
+ auto newIndex = std::min(endIndex, thresholdIndex);
+ if (currentValue == 0) {
+ result += (newIndex - currentIndex);
+ }
+ currentIndex = newIndex;
+ }
+ return result;
+}
+
+i64 CountOnesInBitmap(TRef bitmap, i64 startIndex, i64 endIndex)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(endIndex <= std::ssize(bitmap) * 8);
+
+ if (startIndex == endIndex) {
+ return 0;
+ }
+
+ const auto* qwords = reinterpret_cast<const ui64*>(bitmap.Begin());
+
+ auto startIndexRem = startIndex & 63;
+ auto startIndexQuot = startIndex >> 6;
+
+ auto endIndexRem = endIndex & 63;
+ auto endIndexQuot = endIndex >> 6;
+
+ // Tiny
+ if (startIndexQuot == endIndexQuot) {
+ auto qword = SafeReadQword(qwords + startIndexQuot, bitmap.End());
+ qword &= (1ULL << endIndexRem) - 1;
+ qword >>= startIndexRem;
+ return __builtin_popcountll(qword);
+ }
+
+ i64 result = 0;
+
+ // Head
+ if (startIndexRem != 0) {
+ auto qword = qwords[startIndexQuot];
+ qword >>= startIndexRem;
+ result += __builtin_popcountll(qword);
+ ++startIndexQuot;
+ startIndexRem = 0;
+ }
+
+ // Middle
+ {
+ const auto* currentQword = qwords + startIndexQuot;
+ const auto* endQword = qwords + endIndexQuot;
+ while (currentQword < endQword) {
+ result += __builtin_popcountll(*currentQword++);
+ }
+ }
+
+ // Tail
+ if (endIndexRem != 0) {
+ auto qword = SafeReadQword(qwords + endIndexQuot, bitmap.End());
+ qword &= (1ULL << endIndexRem) - 1;
+ result += __builtin_popcountll(qword);
+ }
+
+ return result;
+}
+
+i64 CountOnesInRleBitmap(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(rleIndexes[0] == 0);
+
+ auto startRleIndex = TranslateRleStartIndex(rleIndexes, startIndex);
+ auto currentInputIndex = startRleIndex;
+ auto currentIndex = startIndex;
+ auto currentRleIndex = startRleIndex;
+ i64 result = 0;
+ while (currentIndex < endIndex) {
+ ++currentRleIndex;
+ auto thresholdIndex = currentRleIndex < std::ssize(rleIndexes) ? static_cast<i64>(rleIndexes[currentRleIndex]) : Max<i64>();
+ auto currentValue = GetBit(bitmap, currentInputIndex++);
+ auto newIndex = std::min(endIndex, thresholdIndex);
+ if (currentValue) {
+ result += (newIndex - currentIndex);
+ }
+ currentIndex = newIndex;
+ }
+ return result;
+}
+
+void CopyBitmapRangeToBitmap(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst)
+{
+ CopyBitmapRangeToBitmapImpl<false>(
+ bitmap,
+ startIndex,
+ endIndex,
+ dst);
+}
+
+void CopyBitmapRangeToBitmapNegated(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst)
+{
+ CopyBitmapRangeToBitmapImpl<true>(
+ bitmap,
+ startIndex,
+ endIndex,
+ dst);
+}
+
+template <typename TByte>
+void DecodeBytemapFromBitmap(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(endIndex - startIndex == std::ssize(dst));
+
+#if defined(__clang__) && defined(__x86_64__)
+ if (NX86::CachedHaveBMI2()) {
+ DecodeBytemapFromBitmapImplBmi2(bitmap, startIndex, endIndex, dst);
+ return;
+ }
+#endif
+
+ DecodeBytemapFromBitmapImplNoBmi2(bitmap, startIndex, endIndex, dst);
+}
+
+void BuildValidityBitmapFromRleNullBitmap(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst)
+{
+ BuildBitmapFromRleImpl(
+ rleIndexes,
+ startIndex,
+ endIndex,
+ [&] (i64 inputIndex) { return !GetBit(bitmap, inputIndex); },
+ dst);
+}
+
+template <typename TByte>
+void BuildNullBytemapFromRleNullBitmap(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst)
+{
+ BuildBytemapFromRleImpl(
+ rleIndexes,
+ startIndex,
+ endIndex,
+ [&] (i64 inputIndex) { return GetBit(bitmap, inputIndex); },
+ dst);
+}
+
+void DecodeStringOffsets(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui32> dst)
+{
+ YT_VERIFY(startIndex <= endIndex);
+ YT_VERIFY(std::ssize(dst) == endIndex - startIndex + 1);
+
+ auto* currentOutput = reinterpret_cast<ui32*>(dst.Begin());
+
+ auto startOffset = DecodeStringOffset(offsets, avgLength, startIndex);
+
+ if (startIndex == 0) {
+ // See DecodeStringOffset for a special handing of 0.
+ *currentOutput++ = 0;
+ ++startIndex;
+ }
+
+ // Mind offsets[index - 1] in DecodeStringOffset.
+ const auto* currentInput = offsets.Begin() + startIndex - 1;
+ // No -1 here; will output endIndex - startIndex + 1 offsets.
+ const auto* endInput = offsets.Begin() + endIndex;
+ // See DecodeStringOffset.
+ auto avgLengthTimesIndex = startIndex * avgLength;
+ while (currentInput < endInput) {
+ *currentOutput++ = avgLengthTimesIndex + ZigZagDecode64(*currentInput++) - startOffset;
+ avgLengthTimesIndex += avgLength;
+ }
+}
+
+void DecodeStringPointersAndLengths(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ TRef stringData,
+ TMutableRange<const char*> strings,
+ TMutableRange<i32> lengths)
+{
+ YT_VERIFY(offsets.Size() == strings.Size());
+ YT_VERIFY(offsets.Size() == lengths.Size());
+
+ i64 startOffset = 0;
+ i64 avgLengthTimesIndex = 0;
+ for (size_t index = 0; index < offsets.size(); ++index) {
+ strings[index] = stringData.Begin() + startOffset;
+ avgLengthTimesIndex += avgLength;
+ i64 endOffset = avgLengthTimesIndex + ZigZagDecode64(offsets[index]);
+ i32 length = endOffset - startOffset;
+ lengths[index] = length;
+ startOffset = endOffset;
+ }
+}
+
+
+i64 CountTotalStringLengthInRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TRange<i32> stringLengths,
+ i64 startIndex,
+ i64 endIndex)
+{
+ YT_VERIFY(startIndex >= 0 && startIndex <= endIndex);
+ YT_VERIFY(rleIndexes[0] == 0);
+
+ auto startRleIndex = TranslateRleStartIndex(rleIndexes, startIndex);
+ const auto* currentInput = dictionaryIndexes.Begin() + startRleIndex;
+ auto currentIndex = startIndex;
+ auto currentRleIndex = startRleIndex;
+ i64 result = 0;
+ while (currentIndex < endIndex) {
+ ++currentRleIndex;
+ auto thresholdIndex = currentRleIndex < static_cast<i64>(rleIndexes.Size()) ? static_cast<i64>(rleIndexes[currentRleIndex]) : Max<i64>();
+ auto currentDictionaryIndex = *currentInput++;
+ auto newIndex = std::min(endIndex, thresholdIndex);
+ if (currentDictionaryIndex != 0) {
+ result += (newIndex - currentIndex) * stringLengths[currentDictionaryIndex - 1];
+ }
+ currentIndex = newIndex;
+ }
+ return result;
+}
+
+i64 TranslateRleIndex(
+ TRange<ui64> rleIndexes,
+ i64 index)
+{
+ YT_VERIFY(index >= 0);
+ YT_VERIFY(rleIndexes[0] == 0);
+
+ return BinarySearch(
+ static_cast<i64>(0),
+ static_cast<i64>(rleIndexes.size()),
+ [&] (i64 k) {
+ return static_cast<i64>(rleIndexes[k]) <= index;
+ }) - 1;
+}
+
+i64 TranslateRleStartIndex(
+ TRange<ui64> rleIndexes,
+ i64 index)
+{
+ return TranslateRleIndex(rleIndexes, index);
+}
+
+i64 TranslateRleEndIndex(
+ TRange<ui64> rleIndexes,
+ i64 index)
+{
+ YT_VERIFY(index >= 0);
+ if (index == 0) {
+ return 0;
+ }
+ return TranslateRleIndex(rleIndexes, index - 1) + 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template void BuildNullBytemapFromDictionaryIndexesWithZeroNull<ui8>(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRange<ui8> dst);
+
+template void BuildNullBytemapFromRleDictionaryIndexesWithZeroNull<ui8>(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui8> dst);
+
+template void BuildNullBytemapFromRleNullBitmap<ui8>(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui8> dst);
+
+template void DecodeBytemapFromBitmap<ui8>(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui8> dst);
+
+#if defined(__cpp_char8_t)
+/*
+ * Explicitly instantiate templates with char8_t.
+ * These are used by CHYT for ClickHouse interop.
+ */
+template void BuildNullBytemapFromDictionaryIndexesWithZeroNull<char8_t>(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRange<char8_t> dst);
+
+template void BuildNullBytemapFromRleDictionaryIndexesWithZeroNull<char8_t>(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<char8_t> dst);
+
+template void BuildNullBytemapFromRleNullBitmap<char8_t>(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<char8_t> dst);
+
+template void DecodeBytemapFromBitmap<char8_t>(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<char8_t> dst);
+#endif
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/columnar.h b/yt/yt/client/table_client/columnar.h
new file mode 100644
index 0000000000..1ac6dcb897
--- /dev/null
+++ b/yt/yt/client/table_client/columnar.h
@@ -0,0 +1,247 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/range.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Returns the minimum number of bytes needed to store a bitmap with #bitCount bits.
+i64 GetBitmapByteSize(i64 bitCount);
+
+//! Builds validity bitmap from #dictionaryIndexes by replacing each zero with
+//! 0-bit and each non-zero with 1-bit.
+void BuildValidityBitmapFromDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRef dst);
+
+//! Same as #BuildValidityBitmapFromDictionaryIndexesWithZeroNull but for RLE.
+void BuildValidityBitmapFromRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst);
+
+//! Builds validity bitmap from #dictionaryIndexes by replacing each zero with
+//! 1-byte and each non-zero with 0-byte.
+//! The size of #dst must match the size of #dictionaryIndexes.
+template <typename TByte>
+void BuildNullBytemapFromDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRange<TByte> dst);
+
+//! Same as #BuildNullBytmapFromDictionaryIndexesWithZeroNull but for RLE.
+template <typename TByte>
+void BuildNullBytemapFromRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst);
+
+//! Copies dictionary indexes from #dictionaryIndexes to #dst subtracting one.
+//! Zeroes are replaced by unspecified values.
+//! The size of #dst must be exactly |endIndex - startIndex|.
+void BuildDictionaryIndexesFromDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TMutableRange<ui32> dst);
+
+//! Same as #BuildDictionaryIndexesFromDictionaryIndexesWithZeroNull but for RLE.
+void BuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui32> dst);
+
+//! Writes a "iota"-like sequence (0, 1, 2, ...) to #dst indicating
+//! RLE groups in accordance to #rleIndexes.
+//! The size of #dst must be exactly |endIndex - startIndex|.
+void BuildIotaDictionaryIndexesFromRleIndexes(
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui32> dst);
+
+//! Counts the number of #indexes equal to zero.
+i64 CountNullsInDictionaryIndexesWithZeroNull(TRange<ui32> indexes);
+
+//! Same as #CountNullsInDictionaryIndexesWithZeroNull but for RLE.
+i64 CountNullsInRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex);
+
+//! Counts the number of 1-bits in range [#startIndex, #endIndex) in #bitmap.
+i64 CountOnesInBitmap(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex);
+
+//! Same as #CountOnesInBitmap but for RLE.
+i64 CountOnesInRleBitmap(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex);
+
+//! Copies bits in range [#startIndex, #endIndex) from #bitmap to #dst (representing another bitmap).
+//! #bitmap must be 8-byte aligned and its trailing qword must be readable.
+//! The byte size of #dst be be enough to store |endIndex - startIndex| bits.
+void CopyBitmapRangeToBitmap(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst);
+
+//! Same as #CopyBitmapRangeToBitmap but inverts the bits.
+void CopyBitmapRangeToBitmapNegated(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst);
+
+//! Decodes bits in range [#startIndex, #endIndex) from #bitmap to #dst bytemap
+//! (0 indicates |false|, 1 indicates |true|).
+//! #bitmap must be 8-byte aligned and its trailing qword must be readable.
+//! The size of #dst must be |endIndex - startIndex|.
+template <typename TByte>
+void DecodeBytemapFromBitmap(
+ TRef bitmap,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst);
+
+//! Decodes RLE #bitmap and inverts the bits.
+//! The byte size of #dst be enough to store |endIndex - startIndex| bits.
+void BuildValidityBitmapFromRleNullBitmap(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRef dst);
+
+//! Decodes RLE #bitmap into #dst bytemap.
+//! The size of #dst be |endIndex - startIndex|.
+template <typename TByte>
+void BuildNullBytemapFromRleNullBitmap(
+ TRef bitmap,
+ TRange<ui64> rleIndexes,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<TByte> dst);
+
+//! Decodes the starting offset of the #index-th string.
+//! #index must be in range [0, N], where N is the size of #offsets.
+i64 DecodeStringOffset(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ i64 index);
+
+//! Decodes the starting offset and the ending offset of the #index-th string.
+//! #index must be in range [0, N), where N is the size of #offsets.
+std::pair<i64, i64> DecodeStringRange(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ i64 index);
+
+//! Decodes start offsets of strings in [#startIndex, #endIndex)
+//! range given by #offsets. The resulting array contains 32-bit offsets and
+//! must be of size |endIndex - startIndex + 1| (the last element will indicate the
+//! offset where the last string ends).
+void DecodeStringOffsets(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ i64 startIndex,
+ i64 endIndex,
+ TMutableRange<ui32> dst);
+
+//! Decodes start pointers and lengths in [#startIndex, #endIndex) row range.
+//! The size of #strings and #lengths must be |endIndex - startIndex|.
+void DecodeStringPointersAndLengths(
+ TRange<ui32> offsets,
+ ui32 avgLength,
+ TRef stringData,
+ TMutableRange<const char*> strings,
+ TMutableRange<i32> lengths);
+
+//! Computes the total length of RLE and dictionary-encoded strings in
+//! a given row range [#startIndex, #endIndex).
+i64 CountTotalStringLengthInRleDictionaryIndexesWithZeroNull(
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TRange<i32> stringLengths,
+ i64 startIndex,
+ i64 endIndex);
+
+//! Given #rleIndexes, translates #index in actual rowspace to
+//! to the index in RLE rowspace.
+i64 TranslateRleIndex(
+ TRange<ui64> rleIndexes,
+ i64 index);
+
+//! A synonym for TranslateRleIndex.
+i64 TranslateRleStartIndex(
+ TRange<ui64> rleIndexes,
+ i64 index);
+
+//! Regards #index as the end (non-inclusive) index.
+//! Equivalent to |TranslateRleIndex(index - 1) + 1| (unless index = 0).
+i64 TranslateRleEndIndex(
+ TRange<ui64> rleIndexes,
+ i64 index);
+
+//! A unversal decoder for dictionary-encoded and RLE integer vectors.
+//! Values are first retrieved via #fetcher, then
+//! pass through #DecodeIntegerValue and finally are forwarded
+//! to #consumer.
+template <
+ class TFetcher,
+ class TConsumer
+>
+void DecodeIntegerVector(
+ i64 startIndex,
+ i64 endIndex,
+ ui64 baseValue,
+ bool zigZagEncoded,
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TFetcher fetcher,
+ TConsumer consumer);
+
+//! A unversal decoder for dictionary-encoded and RLE vectors of raw values.
+//! Values are first retrieved via #fetcher and then are forwarded
+//! to #consumer.
+template <
+ class T,
+ class TFetcher,
+ class TConsumer
+>
+void DecodeRawVector(
+ i64 startIndex,
+ i64 endIndex,
+ TRange<ui32> dictionaryIndexes,
+ TRange<ui64> rleIndexes,
+ TFetcher fetcher,
+ TConsumer consumer);
+
+//! Decodes a single integer from YT chunk representation.
+template <class T>
+T DecodeIntegerValue(
+ ui64 value,
+ ui64 baseValue,
+ bool zigZagEncoded);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+#define COLUMNAR_INL_H_
+#include "columnar-inl.h"
+#undef COLUMNAR_INL_H_
diff --git a/yt/yt/client/table_client/columnar_statistics.cpp b/yt/yt/client/table_client/columnar_statistics.cpp
new file mode 100644
index 0000000000..8ac42f07a4
--- /dev/null
+++ b/yt/yt/client/table_client/columnar_statistics.cpp
@@ -0,0 +1,326 @@
+#include "columnar_statistics.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+#include <library/cpp/iterator/functools.h>
+
+#include <numeric>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNamedColumnarStatistics& TNamedColumnarStatistics::operator+=(const TNamedColumnarStatistics& other)
+{
+ for (const auto& [columnName, dataWeight] : other.ColumnDataWeights) {
+ ColumnDataWeights[columnName] += dataWeight;
+ }
+ if (other.TimestampTotalWeight) {
+ TimestampTotalWeight = TimestampTotalWeight.value_or(0) + *other.TimestampTotalWeight;
+ }
+ LegacyChunkDataWeight += other.LegacyChunkDataWeight;
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+constexpr size_t MaxStringValueLength = 100;
+constexpr auto NullUnversionedValue = MakeNullValue<TUnversionedValue>();
+
+//! Approximates long string values with shorter but lexicographically less ones. Other values are intact.
+TUnversionedOwningValue ApproximateMinValue(TUnversionedValue value)
+{
+ if (value.Type != EValueType::String || value.Length <= MaxStringValueLength) {
+ value.Flags = EValueFlags::None;
+ value.Id = 0;
+ return value;
+ }
+ return MakeUnversionedStringValue(value.AsStringBuf().SubString(0, MaxStringValueLength));
+}
+
+//! Approximates long string values with shorter but lexicographically greater ones. Other values are intact.
+TUnversionedOwningValue ApproximateMaxValue(TUnversionedValue value)
+{
+ if (value.Type != EValueType::String || value.Length <= MaxStringValueLength) {
+ value.Flags = EValueFlags::None;
+ value.Id = 0;
+ return value;
+ }
+
+ const char MaxChar = std::numeric_limits<unsigned char>::max();
+ auto truncatedStringBuf = value.AsStringBuf().SubString(0, MaxStringValueLength);
+
+ while (!truncatedStringBuf.empty() && truncatedStringBuf.back() == MaxChar) {
+ truncatedStringBuf.remove_suffix(1);
+ }
+
+ if (truncatedStringBuf.empty()) {
+ // String was larger than any possible approximation.
+ return MakeSentinelValue<TUnversionedValue>(EValueType::Max);
+ } else {
+ TUnversionedOwningValue result = MakeUnversionedStringValue(truncatedStringBuf);
+ char* mutableString = result.GetMutableString();
+ int lastIndex = truncatedStringBuf.size() - 1;
+ mutableString[lastIndex] = static_cast<unsigned char>(mutableString[lastIndex]) + 1;
+ return result;
+ }
+}
+
+template <typename TRow>
+void UpdateColumnarStatistics(TColumnarStatistics& statistics, TRange<TRow> rows)
+{
+ int maxId = -1;
+ for (const auto& values : rows) {
+ for (const auto& value : values) {
+ maxId = std::max<int>(maxId, value.Id);
+ }
+ }
+ if (maxId >= statistics.GetColumnCount()) {
+ statistics.Resize(maxId + 1);
+ }
+
+ for (const auto& values : rows) {
+ for (const auto& value : values) {
+ statistics.ColumnDataWeights[value.Id] += GetDataWeight(value);
+ }
+ }
+
+ if (!statistics.HasValueStatistics()) {
+ return;
+ }
+
+ // Vectors for precalculation of minimum and maximum values from rows that we are adding.
+ std::vector<TUnversionedValue> minValues(maxId + 1, NullUnversionedValue);
+ std::vector<TUnversionedValue> maxValues(maxId + 1, NullUnversionedValue);
+
+ for (const auto& row : rows) {
+ for (const auto& value : row) {
+ if (value.Type == EValueType::Composite || value.Type == EValueType::Any) {
+ // Composite and YSON values are not comparable.
+ minValues[value.Id] = MakeSentinelValue<TUnversionedValue>(EValueType::Min);
+ maxValues[value.Id] = MakeSentinelValue<TUnversionedValue>(EValueType::Max);
+ } else if (value.Type != EValueType::Null) {
+ if (minValues[value.Id] == NullUnversionedValue || value < minValues[value.Id]) {
+ minValues[value.Id] = value;
+ }
+ if (maxValues[value.Id] == NullUnversionedValue || value > maxValues[value.Id]) {
+ maxValues[value.Id] = value;
+ }
+ }
+ }
+ }
+
+ for (int index = 0; index <= maxId; ++index) {
+
+ if (minValues[index] != NullUnversionedValue &&
+ (statistics.ColumnMinValues[index] == NullUnversionedValue || statistics.ColumnMinValues[index] > minValues[index]))
+ {
+ statistics.ColumnMinValues[index] = ApproximateMinValue(minValues[index]);
+ }
+
+ if (maxValues[index] != NullUnversionedValue &&
+ (statistics.ColumnMaxValues[index] == NullUnversionedValue || statistics.ColumnMaxValues[index] < maxValues[index]))
+ {
+ statistics.ColumnMaxValues[index] = ApproximateMaxValue(maxValues[index]);
+ }
+ }
+
+ for (const auto& row : rows) {
+ for (const auto& value : row) {
+ statistics.ColumnNonNullValueCounts[value.Id] += (value.Type != EValueType::Null);
+ }
+ }
+}
+
+} // namespace
+
+TColumnarStatistics& TColumnarStatistics::operator+=(const TColumnarStatistics& other)
+{
+ if (GetColumnCount() == 0) {
+ Resize(other.GetColumnCount(), other.HasValueStatistics());
+ }
+
+ YT_VERIFY(GetColumnCount() == other.GetColumnCount());
+
+ for (int index = 0; index < GetColumnCount(); ++index) {
+ ColumnDataWeights[index] += other.ColumnDataWeights[index];
+ }
+ if (other.TimestampTotalWeight) {
+ TimestampTotalWeight = TimestampTotalWeight.value_or(0) + *other.TimestampTotalWeight;
+ }
+ LegacyChunkDataWeight += other.LegacyChunkDataWeight;
+
+ if (ChunkRowCount.has_value() && other.ChunkRowCount.has_value()) {
+ ChunkRowCount = *ChunkRowCount + *other.ChunkRowCount;
+ } else {
+ ChunkRowCount.reset();
+ }
+ if (LegacyChunkRowCount.has_value() && other.LegacyChunkRowCount.has_value()) {
+ LegacyChunkRowCount = *LegacyChunkRowCount + *other.LegacyChunkRowCount;
+ } else {
+ LegacyChunkRowCount.reset();
+ }
+
+ if (!other.HasValueStatistics()) {
+ ClearValueStatistics();
+ } else if (HasValueStatistics()) {
+ for (int index = 0; index < GetColumnCount(); ++index) {
+
+ if (other.ColumnMinValues[index] != NullUnversionedValue &&
+ (ColumnMinValues[index] == NullUnversionedValue || ColumnMinValues[index] > other.ColumnMinValues[index]))
+ {
+ ColumnMinValues[index] = other.ColumnMinValues[index];
+ }
+
+ if (other.ColumnMaxValues[index] != NullUnversionedValue &&
+ (ColumnMaxValues[index] == NullUnversionedValue || ColumnMaxValues[index] < other.ColumnMaxValues[index]))
+ {
+ ColumnMaxValues[index] = other.ColumnMaxValues[index];
+ }
+
+ ColumnNonNullValueCounts[index] += other.ColumnNonNullValueCounts[index];
+ }
+ }
+ return *this;
+}
+
+TColumnarStatistics TColumnarStatistics::MakeEmpty(int columnCount, bool hasValueStatistics)
+{
+ TColumnarStatistics result;
+ result.Resize(columnCount, hasValueStatistics);
+ return result;
+}
+
+TColumnarStatistics TColumnarStatistics::MakeLegacy(int columnCount, i64 legacyChunkDataWeight, i64 legacyChunkRowCount)
+{
+ TColumnarStatistics result = MakeEmpty(columnCount, /*hasValueStatistics*/ false);
+ result.LegacyChunkDataWeight = legacyChunkDataWeight;
+ result.LegacyChunkRowCount = legacyChunkRowCount;
+ return result;
+}
+
+TLightweightColumnarStatistics TColumnarStatistics::MakeLightweightStatistics() const
+{
+ return TLightweightColumnarStatistics{
+ .ColumnDataWeightsSum = std::accumulate(ColumnDataWeights.begin(), ColumnDataWeights.end(), (i64)0),
+ .TimestampTotalWeight = TimestampTotalWeight,
+ .LegacyChunkDataWeight = LegacyChunkDataWeight
+ };
+}
+
+TNamedColumnarStatistics TColumnarStatistics::MakeNamedStatistics(const std::vector<TString>& names) const
+{
+ TNamedColumnarStatistics result;
+ result.TimestampTotalWeight = TimestampTotalWeight;
+ result.LegacyChunkDataWeight = LegacyChunkDataWeight;
+
+ for (const auto& [name, columnDataWeight] : Zip(names, ColumnDataWeights)) {
+ result.ColumnDataWeights[name] = columnDataWeight;
+ }
+
+ return result;
+}
+
+bool TColumnarStatistics::HasValueStatistics() const
+{
+ YT_VERIFY(ColumnMinValues.size() == ColumnMaxValues.size());
+ YT_VERIFY(ColumnMinValues.size() == ColumnNonNullValueCounts.size());
+
+ return GetColumnCount() == 0 || !ColumnMinValues.empty();
+}
+
+void TColumnarStatistics::ClearValueStatistics()
+{
+ ColumnMinValues.clear();
+ ColumnMaxValues.clear();
+ ColumnNonNullValueCounts.clear();
+}
+
+int TColumnarStatistics::GetColumnCount() const
+{
+ return ColumnDataWeights.size();
+}
+
+void TColumnarStatistics::Resize(int columnCount, bool keepValueStatistics)
+{
+ keepValueStatistics &= HasValueStatistics();
+
+ ColumnDataWeights.resize(columnCount, 0);
+
+ if (keepValueStatistics) {
+ ColumnMinValues.resize(columnCount, NullUnversionedValue);
+ ColumnMaxValues.resize(columnCount, NullUnversionedValue);
+ ColumnNonNullValueCounts.resize(columnCount, 0);
+ } else {
+ ClearValueStatistics();
+ }
+}
+
+void TColumnarStatistics::Update(TRange<TUnversionedRow> rows)
+{
+ UpdateColumnarStatistics(*this, rows);
+
+ if (ChunkRowCount) {
+ ChunkRowCount = *ChunkRowCount + rows.Size();
+ }
+}
+
+void TColumnarStatistics::Update(TRange<TVersionedRow> rows)
+{
+ std::vector<TRange<TUnversionedValue>> keyColumnRows;
+ keyColumnRows.reserve(rows.Size());
+ for (const auto& row : rows) {
+ keyColumnRows.emplace_back(row.Keys());
+ }
+ UpdateColumnarStatistics(*this, TRange(keyColumnRows));
+
+ std::vector<TRange<TVersionedValue>> valueColumnRows;
+ valueColumnRows.reserve(rows.Size());
+ for (const auto& row : rows) {
+ valueColumnRows.emplace_back(row.Values());
+ }
+ UpdateColumnarStatistics(*this, TRange(valueColumnRows));
+
+ for (const auto& row : rows) {
+ TimestampTotalWeight = TimestampTotalWeight.value_or(0) +
+ (row.GetWriteTimestampCount() + row.GetDeleteTimestampCount()) * sizeof(TTimestamp);
+ }
+
+ if (ChunkRowCount) {
+ ChunkRowCount = *ChunkRowCount + rows.Size();
+ }
+}
+
+TColumnarStatistics TColumnarStatistics::SelectByColumnNames(const TNameTablePtr& nameTable, const std::vector<TStableName>& columnStableNames) const
+{
+ auto result = MakeEmpty(columnStableNames.size(), HasValueStatistics());
+
+ for (const auto& [columnIndex, columnName] : Enumerate(columnStableNames)) {
+ if (auto id = nameTable->FindId(columnName.Get()); id && *id < GetColumnCount()) {
+
+ result.ColumnDataWeights[columnIndex] = ColumnDataWeights[*id];
+
+ if (HasValueStatistics()) {
+ result.ColumnMinValues[columnIndex] = ColumnMinValues[*id];
+ result.ColumnMaxValues[columnIndex] = ColumnMaxValues[*id];
+ result.ColumnNonNullValueCounts[columnIndex] = ColumnNonNullValueCounts[*id];
+ }
+ }
+ }
+ result.TimestampTotalWeight = TimestampTotalWeight;
+ result.LegacyChunkDataWeight = LegacyChunkDataWeight;
+
+ result.ChunkRowCount = ChunkRowCount;
+ result.LegacyChunkRowCount = LegacyChunkRowCount;
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/columnar_statistics.h b/yt/yt/client/table_client/columnar_statistics.h
new file mode 100644
index 0000000000..621ac152e8
--- /dev/null
+++ b/yt/yt/client/table_client/columnar_statistics.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include "public.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TLightweightColumnarStatistics
+{
+ //! Sum of per-column data weight for chunks whose meta contains columnar statistics.
+ i64 ColumnDataWeightsSum = 0;
+ //! Total weight of all write and delete timestamps.
+ std::optional<i64> TimestampTotalWeight;
+ //! Total data weight of legacy chunks whose meta misses columnar statistics.
+ i64 LegacyChunkDataWeight = 0;
+};
+
+struct TNamedColumnarStatistics
+{
+ //! Per-column total data weight for chunks whose meta contains columnar statistics.
+ THashMap<TString, i64> ColumnDataWeights;
+ //! Total weight of all write and delete timestamps.
+ std::optional<i64> TimestampTotalWeight;
+ //! Total data weight of legacy chunks whose meta misses columnar statistics.
+ i64 LegacyChunkDataWeight = 0;
+
+ TNamedColumnarStatistics& operator +=(const TNamedColumnarStatistics& other);
+};
+
+//! TColumnarStatistics stores per-column statistics of data stored in a chunk/table.
+struct TColumnarStatistics
+{
+ //! Per-column total data weight for chunks whose meta contains columnar statistics.
+ std::vector<i64> ColumnDataWeights;
+ //! Total weight of all write and delete timestamps.
+ std::optional<i64> TimestampTotalWeight;
+ //! Total data weight of legacy chunks whose meta misses columnar statistics.
+ i64 LegacyChunkDataWeight = 0;
+
+ //! Per-column approximate minimum non-null values.
+ //! Can be missing for data produced before the introduction of value statistics.
+ //! Stored value is guaranteed to be less than or equal to any value in the corresponding column,
+ //! but it may not correspond to any real value in the column (e.g. strings may be approximated
+ //! with shorter strings to reduce statistics size).
+ //! If it is impossible to determine the minimum value, `EValueType::Min` is used.
+ //! If there aren't non-null values in the column, it contains `EValueType::Null`.
+ std::vector<TUnversionedOwningValue> ColumnMinValues;
+ //! Per-column approximate maximum non-null values.
+ //! Can be missing for data produced before the introduction of value statistics.
+ //! Stored value is guaranteed to be greater than or equal to any value in the corresponding column,
+ //! but it may not correspond to any real value in the column (e.g. strings may be approximated
+ //! with shorter strings to reduce statistics size).
+ //! If it is impossible to determine the maximum value, `EValueType::Max` is used.
+ //! If there aren't non-null values in the column, it contains `EValueType::Null`.
+ std::vector<TUnversionedOwningValue> ColumnMaxValues;
+ //! Number of non-null values in each column.
+ //! Can be missing for data produced before the introduction of value statistics.
+ std::vector<i64> ColumnNonNullValueCounts;
+
+ //! Total number of rows in all chunks whose meta contains columnar statistics.
+ //! Can be missing only if the cluster version is 23.1 or older.
+ std::optional<i64> ChunkRowCount = 0;
+ //! Total number of rows in legacy chunks whose meta misses columnar statistics.
+ //! Can be missing only if the cluster version is 23.1 or older.
+ std::optional<i64> LegacyChunkRowCount = 0;
+
+ TColumnarStatistics& operator+=(const TColumnarStatistics& other);
+ bool operator==(const TColumnarStatistics& other) const = default;
+
+ static TColumnarStatistics MakeEmpty(int columnCount, bool hasValueStatistics = true);
+ static TColumnarStatistics MakeLegacy(int columnCount, i64 legacyChunkDataWeight, i64 legacyChunkRowCount);
+
+ TLightweightColumnarStatistics MakeLightweightStatistics() const;
+
+ TNamedColumnarStatistics MakeNamedStatistics(const std::vector<TString>& names) const;
+
+ //! Checks if there are minimum, maximum, and non-null value statistics.
+ bool HasValueStatistics() const;
+ //! Clears minimum, maximum, and non-null value statistics.
+ void ClearValueStatistics();
+
+ int GetColumnCount() const;
+
+ void Resize(int columnCount, bool keepValueStatistics = true);
+
+ void Update(TRange<TUnversionedRow> rows);
+ void Update(TRange<TVersionedRow> rows);
+
+ TColumnarStatistics SelectByColumnNames(const TNameTablePtr& nameTable, const std::vector<TStableName>& columnStableNames) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/comparator-inl.h b/yt/yt/client/table_client/comparator-inl.h
new file mode 100644
index 0000000000..cfa50be6d8
--- /dev/null
+++ b/yt/yt/client/table_client/comparator-inl.h
@@ -0,0 +1,58 @@
+#ifndef COMPARATOR_INL_H_
+#error "Direct inclusion of this file is not allowed, include comparator.h"
+// For the sake of sane code completion.
+#include "comparator.h"
+#endif
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TComparer>
+int CompareKeys(TUnversionedValueRange lhs, TUnversionedValueRange rhs, const TComparer& prefixComparer)
+{
+ auto minCount = std::min(lhs.Size(), rhs.Size());
+ auto result = prefixComparer(lhs.Begin(), rhs.Begin(), minCount);
+ if (result == 0) {
+ if (lhs.Size() < rhs.Size()) {
+ result = -(minCount + 1);
+ } else if (lhs.Size() > rhs.Size()) {
+ result = minCount + 1;
+ }
+ }
+
+ return GetCompareSign(result);
+}
+
+template <typename TComparer>
+int CompareWithWidening(
+ TUnversionedValueRange keyPrefix,
+ TUnversionedValueRange boundKey,
+ const TComparer& prefixComparer)
+{
+ int result;
+ if (keyPrefix.size() < boundKey.size()) {
+ result = prefixComparer(keyPrefix.begin(), boundKey.begin(), keyPrefix.size());
+
+ if (result == 0) {
+ // Key is widened with nulls. Compare them with bound.
+ int index = keyPrefix.size();
+ while (index < std::ssize(boundKey)) {
+ if (boundKey[index].Type != EValueType::Null) {
+ // Negative value because null is less than non-null value type.
+ result = -(index + 1);
+ break;
+ }
+ ++index;
+ }
+ }
+ } else {
+ result = prefixComparer(keyPrefix.begin(), boundKey.begin(), boundKey.size());
+ }
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/comparator.cpp b/yt/yt/client/table_client/comparator.cpp
new file mode 100644
index 0000000000..1d8375387b
--- /dev/null
+++ b/yt/yt/client/table_client/comparator.cpp
@@ -0,0 +1,509 @@
+#include "comparator.h"
+
+#include "key_bound.h"
+#include "serialize.h"
+
+#include <yt/yt/core/logging/log.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/serialize.h>
+
+namespace NYT::NTableClient {
+
+using namespace NLogging;
+using namespace NYson;
+using namespace NYTree;
+
+//! Used only for YT_LOG_FATAL below.
+static const TLogger Logger("TableClientComparator");
+
+////////////////////////////////////////////////////////////////////////////////
+
+TComparator::TComparator(std::vector<ESortOrder> sortOrders)
+ : SortOrders_(std::move(sortOrders))
+{ }
+
+void TComparator::Persist(const TPersistenceContext& context)
+{
+ using NYT::Persist;
+
+ Persist(context, SortOrders_);
+}
+
+int TComparator::GetLength() const
+{
+ return SortOrders_.size();
+}
+
+void TComparator::ValidateKey(const TKey& key) const
+{
+ YT_LOG_FATAL_IF(
+ key.GetLength() != GetLength(),
+ "Comparator is used with key of different length (Key: %v, Comparator: %v)",
+ key,
+ *this);
+}
+
+void TComparator::ValidateKeyBound(const TKeyBound& keyBound) const
+{
+ YT_LOG_FATAL_IF(
+ static_cast<int>(keyBound.Prefix.GetCount()) > GetLength(),
+ "Comparator is used with longer key bound (KeyBound: %v, Comparator: %v)",
+ keyBound,
+ *this);
+}
+
+int TComparator::CompareValues(int index, const TUnversionedValue& lhs, const TUnversionedValue& rhs) const
+{
+ int valueComparisonResult = CompareRowValues(lhs, rhs);
+
+ if (SortOrders_[index] == ESortOrder::Descending) {
+ valueComparisonResult = -valueComparisonResult;
+ }
+
+ return valueComparisonResult;
+}
+
+TKeyBound TComparator::StrongerKeyBound(const TKeyBound& lhs, const TKeyBound& rhs) const
+{
+ YT_VERIFY(lhs);
+ YT_VERIFY(rhs);
+
+ YT_VERIFY(lhs.IsUpper == rhs.IsUpper);
+ auto comparisonResult = CompareKeyBounds(lhs, rhs);
+ if (lhs.IsUpper) {
+ comparisonResult = -comparisonResult;
+ }
+
+ return (comparisonResult <= 0) ? rhs : lhs;
+}
+
+void TComparator::ReplaceIfStrongerKeyBound(TKeyBound& lhs, const TKeyBound& rhs) const
+{
+ if (!lhs) {
+ lhs = rhs;
+ return;
+ }
+
+ if (!rhs) {
+ return;
+ }
+
+ YT_VERIFY(lhs.IsUpper == rhs.IsUpper);
+ auto comparisonResult = CompareKeyBounds(lhs, rhs);
+ if (lhs.IsUpper) {
+ comparisonResult = -comparisonResult;
+ }
+
+ if (comparisonResult < 0) {
+ lhs = rhs;
+ }
+}
+
+void TComparator::ReplaceIfStrongerKeyBound(TOwningKeyBound& lhs, const TOwningKeyBound& rhs) const
+{
+ if (!lhs) {
+ lhs = rhs;
+ return;
+ }
+
+ if (!rhs) {
+ return;
+ }
+
+ YT_VERIFY(lhs.IsUpper == rhs.IsUpper);
+ auto comparisonResult = CompareKeyBounds(lhs, rhs);
+ if (lhs.IsUpper) {
+ comparisonResult = -comparisonResult;
+ }
+
+ if (comparisonResult < 0) {
+ lhs = rhs;
+ }
+}
+
+TKeyBound TComparator::WeakerKeyBound(const TKeyBound& lhs, const TKeyBound& rhs) const
+{
+ YT_VERIFY(lhs.IsUpper == rhs.IsUpper);
+ auto comparisonResult = CompareKeyBounds(lhs, rhs);
+ if (lhs.IsUpper) {
+ comparisonResult = -comparisonResult;
+ }
+
+ return (comparisonResult >= 0) ? rhs : lhs;
+}
+
+bool TComparator::IsRangeEmpty(const TKeyBound& lowerBound, const TKeyBound& upperBound) const
+{
+ YT_VERIFY(!lowerBound.IsUpper);
+ YT_VERIFY(upperBound.IsUpper);
+ return CompareKeyBounds(lowerBound, upperBound, /* lowerVsUpper */ 1) >= 0;
+}
+
+bool TComparator::IsInteriorEmpty(const TKeyBound& lowerBound, const TKeyBound& upperBound) const
+{
+ YT_VERIFY(!lowerBound.IsUpper);
+ YT_VERIFY(upperBound.IsUpper);
+ return IsRangeEmpty(lowerBound, upperBound) || TryAsSingletonKey(lowerBound, upperBound);
+}
+
+bool TComparator::TestKey(const TKey& key, const TKeyBound& keyBound) const
+{
+ ValidateKey(key);
+ ValidateKeyBound(keyBound);
+
+ int comparisonResult = 0;
+
+ for (int index = 0; index < static_cast<int>(keyBound.Prefix.GetCount()); ++index) {
+ const auto& keyValue = key[index];
+ const auto& keyBoundValue = keyBound.Prefix[index];
+ comparisonResult = CompareValues(index, keyValue, keyBoundValue);
+ if (comparisonResult != 0) {
+ break;
+ }
+ }
+
+ if (keyBound.IsUpper) {
+ comparisonResult = -comparisonResult;
+ }
+
+ // Now:
+ // - comparisonResult > 0 means that key is strictly inside ray (i.e. test is positive);
+ // - comparisonResult == 0 means that key starts with key bound prefix (i.e. we should consider inclusiveness);
+ // - comparisonResult < 0 means that key is strictly outside ray (i.e. test is negative).
+
+ return comparisonResult > 0 || (comparisonResult == 0 && keyBound.IsInclusive);
+}
+
+int TComparator::CompareKeyBounds(const TKeyBound& lhs, const TKeyBound& rhs, int lowerVsUpper) const
+{
+ ValidateKeyBound(lhs);
+ ValidateKeyBound(rhs);
+
+ int comparisonResult = 0;
+
+ // In case when one key bound is a proper prefix of another, points to the shorter one.
+ const TKeyBound* shorter = nullptr;
+
+ for (int index = 0; ; ++index) {
+ if (index >= static_cast<int>(lhs.Prefix.GetCount()) &&
+ index >= static_cast<int>(rhs.Prefix.GetCount()))
+ {
+ // Prefixes coincide. Check if key bounds are indeed at the same point.
+ {
+ auto lhsInclusivenessAsUpper = (lhs.IsUpper && lhs.IsInclusive) || (!lhs.IsUpper && !lhs.IsInclusive);
+ auto rhsInclusivenessAsUpper = (rhs.IsUpper && rhs.IsInclusive) || (!rhs.IsUpper && !rhs.IsInclusive);
+ if (lhsInclusivenessAsUpper != rhsInclusivenessAsUpper) {
+ return lhsInclusivenessAsUpper - rhsInclusivenessAsUpper;
+ }
+ }
+
+ // Ok, they are indeed at the same point. How do we break ties?
+ if (lowerVsUpper == 0) {
+ // We are asked not to break ties.
+ return 0;
+ }
+
+ // Break ties using #upperFirst.
+ comparisonResult = lhs.IsUpper - rhs.IsUpper;
+
+ if (lowerVsUpper > 0) {
+ comparisonResult = -comparisonResult;
+ }
+ return comparisonResult;
+ } else if (index >= static_cast<int>(lhs.Prefix.GetCount())) {
+ shorter = &lhs;
+ break;
+ } else if (index >= static_cast<int>(rhs.Prefix.GetCount())) {
+ shorter = &rhs;
+ break;
+ } else {
+ const auto& lhsValue = lhs.Prefix[index];
+ const auto& rhsValue = rhs.Prefix[index];
+ comparisonResult = CompareValues(index, lhsValue, rhsValue);
+ if (comparisonResult != 0) {
+ return comparisonResult;
+ }
+ }
+ }
+ YT_VERIFY(shorter);
+
+ // By this moment, longer operand is strictly between shorter operand and toggleInclusiveness(shorter operand).
+ // Thus we have to check if shorter operand is "largest" among itself and its toggleInclusiveness counterpart.
+ if ((shorter->IsUpper && shorter->IsInclusive) || (!shorter->IsUpper && !shorter->IsInclusive)) {
+ comparisonResult = -1;
+ } else {
+ comparisonResult = 1;
+ }
+
+ // By now comparisonResult expresses if longer < shorter. Now check which hand is actually shorter.
+ if (shorter == &lhs) {
+ comparisonResult = -comparisonResult;
+ }
+
+ return comparisonResult;
+}
+
+int TComparator::CompareKeys(const TKey& lhs, const TKey& rhs) const
+{
+ ValidateKey(lhs);
+ ValidateKey(rhs);
+
+ for (int index = 0; index < lhs.GetLength(); ++index) {
+ auto valueComparisonResult = CompareValues(index, lhs[index], rhs[index]);
+ if (valueComparisonResult != 0) {
+ return valueComparisonResult;
+ }
+ }
+
+ return 0;
+}
+
+std::optional<TKey> TComparator::TryAsSingletonKey(const TKeyBound& lowerBound, const TKeyBound& upperBound) const
+{
+ ValidateKeyBound(lowerBound);
+ ValidateKeyBound(upperBound);
+ YT_VERIFY(!lowerBound.IsUpper);
+ YT_VERIFY(upperBound.IsUpper);
+
+ if (static_cast<int>(lowerBound.Prefix.GetCount()) != GetLength() ||
+ static_cast<int>(upperBound.Prefix.GetCount()) != GetLength())
+ {
+ return std::nullopt;
+ }
+
+ if (!lowerBound.IsInclusive || !upperBound.IsInclusive) {
+ return std::nullopt;
+ }
+
+ for (int index = 0; index < static_cast<int>(lowerBound.Prefix.GetCount()); ++index) {
+ if (CompareValues(index, lowerBound.Prefix[index], upperBound.Prefix[index]) != 0) {
+ return std::nullopt;
+ }
+ }
+
+ return TKey::FromRowUnchecked(lowerBound.Prefix);
+}
+
+TComparator TComparator::Trim(int keyColumnCount) const
+{
+ YT_VERIFY(keyColumnCount <= std::ssize(SortOrders_));
+
+ auto sortOrders = SortOrders_;
+ sortOrders.resize(keyColumnCount);
+ return TComparator(std::move(sortOrders));
+}
+
+bool TComparator::HasDescendingSortOrder() const
+{
+ return std::find(SortOrders_.begin(), SortOrders_.end(), ESortOrder::Descending) != SortOrders_.end();
+}
+
+TComparator::operator bool() const
+{
+ return !SortOrders_.empty();
+}
+
+void FormatValue(TStringBuilderBase* builder, const TComparator& comparator, TStringBuf /* spec */)
+{
+ builder->AppendFormat("{Length: %v, SortOrders: ", comparator.GetLength());
+ for (auto sortOrder : comparator.SortOrders()) {
+ switch (sortOrder) {
+ case ESortOrder::Ascending:
+ builder->AppendChar('A');
+ break;
+ case ESortOrder::Descending:
+ builder->AppendChar('D');
+ break;
+ default:
+ YT_ABORT();
+ }
+ }
+ builder->AppendChar('}');
+}
+
+TString ToString(const TComparator& comparator)
+{
+ return ToStringViaBuilder(comparator);
+}
+
+void Serialize(const TComparator& comparator, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .DoListFor(comparator.SortOrders(), [&] (TFluentList fluent, ESortOrder sortOrder) {
+ fluent.Item().Value(sortOrder);
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int GetCompareSign(int value)
+{
+ return value != 0 ? value > 0 ? 1 : -1 : 0;
+}
+
+int ComparePrefix(const TUnversionedValue* lhs, const TUnversionedValue* rhs, int length)
+{
+ int index = 0;
+ while (index < length) {
+ int result = CompareRowValues(lhs[index], rhs[index]);
+ ++index;
+ if (result != 0) {
+ return result > 0 ? index : -index;
+ }
+ }
+
+ return 0;
+}
+
+int CompareKeys(TLegacyKey lhs, TLegacyKey rhs, TPrefixComparer prefixComparer)
+{
+ return CompareKeys(ToKeyRef(lhs), ToKeyRef(rhs), prefixComparer);
+}
+
+int CompareKeys(TLegacyKey lhs, TLegacyKey rhs, const TKeyComparer& prefixComparer)
+{
+ return CompareKeys(ToKeyRef(lhs), ToKeyRef(rhs), prefixComparer);
+}
+
+TKeyComparer::TKeyComparer(const TBase& base)
+ : TBase(base)
+{ }
+
+TKeyComparer::TKeyComparer()
+ : TBase(
+ New<TCaller>(
+#ifdef YT_ENABLE_BIND_LOCATION_TRACKING
+ FROM_HERE,
+#endif
+ nullptr,
+ &ComparePrefix),
+ &TCaller::StaticInvoke)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyRef ToKeyRef(TKey key)
+{
+ return key.Elements();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyBoundRef::TKeyBoundRef(
+ TKeyRef base,
+ bool inclusive,
+ bool upper)
+ : TKeyRef(base)
+ , Inclusive(inclusive)
+ , Upper(upper)
+{ }
+
+TKeyBoundRef ToKeyBoundRef(const TKeyBound& bound)
+{
+ return TKeyBoundRef(ToKeyRef(bound.Prefix), bound.IsInclusive, bound.IsUpper);
+}
+
+TKeyBoundRef ToKeyBoundRef(const TOwningKeyBound& bound)
+{
+ return TKeyBoundRef(ToKeyRef(bound.Prefix), bound.IsInclusive, bound.IsUpper);
+}
+
+std::pair<int, bool> GetBoundPrefixAndInclusiveness(TUnversionedRow row, bool isUpper, int keyLength);
+
+TKeyBoundRef ToKeyBoundRef(TUnversionedRow row, bool upper, int keyLength)
+{
+ if (!row) {
+ return TKeyBoundRef({}, /*inclusive*/ true, upper);
+ }
+
+ auto [prefixLength, inclusive] = GetBoundPrefixAndInclusiveness(row, upper, keyLength);
+ return TKeyBoundRef(
+ ToKeyRef(row, prefixLength),
+ inclusive,
+ upper);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int TestComparisonResult(int result, TRange<ESortOrder> sortOrders, bool inclusive, bool upper)
+{
+ if (result != 0) {
+ if (sortOrders[std::abs(result) - 1] == ESortOrder::Descending) {
+ result = -result;
+ }
+
+ if (upper) {
+ result = -result;
+ }
+ }
+
+ return result > 0 || inclusive && result == 0;
+}
+
+int TestComparisonResult(int result, bool inclusive, bool upper)
+{
+ if (upper) {
+ result = -result;
+ }
+
+ return result > 0 || inclusive && result == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int CompareWithWidening(
+ TUnversionedValueRange keyPrefix,
+ TUnversionedValueRange boundKey)
+{
+ return CompareWithWidening(keyPrefix, boundKey, ComparePrefix);
+}
+
+int TestKey(TUnversionedValueRange key, const TKeyBoundRef& bound, TRange<ESortOrder> sortOrders)
+{
+ YT_VERIFY(bound.size() <= key.size());
+ int result = ComparePrefix(key.begin(), bound.begin(), bound.size());
+
+ return TestComparisonResult(result, sortOrders, bound.Inclusive, bound.Upper);
+}
+
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound, TRange<ESortOrder> sortOrders)
+{
+ int result = CompareWithWidening(key, bound);
+ return TestComparisonResult(result, sortOrders, bound.Inclusive, bound.Upper);
+}
+
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound)
+{
+ int result = CompareWithWidening(key, bound);
+ return TestComparisonResult(result, bound.Inclusive, bound.Upper);
+}
+
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound, TPrefixComparer prefixComparer)
+{
+ int result = CompareWithWidening(key, bound, prefixComparer);
+ return TestComparisonResult(result, bound.Inclusive, bound.Upper);
+}
+
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound, const TKeyComparer& prefixComparer)
+{
+ int result = CompareWithWidening(key, bound, prefixComparer);
+ return TestComparisonResult(result, bound.Inclusive, bound.Upper);
+}
+
+int TestKeyWithWidening(TPrefixComparer prefixComparer, TUnversionedValueRange key, const TKeyBoundRef& bound)
+{
+ int result = CompareWithWidening(key, bound, prefixComparer);
+ return TestComparisonResult(result, bound.Inclusive, bound.Upper);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, ESortOrder sortOrder, TStringBuf /* spec */)
+{
+ FormatEnum(builder, sortOrder, true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/comparator.h b/yt/yt/client/table_client/comparator.h
new file mode 100644
index 0000000000..9c9e1d78d8
--- /dev/null
+++ b/yt/yt/client/table_client/comparator.h
@@ -0,0 +1,180 @@
+#pragma once
+
+#include "public.h"
+
+#include "key.h"
+
+#include <yt/yt/core/actions/callback.h>
+
+#include <yt/yt/library/codegen/caller.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ESortOrder,
+ ((Ascending) (0))
+ ((Descending) (1))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Class that encapsulates all necessary information for key comparison
+//! and testing if key belongs to the ray defined by a key bound.
+class TComparator
+{
+public:
+ DEFINE_BYREF_RO_PROPERTY(std::vector<ESortOrder>, SortOrders);
+
+public:
+ TComparator() = default;
+ explicit TComparator(std::vector<ESortOrder> sortOrders);
+
+ void Persist(const TPersistenceContext& context);
+
+ //! Test if key #key belongs to the ray defined by #keyBound.
+ bool TestKey(const TKey& key, const TKeyBound& keyBound) const;
+
+ //! Compare key bounds according to their logical position on a line of all possible keys.
+ //! If lhs and rhs belong to the same point, compare lower limit against upper limit as
+ //! defined by #lowerVsUpperResult (i.e. if 0, lower == upper; if < 0, lower < upper; if > 0, lower > upper) .
+ int CompareKeyBounds(const TKeyBound& lhs, const TKeyBound& rhs, int lowerVsUpperResult = 0) const;
+
+ //! Compare two values belonging to the index #index of the key.
+ int CompareValues(int index, const TUnversionedValue& lhs, const TUnversionedValue& rhs) const;
+
+ //! Compare keys.
+ int CompareKeys(const TKey& lhs, const TKey& rhs) const;
+
+ //! Returns the strongest of two key bounds. Key bounds should be of same direction
+ //! (but possibly of different inclusiveness).
+ TKeyBound StrongerKeyBound(const TKeyBound& lhs, const TKeyBound& rhs) const;
+
+ //! Shorthand for #lhs = #StrongerKeyBound(#lhs, #rhs).
+ void ReplaceIfStrongerKeyBound(TKeyBound& lhs, const TKeyBound& rhs) const;
+
+ //! Same as previous for owning key bounds.
+ void ReplaceIfStrongerKeyBound(TOwningKeyBound& lhs, const TOwningKeyBound& rhs) const;
+
+ //! Returns the weakest of two key bounds. Key bounds should be of same direction
+ //! (but possibly of different inclusiveness).
+ TKeyBound WeakerKeyBound(const TKeyBound& lhs, const TKeyBound& rhs) const;
+
+ //! Check if the range defined by two key bounds is empty.
+ bool IsRangeEmpty(const TKeyBound& lowerBound, const TKeyBound& upperBound) const;
+
+ //! Check if the range defined by two key bounds has empty interior, i.e. is empty or is a singleton key.
+ bool IsInteriorEmpty(const TKeyBound& lowerBound, const TKeyBound& upperBound) const;
+
+ //! Return length of the primary key to which this comparator corresponds.
+ //! In particular, any key bound length passed as an argument must not exceed GetLength()
+ //! and any key length should be equal to GetLength().
+ int GetLength() const;
+
+ //! If there exists such key K that #lhs == ">= K" and #rhs == "<= K", return it.
+ std::optional<TKey> TryAsSingletonKey(const TKeyBound& lhs, const TKeyBound& rhs) const;
+
+ //! Returns a comparator that compares rows by first #keyColumnCount columns and ignores other.
+ TComparator Trim(int keyColumnCount) const;
+
+ //! Returns true if at least one column has descending sort order.
+ bool HasDescendingSortOrder() const;
+
+ //! Empty comparator is identified with an absence of comparator.
+ //! This may be used instead of TComparator.
+ explicit operator bool() const;
+
+private:
+ void ValidateKey(const TKey& key) const;
+ void ValidateKeyBound(const TKeyBound& keyBound) const;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TComparator& comparator, TStringBuf spec);
+TString ToString(const TComparator& comparator);
+
+void Serialize(const TComparator& comparator, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TPrefixComparer = int(const TUnversionedValue*, const TUnversionedValue*, int);
+
+////////////////////////////////////////////////////////////////////////////////
+
+int GetCompareSign(int value);
+
+//! Obeys the usual rule: the result's sign incidates the comparison outcome.
+//! Also |abs(result) - 1| is equal to index of first non-equal component.
+template <typename TComparer>
+int CompareKeys(TUnversionedValueRange lhs, TUnversionedValueRange rhs, const TComparer& prefixComparer);
+int ComparePrefix(const TUnversionedValue* lhs, const TUnversionedValue* rhs, int length);
+int CompareKeys(TLegacyKey lhs, TLegacyKey rhs, TPrefixComparer prefixComparer);
+int CompareKeys(TLegacyKey lhs, TLegacyKey rhs, const TKeyComparer& prefixComparer);
+
+class TKeyComparer
+ : public TCallback<TPrefixComparer>
+{
+public:
+ using TBase = TCallback<TPrefixComparer>;
+ using TBase::TBase;
+
+ using TCaller = NCodegen::TCGCaller<TPrefixComparer>;
+
+ TKeyComparer(const TBase& base);
+ TKeyComparer();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyRef ToKeyRef(TUnversionedRow row);
+TKeyRef ToKeyRef(TUnversionedRow row, int prefixLength);
+TKeyRef ToKeyRef(TKey key);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TKeyBoundRef
+ : public TKeyRef
+{
+public:
+ bool Inclusive;
+ bool Upper;
+
+ TKeyBoundRef(
+ TKeyRef base,
+ bool inclusive = false,
+ bool upper = false);
+};
+
+TKeyBoundRef ToKeyBoundRef(const TKeyBound& bound);
+TKeyBoundRef ToKeyBoundRef(const TOwningKeyBound& bound);
+TKeyBoundRef ToKeyBoundRef(TUnversionedRow row, bool upper, int keyLength);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TComparer>
+int CompareWithWidening(
+ TUnversionedValueRange keyPrefix,
+ TUnversionedValueRange boundKey,
+ const TComparer& prefixComparer);
+int CompareWithWidening(
+ TUnversionedValueRange keyPrefix,
+ TUnversionedValueRange boundKey);
+
+int TestKey(TUnversionedValueRange key, const TKeyBoundRef& bound, TRange<ESortOrder> sortOrders);
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound);
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound, TPrefixComparer prefixComparer);
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound, const TKeyComparer& prefixComparer);
+int TestKeyWithWidening(TUnversionedValueRange key, const TKeyBoundRef& bound, TRange<ESortOrder> sortOrders);
+
+int TestComparisonResult(int result, TRange<ESortOrder> sortOrders, bool inclusive, bool upper);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, ESortOrder sortOrder, TStringBuf /* spec */);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+#define COMPARATOR_INL_H_
+#include "comparator-inl.h"
+#undef COMPARATOR_INL_H_
diff --git a/yt/yt/client/table_client/composite_compare.cpp b/yt/yt/client/table_client/composite_compare.cpp
new file mode 100644
index 0000000000..7d28c21b3a
--- /dev/null
+++ b/yt/yt/client/table_client/composite_compare.cpp
@@ -0,0 +1,279 @@
+#include "composite_compare.h"
+
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/library/numeric/util.h>
+
+#include <library/cpp/yt/farmhash/farm_hash.h>
+
+#include <util/stream/mem.h>
+
+namespace NYT::NTableClient {
+
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// This file implements comparison for composite values.
+// Composite types that supports comparison are:
+// 1. Optional
+// 2. List
+// 3. Tuple
+// 4. Variant
+//
+// When we compare composite values we assume that they are well-formed yson representations of same type supporting comparison.
+// And we compare them in following manner:
+// 1. We scan two values simultaneously and look at their yson tokens and find first mismatching token.
+// 2. If one of the token is EndList (this only can happen if we parsing values of list type
+// and one list is shorter than another) that means that value containing EndList is less that other.
+// 3. Otherwise if one of the values is Entity (other value have to be non null value) that means
+// that value containing Entity is less than other.
+// 4. Otherwise it's 2 values of the same type and we can easily compare them.
+DEFINE_ENUM_WITH_UNDERLYING_TYPE(ECompareClass, ui32,
+ ((Incomparable)(0))
+ ((EndList)(1))
+ ((Entity)(2))
+ ((BeginValue)(3))
+);
+
+// Helper function for GetCompareClass
+static constexpr ui32 SetMask(EYsonItemType type, ECompareClass compareClass)
+{
+ return static_cast<ui32>(compareClass) << (static_cast<ui32>(type) * 2);
+}
+
+static constexpr ECompareClass GetCompareClass(EYsonItemType type)
+{
+ // We have single integer where each pair of bits encodes map EYsonItemType -> ECompareClass.
+ constexpr ui32 compareClassMask =
+ SetMask(EYsonItemType::EndList, ECompareClass::EndList) |
+ SetMask(EYsonItemType::EntityValue, ECompareClass::Entity) |
+ SetMask(EYsonItemType::BeginList, ECompareClass::BeginValue) |
+ SetMask(EYsonItemType::Int64Value, ECompareClass::BeginValue) |
+ SetMask(EYsonItemType::Uint64Value, ECompareClass::BeginValue) |
+ SetMask(EYsonItemType::DoubleValue, ECompareClass::BeginValue) |
+ SetMask(EYsonItemType::BooleanValue, ECompareClass::BeginValue) |
+ SetMask(EYsonItemType::StringValue, ECompareClass::BeginValue);
+ static_assert(TEnumTraits<EYsonItemType>::GetDomainSize() * 2 <= 32);
+ return static_cast<ECompareClass>(0x3u & (compareClassMask >> (static_cast<ui32>(type) * 2)));
+}
+
+template <typename T>
+Y_FORCE_INLINE int ComparePrimitive(T lhs, T rhs)
+{
+ if (lhs == rhs) {
+ return 0;
+ } else if (lhs < rhs) {
+ return -1;
+ } else {
+ return 1;
+ }
+}
+
+template <>
+Y_FORCE_INLINE int ComparePrimitive<double>(double lhs, double rhs)
+{
+ if (lhs < rhs) {
+ return -1;
+ } else if (lhs > rhs) {
+ return 1;
+ } else if (std::isnan(lhs)) {
+ if (std::isnan(rhs)) {
+ return 0;
+ }
+ return 1;
+ } else if (std::isnan(rhs)) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+[[noreturn]] static void ThrowIncomparableYsonToken(EYsonItemType tokenType)
+{
+ THROW_ERROR_EXCEPTION("Incomparable YSON token %Qlv",
+ tokenType);
+}
+
+Y_FORCE_INLINE static int GetSign(int x)
+{
+ return static_cast<int>(0 < x) - static_cast<int>(0 > x);
+}
+
+Y_FORCE_INLINE static int CompareYsonItems(const TYsonItem& lhs, const TYsonItem& rhs)
+{
+ if (lhs.GetType() == rhs.GetType()) {
+ switch (lhs.GetType()) {
+ case EYsonItemType::EndOfStream:
+ case EYsonItemType::BeginList:
+ case EYsonItemType::EndList:
+ case EYsonItemType::EntityValue:
+ return 0;
+ case EYsonItemType::Int64Value:
+ return ComparePrimitive(lhs.UncheckedAsInt64(), rhs.UncheckedAsInt64());
+ case EYsonItemType::Uint64Value:
+ return ComparePrimitive(lhs.UncheckedAsUint64(), rhs.UncheckedAsUint64());
+ case EYsonItemType::DoubleValue:
+ return ComparePrimitive(lhs.UncheckedAsDouble(), rhs.UncheckedAsDouble());
+ case EYsonItemType::BooleanValue:
+ return ComparePrimitive(lhs.UncheckedAsBoolean(), rhs.UncheckedAsBoolean());
+ case EYsonItemType::StringValue:
+ return GetSign(TString::compare(lhs.UncheckedAsString(), rhs.UncheckedAsString()));
+
+ case EYsonItemType::BeginMap:
+ case EYsonItemType::EndMap:
+ case EYsonItemType::BeginAttributes:
+ case EYsonItemType::EndAttributes:
+ ThrowIncomparableYsonToken(lhs.GetType());
+ }
+ YT_ABORT();
+ }
+
+ const auto lhsClass = GetCompareClass(lhs.GetType());
+ const auto rhsClass = GetCompareClass(rhs.GetType());
+
+ if (lhsClass == ECompareClass::Incomparable) {
+ ThrowIncomparableYsonToken(lhs.GetType());
+ }
+ if (rhsClass == ECompareClass::Incomparable) {
+ ThrowIncomparableYsonToken(rhs.GetType());
+ }
+
+ if (lhsClass == ECompareClass::BeginValue && rhsClass == ECompareClass::BeginValue) {
+ THROW_ERROR_EXCEPTION("Incomparable scalar types %Qlv and %Qlv in YSON representation",
+ lhs.GetType(),
+ rhs.GetType());
+ }
+ return ComparePrimitive(static_cast<ui32>(lhsClass), static_cast<ui32>(rhsClass));
+}
+
+} // namespace
+
+int CompareCompositeValues(TYsonStringBuf lhs, TYsonStringBuf rhs)
+{
+ YT_ASSERT(lhs.GetType() == EYsonType::Node);
+ YT_ASSERT(rhs.GetType() == EYsonType::Node);
+
+ TMemoryInput lhsIn(lhs.AsStringBuf());
+ TMemoryInput rhsIn(rhs.AsStringBuf());
+
+ TYsonPullParser lhsParser(&lhsIn, EYsonType::Node);
+ TYsonPullParser rhsParser(&rhsIn, EYsonType::Node);
+
+ for (;;) {
+ const auto lhsItem = lhsParser.Next();
+ const auto rhsItem = rhsParser.Next();
+
+ auto res = CompareYsonItems(lhsItem, rhsItem);
+ if (res != 0) {
+ return res;
+ } else if (lhsItem.GetType() == EYsonItemType::EndOfStream) {
+ return 0;
+ }
+ Y_ASSERT(lhsItem.GetType() != EYsonItemType::EndOfStream &&
+ rhsItem.GetType() != EYsonItemType::EndOfStream);
+ }
+}
+
+TFingerprint CompositeFarmHash(TYsonStringBuf value)
+{
+ YT_ASSERT(value.GetType() == EYsonType::Node);
+
+ TMemoryInput in(value.AsStringBuf());
+ TYsonPullParser parser(&in, EYsonType::Node);
+
+ auto throwUnexpectedYsonToken = [] (const TYsonItem& item) {
+ THROW_ERROR_EXCEPTION("Unexpected YSON token %Qlv in composite value", item.GetType());
+ };
+
+ TFingerprint result;
+
+ auto item = parser.Next();
+ switch (item.GetType()) {
+ case EYsonItemType::BeginAttributes:
+ case EYsonItemType::EndAttributes:
+ case EYsonItemType::BeginMap:
+ case EYsonItemType::EndMap:
+ throwUnexpectedYsonToken(item);
+ [[fallthrough]]; // AUTOGENERATED_FALLTHROUGH_FIXME
+ case EYsonItemType::BeginList:
+ result = FarmFingerprint('[');
+ break;
+ case EYsonItemType::EndList:
+ result = FarmFingerprint(']');
+ break;
+ case EYsonItemType::EntityValue:
+ result = FarmFingerprint(0);
+ break;
+ case EYsonItemType::Int64Value:
+ result = FarmFingerprint(item.UncheckedAsInt64());
+ break;
+ case EYsonItemType::Uint64Value:
+ result = FarmFingerprint(item.UncheckedAsUint64());
+ break;
+ case EYsonItemType::DoubleValue: {
+ // NB. We cannot compute hash of double
+ // So we replicate logic of FarmFingerprint(const TUnversionedValue& ) here
+ // and cast double to ui64
+ result = FarmFingerprint(BitCast<ui64>(item.UncheckedAsDouble()));
+ break;
+ }
+ case EYsonItemType::BooleanValue:
+ result = FarmFingerprint(item.UncheckedAsBoolean());
+ break;
+ case EYsonItemType::StringValue: {
+ auto string = item.UncheckedAsString();
+ result = FarmFingerprint(FarmFingerprint(string.Data(), string.size()));
+ break;
+ }
+ case EYsonItemType::EndOfStream:
+ // Invalid yson, parser should have thrown.
+ Y_FAIL();
+ }
+
+ for (;;) {
+ item = parser.Next();
+ switch (item.GetType()) {
+ case EYsonItemType::BeginAttributes:
+ case EYsonItemType::EndAttributes:
+ case EYsonItemType::BeginMap:
+ case EYsonItemType::EndMap:
+ throwUnexpectedYsonToken(item);
+ continue;
+ case EYsonItemType::BeginList:
+ result = FarmFingerprint(result, '[');
+ continue;
+ case EYsonItemType::EndList:
+ result = FarmFingerprint(result, ']');
+ continue;
+ case EYsonItemType::EntityValue:
+ result = FarmFingerprint(result, 0);
+ continue;
+ case EYsonItemType::Int64Value:
+ result = FarmFingerprint(item.UncheckedAsInt64(), result);
+ continue;
+ case EYsonItemType::Uint64Value:
+ result = FarmFingerprint(result, item.UncheckedAsUint64());
+ continue;
+ case EYsonItemType::DoubleValue:
+ // NB. see comment above.
+ result = FarmFingerprint(BitCast<ui64>(item.UncheckedAsDouble()));
+ continue;
+ case EYsonItemType::BooleanValue:
+ result = FarmFingerprint(result, item.UncheckedAsBoolean());
+ continue;
+ case EYsonItemType::StringValue: {
+ auto string = item.UncheckedAsString();
+ result = FarmFingerprint(result, FarmFingerprint(string.Data(), string.size()));
+ continue;
+ }
+ case EYsonItemType::EndOfStream:
+ return result;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/composite_compare.h b/yt/yt/client/table_client/composite_compare.h
new file mode 100644
index 0000000000..75556472ad
--- /dev/null
+++ b/yt/yt/client/table_client/composite_compare.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "public.h"
+
+#include <library/cpp/yt/farmhash/farm_hash.h>
+
+#include <library/cpp/yt/yson_string/string.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+int CompareCompositeValues(NYson::TYsonStringBuf lhs, NYson::TYsonStringBuf rhs);
+TFingerprint CompositeFarmHash(NYson::TYsonStringBuf compositeValue);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/config.cpp b/yt/yt/client/table_client/config.cpp
new file mode 100644
index 0000000000..3f4ec24ad6
--- /dev/null
+++ b/yt/yt/client/table_client/config.cpp
@@ -0,0 +1,414 @@
+#include "config.h"
+
+#include <yt/yt/client/table_client/helpers.h>
+
+#include <yt/yt/client/tablet_client/config.h>
+#include <yt/yt/client/tablet_client/helpers.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <yt/yt/core/misc/singleton.h>
+
+#include <yt/yt/library/quantile_digest/config.h>
+
+namespace NYT::NTableClient {
+
+using namespace NChunkClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TRetentionConfigPtr& obj)
+{
+ static const TString NullPtrName("<nullptr>");
+ return obj
+ ? NYson::ConvertToYsonString(obj, NYson::EYsonFormat::Text).ToString()
+ : NullPtrName;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TRetentionConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("min_data_versions", &TThis::MinDataVersions)
+ .GreaterThanOrEqual(0)
+ .Default(1);
+ registrar.Parameter("max_data_versions", &TThis::MaxDataVersions)
+ .GreaterThanOrEqual(0)
+ .Default(1);
+ registrar.Parameter("min_data_ttl", &TThis::MinDataTtl)
+ .Default(TDuration::Minutes(30));
+ registrar.Parameter("max_data_ttl", &TThis::MaxDataTtl)
+ .Default(TDuration::Minutes(30));
+ registrar.Parameter("ignore_major_timestamp", &TThis::IgnoreMajorTimestamp)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TChunkReaderConfigPtr TChunkReaderConfig::GetDefault()
+{
+ return LeakyRefCountedSingleton<TChunkReaderConfig>();
+}
+
+void TChunkReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("sampling_mode", &TThis::SamplingMode)
+ .Default();
+
+ registrar.Parameter("sampling_rate", &TThis::SamplingRate)
+ .Default()
+ .InRange(0, 1);
+
+ registrar.Parameter("sampling_seed", &TThis::SamplingSeed)
+ .Default();
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->SamplingRate && !config->SamplingMode) {
+ config->SamplingMode = ESamplingMode::Row;
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void THashTableChunkIndexWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("load_factor", &TThis::LoadFactor)
+ .Default(0.5)
+ .GreaterThan(0.)
+ .LessThanOrEqual(1.);
+ registrar.Parameter("rehash_trial_count", &TThis::RehashTrialCount)
+ .Default(3)
+ .GreaterThan(0);
+ registrar.Parameter("enable_group_reordering", &TThis::EnableGroupReordering)
+ .Default(false);
+ registrar.Parameter("max_block_size", &TThis::MaxBlockSize)
+ .Default(128_KB);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkIndexesWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("hash_table", &TThis::HashTable)
+ .DefaultNew();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TSlimVersionedWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("top_value_quantile", &TThis::TopValueQuantile)
+ .Default(0.1)
+ .InRange(0.0, 1.0);
+ registrar.Parameter("enable_per_value_dictionary_encoding", &TThis::EnablePerValueDictionaryEncoding)
+ .Default(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkWriterTestingOptions::Register(TRegistrar registrar)
+{
+ registrar.Parameter("add_unsupported_feature", &TThis::AddUnsupportedFeature)
+ .Default(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkWriterConfig::Register(TRegistrar registrar)
+{
+ // Allow very small blocks for testing purposes.
+ registrar.Parameter("block_size", &TThis::BlockSize)
+ .GreaterThan(0)
+ .Default(16_MB);
+
+ registrar.Parameter("max_segment_value_count", &TThis::MaxSegmentValueCount)
+ .GreaterThan(0)
+ .Default(128 * 1024);
+
+ registrar.Parameter("max_buffer_size", &TThis::MaxBufferSize)
+ .GreaterThan(0)
+ .Default(16_MB);
+
+ registrar.Parameter("max_row_weight", &TThis::MaxRowWeight)
+ .GreaterThanOrEqual(5_MB)
+ .LessThanOrEqual(MaxRowWeightLimit)
+ .Default(16_MB);
+
+ registrar.Parameter("max_key_weight", &TThis::MaxKeyWeight)
+ .GreaterThan(0)
+ .LessThanOrEqual(MaxKeyWeightLimit)
+ .Default(16_KB);
+
+ registrar.Parameter("max_data_weight_between_blocks", &TThis::MaxDataWeightBetweenBlocks)
+ .GreaterThan(0)
+ .Default(2_GB);
+
+ registrar.Parameter("sample_rate", &TThis::SampleRate)
+ .InRange(0.0, 0.001)
+ .Default(0.0001);
+
+ registrar.Parameter("chunk_indexes", &TThis::ChunkIndexes)
+ .DefaultNew();
+
+ registrar.Parameter("slim", &TThis::Slim)
+ .DefaultNew();
+
+ registrar.Parameter("versioned_row_digest", &TThis::VersionedRowDigest)
+ .DefaultNew();
+
+ registrar.Parameter("testing_options", &TThis::TestingOptions)
+ .DefaultNew();
+
+ registrar.Parameter("key_filter", &TThis::KeyFilter)
+ .DefaultNew();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TKeyFilterWriterConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable", &TThis::Enable)
+ .Default(false);
+
+ registrar.Parameter("block_size", &TThis::BlockSize)
+ .GreaterThan(0)
+ .Default(64_KB);
+
+ registrar.Parameter("trial_count", &TThis::TrialCount)
+ .GreaterThan(0)
+ .Default(100);
+
+ registrar.Parameter("bits_per_key", &TThis::BitsPerKey)
+ .InRange(0, 62)
+ .Optional();
+
+ registrar.Parameter("false_positive_rate", &TThis::FalsePositiveRate)
+ .InRange(0, 1.0 / (1ll << 62))
+ .Default()
+ .Optional();
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->BitsPerKey && config->FalsePositiveRate) {
+ THROW_ERROR_EXCEPTION("At most one of \"bits_per_key\" and "
+ "\"false_positive_rate\" can be specified");
+ }
+
+ if (config->FalsePositiveRate) {
+ int bitsPerKey = 1;
+
+ while ((1ll << bitsPerKey) * *config->FalsePositiveRate < 1) {
+ ++bitsPerKey;
+ }
+
+ config->EffectiveBitsPerKey = bitsPerKey;
+ } else {
+ config->EffectiveBitsPerKey = config->BitsPerKey.value_or(DefaultBitsPerKey);
+ }
+ });
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void TBatchHunkReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("max_hunk_count_per_read", &TThis::MaxHunkCountPerRead)
+ .GreaterThan(0)
+ .Default(10'000);
+ registrar.Parameter("max_total_hunk_length_per_read", &TThis::MaxTotalHunkLengthPerRead)
+ .GreaterThan(0)
+ .Default(16_MB);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TTableReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("suppress_access_tracking", &TThis::SuppressAccessTracking)
+ .Default(false);
+ registrar.Parameter("suppress_expiration_timeout_renewal", &TThis::SuppressExpirationTimeoutRenewal)
+ .Default(false);
+ registrar.Parameter("unavailable_chunk_strategy", &TThis::UnavailableChunkStrategy)
+ .Default(EUnavailableChunkStrategy::Restore);
+ registrar.Parameter("chunk_availability_policy", &TThis::ChunkAvailabilityPolicy)
+ .Default(EChunkAvailabilityPolicy::Repairable);
+ registrar.Parameter("max_read_duration", &TThis::MaxReadDuration)
+ .Default();
+ registrar.Parameter("dynamic_store_reader", &TThis::DynamicStoreReader)
+ .DefaultNew();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TTypeConversionConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_type_conversion", &TThis::EnableTypeConversion)
+ .Default(false);
+ registrar.Parameter("enable_string_to_all_conversion", &TThis::EnableStringToAllConversion)
+ .Default(false);
+ registrar.Parameter("enable_all_to_string_conversion", &TThis::EnableAllToStringConversion)
+ .Default(false);
+ registrar.Parameter("enable_integral_type_conversion", &TThis::EnableIntegralTypeConversion)
+ .Default(true);
+ registrar.Parameter("enable_integral_to_double_conversion", &TThis::EnableIntegralToDoubleConversion)
+ .Default(false);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->EnableTypeConversion) {
+ config->EnableStringToAllConversion = true;
+ config->EnableAllToStringConversion = true;
+ config->EnableIntegralTypeConversion = true;
+ config->EnableIntegralToDoubleConversion = true;
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TInsertRowsFormatConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_null_to_yson_entity_conversion", &TThis::EnableNullToYsonEntityConversion)
+ .Default(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TChunkReaderOptionsPtr TChunkReaderOptions::GetDefault()
+{
+ return LeakyRefCountedSingleton<TChunkReaderOptions>();
+}
+
+void TChunkReaderOptions::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_table_index", &TThis::EnableTableIndex)
+ .Default(false);
+
+ registrar.Parameter("enable_range_index", &TThis::EnableRangeIndex)
+ .Default(false);
+
+ registrar.Parameter("enable_row_index", &TThis::EnableRowIndex)
+ .Default(false);
+
+ registrar.Parameter("enable_tablet_index", &TThis::EnableTabletIndex)
+ .Default(false);
+
+ registrar.Parameter("dynamic_table", &TThis::DynamicTable)
+ .Default(false);
+
+ registrar.Parameter("enable_key_widening", &TThis::EnableKeyWidening)
+ .Default(false);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->EnableRangeIndex && !config->EnableRowIndex) {
+ THROW_ERROR_EXCEPTION("\"enable_row_index\" must be set when \"enable_range_index\" is set");
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TChunkWriterOptions::Register(TRegistrar registrar)
+{
+ registrar.Parameter("validate_sorted", &TThis::ValidateSorted)
+ .Default(true);
+ registrar.Parameter("validate_row_weight", &TThis::ValidateRowWeight)
+ .Default(false);
+ registrar.Parameter("validate_key_weight", &TThis::ValidateKeyWeight)
+ .Default(false);
+ registrar.Parameter("validate_duplicate_ids", &TThis::ValidateDuplicateIds)
+ .Default(false);
+ registrar.Parameter("validate_column_count", &TThis::ValidateColumnCount)
+ .Default(false);
+ registrar.Parameter("validate_any_is_valid_yson", &TThis::ValidateAnyIsValidYson)
+ .Default(false);
+ registrar.Parameter("validate_unique_keys", &TThis::ValidateUniqueKeys)
+ .Default(false);
+ registrar.Parameter("explode_on_validation_error", &TThis::ExplodeOnValidationError)
+ .Default(false);
+ registrar.Parameter("optimize_for", &TThis::OptimizeFor)
+ .Default(EOptimizeFor::Lookup);
+ registrar.Parameter("chunk_format", &TThis::ChunkFormat)
+ .Default();
+ registrar.Parameter("evaluate_computed_columns", &TThis::EvaluateComputedColumns)
+ .Default(true);
+ registrar.Parameter("enable_skynet_sharing", &TThis::EnableSkynetSharing)
+ .Default(false);
+ registrar.Parameter("return_boundary_keys", &TThis::ReturnBoundaryKeys)
+ .Default(true);
+ registrar.Parameter("cast_any_to_composite", &TThis::CastAnyToCompositeNode)
+ .Default();
+ registrar.Parameter("single_column_group_by_default", &TThis::SingleColumnGroupByDefault)
+ .Default();
+
+ registrar.Parameter("schema_modification", &TThis::SchemaModification)
+ .Default(ETableSchemaModification::None);
+ registrar.Parameter("max_heavy_columns", &TThis::MaxHeavyColumns)
+ .Default(0);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->ValidateUniqueKeys && !config->ValidateSorted) {
+ THROW_ERROR_EXCEPTION("\"validate_unique_keys\" is allowed to be true only if \"validate_sorted\" is true");
+ }
+
+ if (config->CastAnyToCompositeNode) {
+ try {
+ config->CastAnyToComposite = NYTree::ConvertTo<bool>(config->CastAnyToCompositeNode);
+ } catch (const std::exception&) {
+ // COMPAT: Do nothing for backward compatibility.
+ }
+ }
+
+ switch (config->SchemaModification) {
+ case ETableSchemaModification::None:
+ break;
+
+ case ETableSchemaModification::UnversionedUpdate:
+ if (!config->ValidateSorted || !config->ValidateUniqueKeys) {
+ THROW_ERROR_EXCEPTION(
+ "\"schema_modification\" is allowed to be %Qlv only if "
+ "\"validate_sorted\" and \"validate_unique_keys\" are true",
+ config->SchemaModification);
+ }
+ break;
+
+ case ETableSchemaModification::UnversionedUpdateUnsorted:
+ THROW_ERROR_EXCEPTION("\"schema_modification\" is not allowed to be %Qlv",
+ config->SchemaModification);
+
+ default:
+ YT_ABORT();
+ }
+
+ if (config->ChunkFormat) {
+ ValidateTableChunkFormatAndOptimizeFor(*config->ChunkFormat, config->OptimizeFor);
+ }
+ });
+}
+
+EChunkFormat TChunkWriterOptions::GetEffectiveChunkFormat(bool versioned) const
+{
+ return ChunkFormat.value_or(DefaultFormatFromOptimizeFor(OptimizeFor, versioned));
+}
+
+void TChunkWriterOptions::EnableValidationOptions(bool validateAnyIsValidYson)
+{
+ ValidateDuplicateIds = true;
+ ValidateRowWeight = true;
+ ValidateKeyWeight = true;
+ ValidateColumnCount = true;
+ ValidateAnyIsValidYson = validateAnyIsValidYson;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TVersionedRowDigestConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable", &TThis::Enable)
+ .Default(false);
+ registrar.Parameter("t_digest", &TThis::TDigest)
+ .DefaultNew();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/config.h b/yt/yt/client/table_client/config.h
new file mode 100644
index 0000000000..7c8a96f7b8
--- /dev/null
+++ b/yt/yt/client/table_client/config.h
@@ -0,0 +1,381 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/chunk_client/config.h>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <yt/yt/library/quantile_digest/public.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRetentionConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ int MinDataVersions;
+ int MaxDataVersions;
+ TDuration MinDataTtl;
+ TDuration MaxDataTtl;
+ bool IgnoreMajorTimestamp;
+
+ REGISTER_YSON_STRUCT(TRetentionConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TRetentionConfig)
+
+TString ToString(const TRetentionConfigPtr& obj);
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ESamplingMode,
+ ((Row) (1))
+ ((Block) (2))
+);
+
+class TChunkReaderConfig
+ : public virtual NChunkClient::TBlockFetcherConfig
+{
+public:
+ std::optional<ESamplingMode> SamplingMode;
+ std::optional<double> SamplingRate;
+ std::optional<ui64> SamplingSeed;
+
+ static TChunkReaderConfigPtr GetDefault();
+
+ REGISTER_YSON_STRUCT(TChunkReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkWriterTestingOptions
+ : public NYTree::TYsonStruct
+{
+public:
+ //! If true, unsupported chunk feature is added to chunk meta.
+ bool AddUnsupportedFeature;
+
+ REGISTER_YSON_STRUCT(TChunkWriterTestingOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkWriterTestingOptions)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class THashTableChunkIndexWriterConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ //! Hash table load factor.
+ double LoadFactor;
+
+ //! Final hash table seed will be picked considering this number of rehash trials.
+ int RehashTrialCount;
+
+ // TODO(akozhikhov).
+ bool EnableGroupReordering;
+
+ //! Unless null, key set will be split to produce multiple hash tables,
+ //! each of which corresponds to a single system block and is not greater than #MaxBlockSize.
+ std::optional<int> MaxBlockSize;
+
+ REGISTER_YSON_STRUCT(THashTableChunkIndexWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(THashTableChunkIndexWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkIndexesWriterConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ THashTableChunkIndexWriterConfigPtr HashTable;
+
+ REGISTER_YSON_STRUCT(TChunkIndexesWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkIndexesWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSlimVersionedWriterConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ double TopValueQuantile;
+ bool EnablePerValueDictionaryEncoding;
+
+ REGISTER_YSON_STRUCT(TSlimVersionedWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TSlimVersionedWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkWriterConfig
+ : public NChunkClient::TEncodingWriterConfig
+{
+public:
+ i64 BlockSize;
+
+ i64 MaxSegmentValueCount;
+
+ i64 MaxBufferSize;
+
+ i64 MaxRowWeight;
+
+ i64 MaxKeyWeight;
+
+ //! This limits ensures that chunk index is dense enough
+ //! e.g. to produce good slices for reduce.
+ i64 MaxDataWeightBetweenBlocks;
+
+ double SampleRate;
+
+ TChunkIndexesWriterConfigPtr ChunkIndexes;
+
+ TSlimVersionedWriterConfigPtr Slim;
+
+ TVersionedRowDigestConfigPtr VersionedRowDigest;
+
+ TChunkWriterTestingOptionsPtr TestingOptions;
+
+ TKeyFilterWriterConfigPtr KeyFilter;
+
+ REGISTER_YSON_STRUCT(TChunkWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TKeyFilterWriterConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ bool Enable;
+
+ i64 BlockSize;
+
+ int TrialCount;
+
+ std::optional<int> BitsPerKey;
+ std::optional<double> FalsePositiveRate;
+
+ static constexpr int DefaultBitsPerKey = 8;
+ int EffectiveBitsPerKey;
+
+ REGISTER_YSON_STRUCT(TKeyFilterWriterConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TKeyFilterWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBatchHunkReaderConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ int MaxHunkCountPerRead;
+ i64 MaxTotalHunkLengthPerRead;
+
+ REGISTER_YSON_STRUCT(TBatchHunkReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TBatchHunkReaderConfig)
+
+///////////////////////////////////////////////////////////////////////////////
+
+class TTableReaderConfig
+ : public virtual NChunkClient::TMultiChunkReaderConfig
+ , public virtual TChunkReaderConfig
+ , public TBatchHunkReaderConfig
+ , public NChunkClient::TChunkFragmentReaderConfig
+{
+public:
+ bool SuppressAccessTracking;
+ bool SuppressExpirationTimeoutRenewal;
+ EUnavailableChunkStrategy UnavailableChunkStrategy;
+ NChunkClient::EChunkAvailabilityPolicy ChunkAvailabilityPolicy;
+ std::optional<TDuration> MaxReadDuration;
+
+ NTabletClient::TRetryingRemoteDynamicStoreReaderConfigPtr DynamicStoreReader;
+
+ REGISTER_YSON_STRUCT(TTableReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableWriterConfig
+ : public TChunkWriterConfig
+ , public NChunkClient::TMultiChunkWriterConfig
+{
+ REGISTER_YSON_STRUCT(TTableWriterConfig);
+
+ static void Register(TRegistrar)
+ { }
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableWriterConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTypeConversionConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ bool EnableTypeConversion;
+ bool EnableStringToAllConversion;
+ bool EnableAllToStringConversion;
+ bool EnableIntegralTypeConversion;
+ bool EnableIntegralToDoubleConversion;
+
+ REGISTER_YSON_STRUCT(TTypeConversionConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TTypeConversionConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TInsertRowsFormatConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ bool EnableNullToYsonEntityConversion;
+
+ REGISTER_YSON_STRUCT(TInsertRowsFormatConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TInsertRowsFormatConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkReaderOptions
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ bool EnableTableIndex;
+ bool EnableRangeIndex;
+ bool EnableRowIndex;
+ bool DynamicTable;
+ bool EnableTabletIndex;
+ bool EnableKeyWidening;
+
+ static TChunkReaderOptionsPtr GetDefault();
+
+ REGISTER_YSON_STRUCT(TChunkReaderOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkReaderOptions)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TChunkWriterOptions
+ : public virtual NChunkClient::TEncodingWriterOptions
+{
+public:
+ bool ValidateSorted;
+ bool ValidateRowWeight;
+ bool ValidateKeyWeight;
+ bool ValidateDuplicateIds;
+ bool ValidateUniqueKeys;
+ bool ExplodeOnValidationError;
+ bool ValidateColumnCount;
+ bool ValidateAnyIsValidYson;
+ bool EvaluateComputedColumns;
+ bool EnableSkynetSharing;
+ bool ReturnBoundaryKeys;
+ bool CastAnyToComposite = false;
+ bool SingleColumnGroupByDefault = false;
+ NYTree::INodePtr CastAnyToCompositeNode;
+
+ ETableSchemaModification SchemaModification;
+
+ EOptimizeFor OptimizeFor;
+ std::optional<NChunkClient::EChunkFormat> ChunkFormat;
+ NChunkClient::EChunkFormat GetEffectiveChunkFormat(bool versioned) const;
+
+ //! Maximum number of heavy columns in approximate statistics.
+ int MaxHeavyColumns;
+
+ void EnableValidationOptions(bool validateAnyIsValidYson = false);
+
+ REGISTER_YSON_STRUCT(TChunkWriterOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TChunkWriterOptions)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TVersionedRowDigestConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ bool Enable;
+ TTDigestConfigPtr TDigest;
+
+ REGISTER_YSON_STRUCT(TVersionedRowDigestConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TVersionedRowDigestConfig)
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct TRowBatchReadOptions
+{
+ //! The desired number of rows to read.
+ //! This is just an estimate; not all readers support this limit.
+ i64 MaxRowsPerRead = 10000;
+
+ //! The desired data weight to read.
+ //! This is just an estimate; not all readers support this limit.
+ i64 MaxDataWeightPerRead = 16_MB;
+
+ //! If true then the reader may return a columnar batch.
+ //! If false then the reader must return a non-columnar batch.
+ bool Columnar = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/helpers-inl.h b/yt/yt/client/table_client/helpers-inl.h
new file mode 100644
index 0000000000..0231b1c21f
--- /dev/null
+++ b/yt/yt/client/table_client/helpers-inl.h
@@ -0,0 +1,563 @@
+#ifndef HELPERS_INL_H_
+#error "Direct inclusion of this file is not allowed, include helpers.h"
+// For the sake of sane code completion.
+#include "helpers.h"
+#endif
+
+#include "row_buffer.h"
+#include "row_batch.h"
+
+#include <yt/yt/core/yson/protobuf_interop.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <library/cpp/yt/misc/strong_typedef.h>
+
+#include <array>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+// Scalar inline types
+
+#define XX(T) \
+ template <> \
+ struct TUnversionedValueConversionTraits<T, void> \
+ { \
+ static constexpr bool Scalar = true; \
+ static constexpr bool Inline = true; \
+ };
+
+XX(std::nullopt_t)
+XX(i64)
+XX(ui64)
+XX(i32)
+XX(ui32)
+XX(i16)
+XX(ui16)
+XX(i8)
+XX(ui8)
+XX(bool)
+XX(double)
+XX(TInstant)
+XX(TDuration)
+
+#undef XX
+
+////////////////////////////////////////////////////////////////////////////////
+// Scalar non-inline types
+
+#define XX(T) \
+ template <> \
+ struct TUnversionedValueConversionTraits<T, void> \
+ { \
+ static constexpr bool Scalar = true; \
+ static constexpr bool Inline = false; \
+ };
+
+XX(TString)
+XX(TStringBuf)
+XX(TGuid)
+
+#undef XX
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+ requires TEnumTraits<T>::IsEnum
+struct TUnversionedValueConversionTraits<T>
+{
+ static constexpr bool Scalar = true;
+ static constexpr bool Inline = !TEnumTraits<T>::IsStringSerializableEnum;
+};
+
+template <class T>
+struct TUnversionedValueConversionTraits<std::optional<T>>
+{
+ static constexpr bool Scalar = TUnversionedValueConversionTraits<T>::Scalar;
+ static constexpr bool Inline = TUnversionedValueConversionTraits<T>::Inline;
+};
+
+template <class T, class TTag>
+struct TUnversionedValueConversionTraits<TStrongTypedef<T, TTag>>
+{
+ static constexpr bool Scalar = TUnversionedValueConversionTraits<T>::Scalar;
+ static constexpr bool Inline = TUnversionedValueConversionTraits<T>::Inline;
+};
+
+template <class T>
+struct TUnversionedValueConversionTraits<TAnnotatedValue<T>>
+{
+ static constexpr bool Scalar = TUnversionedValueConversionTraits<T>::Scalar;
+ static constexpr bool Inline = TUnversionedValueConversionTraits<T>::Inline;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const TAnnotatedValue<T>& value,
+ const TRowBufferPtr& rowBuffer,
+ int /*id*/,
+ EValueFlags /*flags*/)
+{
+ ToUnversionedValue(unversionedValue, value.Value, rowBuffer, value.Id, value.Flags);
+}
+
+template <class T>
+struct TValueWithIdTrait
+{
+ static constexpr bool WithId = false;
+};
+
+template <class T>
+struct TValueWithIdTrait<TAnnotatedValue<T>>
+{
+ static constexpr bool WithId = true;
+};
+
+template <class... Ts>
+struct TRowValueTypesChecker
+{
+ static constexpr bool AllWithIds = (... && TValueWithIdTrait<Ts>::WithId);
+ static constexpr bool AllWithoutIds = (... && !TValueWithIdTrait<Ts>::WithId);
+ static_assert(AllWithIds || AllWithoutIds, "All values must be wrapped into TValueWithId or none must be.");
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+ requires TEnumTraits<T>::IsEnum
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ T value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ if constexpr (TEnumTraits<T>::IsStringSerializableEnum) {
+ ToUnversionedValue(unversionedValue, NYT::FormatEnum(value), rowBuffer, id, flags);
+ } else if constexpr (TEnumTraits<T>::IsBitEnum) {
+ ToUnversionedValue(unversionedValue, static_cast<ui64>(value), rowBuffer, id, flags);
+ } else {
+ ToUnversionedValue(unversionedValue, static_cast<i64>(value), rowBuffer, id, flags);
+ }
+}
+
+template <class T>
+ requires TEnumTraits<T>::IsEnum
+void FromUnversionedValue(
+ T* value,
+ TUnversionedValue unversionedValue)
+{
+ switch (unversionedValue.Type) {
+ case EValueType::Int64:
+ *value = static_cast<T>(unversionedValue.Data.Int64);
+ break;
+ case EValueType::Uint64:
+ *value = static_cast<T>(unversionedValue.Data.Uint64);
+ break;
+ case EValueType::String:
+ *value = NYT::ParseEnum<T>(unversionedValue.AsStringBuf());
+ break;
+ default:
+ THROW_ERROR_EXCEPTION("Cannot parse enum value from %Qlv",
+ unversionedValue.Type);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ProtobufToUnversionedValueImpl(
+ TUnversionedValue* unversionedValue,
+ const google::protobuf::Message& value,
+ const NYson::TProtobufMessageType* type,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags);
+
+template <class T>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const T& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+ requires std::is_convertible<T*, google::protobuf::Message*>::value
+{
+ ProtobufToUnversionedValueImpl(
+ unversionedValue,
+ value,
+ NYson::ReflectProtobufMessageType<T>(),
+ rowBuffer,
+ id,
+ flags);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void UnversionedValueToProtobufImpl(
+ google::protobuf::Message* value,
+ const NYson::TProtobufMessageType* type,
+ TUnversionedValue unversionedValue);
+
+template <class T>
+void FromUnversionedValue(
+ T* value,
+ TUnversionedValue unversionedValue)
+ requires std::is_convertible<T*, google::protobuf::Message*>::value
+{
+ UnversionedValueToProtobufImpl(
+ value,
+ NYson::ReflectProtobufMessageType<T>(),
+ unversionedValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const std::optional<T>& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ if (value) {
+ ToUnversionedValue(unversionedValue, *value, rowBuffer, id, flags);
+ } else {
+ *unversionedValue = MakeUnversionedSentinelValue(EValueType::Null, id, flags);
+ }
+}
+
+template <class T>
+void FromUnversionedValue(
+ std::optional<T>* value,
+ TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ *value = std::nullopt;
+ } else {
+ value->emplace();
+ FromUnversionedValue(&**value, unversionedValue);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, class TTag>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const TStrongTypedef<T, TTag>& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ ToUnversionedValue(unversionedValue, value.Underlying(), rowBuffer, id, flags);
+}
+
+template <class T, class TTag>
+void FromUnversionedValue(
+ TStrongTypedef<T, TTag>* value,
+ TUnversionedValue unversionedValue)
+{
+ FromUnversionedValue(&value->Underlying(), unversionedValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void ToVersionedValue(
+ TVersionedValue* versionedValue,
+ T&& value,
+ const TRowBufferPtr& rowBuffer,
+ NTransactionClient::TTimestamp timestamp,
+ int id,
+ EValueFlags flags)
+{
+ ToUnversionedValue(
+ static_cast<TUnversionedValue*>(versionedValue),
+ std::forward<T>(value),
+ rowBuffer,
+ id,
+ flags);
+ versionedValue->Timestamp = timestamp;
+}
+
+template <class T>
+TVersionedValue ToVersionedValue(
+ T&& value,
+ const TRowBufferPtr& rowBuffer,
+ NTransactionClient::TTimestamp timestamp,
+ int id,
+ EValueFlags flags)
+{
+ TVersionedValue versionedValue;
+ ToVersionedValue(
+ &versionedValue,
+ std::forward<T>(value),
+ rowBuffer,
+ timestamp,
+ id,
+ flags);
+ return versionedValue;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ListToUnversionedValueImpl(
+ TUnversionedValue* unversionedValue,
+ const std::function<bool(TUnversionedValue*)> producer,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags);
+
+template <class T>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const std::vector<T>& values,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ size_t index = 0;
+ ListToUnversionedValueImpl(
+ unversionedValue,
+ [&] (TUnversionedValue* itemValue) mutable -> bool {
+ if (index == values.size()) {
+ return false;
+ }
+ ToUnversionedValue(itemValue, values[index++], rowBuffer);
+ return true;
+ },
+ rowBuffer,
+ id,
+ flags);
+}
+
+void UnversionedValueToListImpl(
+ std::function<google::protobuf::Message*()> appender,
+ const NYson::TProtobufMessageType* type,
+ TUnversionedValue unversionedValue);
+
+template <class T>
+void FromUnversionedValue(
+ std::vector<T>* values,
+ TUnversionedValue unversionedValue)
+ requires std::is_convertible<T*, google::protobuf::Message*>::value
+{
+ values->clear();
+ UnversionedValueToListImpl(
+ [&] {
+ values->emplace_back();
+ return &values->back();
+ },
+ NYson::ReflectProtobufMessageType<T>(),
+ unversionedValue);
+}
+
+void UnversionedValueToListImpl(
+ std::function<void(TUnversionedValue)> appender,
+ TUnversionedValue unversionedValue);
+
+template <class T>
+void FromUnversionedValue(
+ std::vector<T>* values,
+ TUnversionedValue unversionedValue)
+ requires TUnversionedValueConversionTraits<T>::Scalar
+{
+ values->clear();
+ UnversionedValueToListImpl(
+ [&] (TUnversionedValue itemValue) {
+ values->emplace_back();
+ FromUnversionedValue(&values->back(), itemValue);
+ },
+ unversionedValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void MapToUnversionedValueImpl(
+ TUnversionedValue* unversionedValue,
+ const std::function<bool(TString*, TUnversionedValue*)> producer,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags);
+
+template <class TKey, class TValue>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const THashMap<TKey, TValue>& map,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ auto it = map.begin();
+ MapToUnversionedValueImpl(
+ unversionedValue,
+ [&] (TString* itemKey, TUnversionedValue* itemValue) mutable -> bool {
+ if (it == map.end()) {
+ return false;
+ }
+ *itemKey = ToString(it->first);
+ ToUnversionedValue(itemValue, it->second, rowBuffer);
+ ++it;
+ return true;
+ },
+ rowBuffer,
+ id,
+ flags);
+}
+
+void UnversionedValueToMapImpl(
+ std::function<google::protobuf::Message*(TString)> appender,
+ const NYson::TProtobufMessageType* type,
+ TUnversionedValue unversionedValue);
+
+template <class TKey, class TValue>
+void FromUnversionedValue(
+ THashMap<TKey, TValue>* map,
+ TUnversionedValue unversionedValue)
+ requires std::is_convertible<TValue*, ::google::protobuf::Message*>::value
+{
+ map->clear();
+ UnversionedValueToMapImpl(
+ [&] (TString key) {
+ auto pair = map->emplace(FromString<TKey>(std::move(key)), TValue());
+ return &pair.first->second;
+ },
+ NYson::ReflectProtobufMessageType<TValue>(),
+ unversionedValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class... Ts>
+auto ToUnversionedValues(
+ const TRowBufferPtr& rowBuffer,
+ Ts&&... values)
+ -> std::array<TUnversionedValue, sizeof...(Ts)>
+{
+ TRowValueTypesChecker<Ts...>();
+ std::array<TUnversionedValue, sizeof...(Ts)> array;
+ auto* current = array.data();
+ int id = 0;
+ (ToUnversionedValue(current++, std::forward<Ts>(values), rowBuffer, id++), ...);
+ return array;
+}
+
+template <class... Ts>
+void FromUnversionedRow(
+ TUnversionedRow row,
+ Ts*... values)
+{
+ if (row.GetCount() < sizeof...(Ts)) {
+ THROW_ERROR_EXCEPTION("Invalid number of values in row: expected >=%v, got %v",
+ sizeof...(Ts),
+ row.GetCount());
+ }
+ const auto* current = row.Begin();
+ (FromUnversionedValue(values, *current++), ...);
+}
+
+namespace NDetail {
+
+template <size_t Index, class... Ts>
+void TupleFromUnversionedRowHelper(std::tuple<Ts...>* tuple, TUnversionedRow row)
+{
+ if constexpr(Index < sizeof...(Ts)) {
+ FromUnversionedValue(&std::get<Index>(*tuple), row[Index]);
+ TupleFromUnversionedRowHelper<Index + 1, Ts...>(tuple, row);
+ }
+}
+
+} // namespace NDetail
+
+template <class... Ts>
+std::tuple<Ts...> FromUnversionedRow(TUnversionedRow row)
+{
+ if (row.GetCount() < sizeof...(Ts)) {
+ THROW_ERROR_EXCEPTION("Invalid number of values in row: expected >=%v, got %v",
+ sizeof...(Ts),
+ row.GetCount());
+ }
+ std::tuple<Ts...> result;
+ NDetail::TupleFromUnversionedRowHelper<0, Ts...>(&result, row);
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+TUnversionedValue ToUnversionedValue(T&& value, const TRowBufferPtr& rowBuffer, int id, EValueFlags flags)
+{
+ TUnversionedValue unversionedValue;
+ ToUnversionedValue(&unversionedValue, std::forward<T>(value), rowBuffer, id, flags);
+ return unversionedValue;
+}
+
+template <class T>
+T FromUnversionedValue(TUnversionedValue unversionedValue)
+{
+ T value;
+ FromUnversionedValue(&value, unversionedValue);
+ return value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class... Ts>
+TUnversionedOwningRow MakeUnversionedOwningRow(Ts&&... values)
+{
+ TRowValueTypesChecker<Ts...>();
+ constexpr bool AllTypesInline = (... && TUnversionedValueConversionTraits<Ts>::Inline);
+ auto rowBuffer = AllTypesInline ? TRowBufferPtr() : New<TRowBuffer>();
+ TUnversionedOwningRowBuilder builder(sizeof...(Ts));
+ int id = 0;
+ (builder.AddValue(ToUnversionedValue(std::forward<Ts>(values), rowBuffer, id++)), ...);
+ return builder.FinishRow();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class... Ts>
+void TUnversionedRowsBuilder::AddRow(Ts&&... values)
+{
+ TRowValueTypesChecker<Ts...>();
+ auto row = RowBuffer_->AllocateUnversioned(sizeof...(Ts));
+ auto* current = row.Begin();
+ int id = 0;
+ (ToUnversionedValue(current++, std::forward<Ts>(values), RowBuffer_, id++, /*flags*/ EValueFlags::None), ...);
+ Rows_.push_back(row);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TReader, class... TArgs>
+auto ReadRowBatch(const TIntrusivePtr<TReader>& reader, TArgs&&... args)
+{
+ while (true) {
+ auto batch = reader->Read(std::forward<TArgs>(args)...);
+ if (!batch || !batch->IsEmpty()) {
+ return batch;
+ }
+ NConcurrency::WaitFor(reader->GetReadyEvent())
+ .ThrowOnError();
+ }
+}
+
+template <class TWriter, class... TArgs>
+auto WriteRowBatch(const TIntrusivePtr<TWriter>& writer, TArgs&&... args)
+{
+ if (!writer->Write(std::forward<TArgs>(args)...)) {
+ NConcurrency::WaitFor(writer->GetReadyEvent())
+ .ThrowOnError();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/helpers.cpp b/yt/yt/client/table_client/helpers.cpp
new file mode 100644
index 0000000000..66343bc32b
--- /dev/null
+++ b/yt/yt/client/table_client/helpers.cpp
@@ -0,0 +1,1570 @@
+#include "helpers.h"
+#include "schema.h"
+#include "name_table.h"
+#include "key_bound.h"
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <yt/yt/core/net/address.h>
+
+#include <yt/yt/core/yson/parser.h>
+#include <yt/yt/core/yson/protobuf_interop.h>
+#include <yt/yt/core/yson/token_writer.h>
+
+#include <library/cpp/yt/coding/varint.h>
+
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+namespace NYT::NTableClient {
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NNet;
+using namespace NChunkClient;
+
+using namespace google::protobuf;
+using namespace google::protobuf::io;
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsValidTableChunkFormat(EChunkFormat chunkFormat)
+{
+ return
+ chunkFormat == EChunkFormat::TableUnversionedSchemaful ||
+ chunkFormat == EChunkFormat::TableUnversionedSchemalessHorizontal ||
+ chunkFormat == EChunkFormat::TableUnversionedColumnar ||
+ chunkFormat == EChunkFormat::TableVersionedSimple ||
+ chunkFormat == EChunkFormat::TableVersionedIndexed ||
+ chunkFormat == EChunkFormat::TableVersionedColumnar ||
+ chunkFormat == EChunkFormat::TableVersionedSlim;
+}
+
+bool IsTableChunkFormatVersioned(EChunkFormat chunkFormat)
+{
+ return
+ chunkFormat == EChunkFormat::TableVersionedSimple ||
+ chunkFormat == EChunkFormat::TableVersionedIndexed ||
+ chunkFormat == EChunkFormat::TableVersionedColumnar ||
+ chunkFormat == EChunkFormat::TableVersionedSlim;
+}
+
+void ValidateTableChunkFormat(EChunkFormat chunkFormat)
+{
+ if (!IsValidTableChunkFormat(chunkFormat)) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::InvalidTableChunkFormat,
+ "%Qlv is not a valid table chunk format",
+ chunkFormat);
+ }
+}
+
+void ValidateTableChunkFormatAndOptimizeFor(
+ EChunkFormat chunkFormat,
+ EOptimizeFor optimizeFor)
+{
+ ValidateTableChunkFormat(chunkFormat);
+ if (OptimizeForFromFormat(chunkFormat) != optimizeFor) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::InvalidTableChunkFormat,
+ "%Qlv is not a valid %Qlv chunk format",
+ chunkFormat,
+ optimizeFor);
+ }
+}
+
+void ValidateTableChunkFormatVersioned(
+ EChunkFormat chunkFormat,
+ bool versioned)
+{
+ if (IsTableChunkFormatVersioned(chunkFormat) != versioned) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::InvalidTableChunkFormat,
+ "%Qlv is not a valid %v chunk format",
+ chunkFormat,
+ versioned ? "versioned" : "unversioned");
+ }
+}
+
+EOptimizeFor OptimizeForFromFormat(EChunkFormat chunkFormat)
+{
+ ValidateTableChunkFormat(chunkFormat);
+ switch (chunkFormat) {
+ case EChunkFormat::TableUnversionedSchemaful:
+ case EChunkFormat::TableUnversionedSchemalessHorizontal:
+ case EChunkFormat::TableVersionedSimple:
+ case EChunkFormat::TableVersionedIndexed:
+ case EChunkFormat::TableVersionedSlim:
+ return EOptimizeFor::Lookup;
+
+ case EChunkFormat::TableUnversionedColumnar:
+ case EChunkFormat::TableVersionedColumnar:
+ return EOptimizeFor::Scan;
+
+ default:
+ YT_ABORT();
+ }
+}
+
+EChunkFormat DefaultFormatFromOptimizeFor(
+ EOptimizeFor optimizeFor,
+ bool versioned)
+{
+ if (versioned) {
+ switch (optimizeFor) {
+ case EOptimizeFor::Lookup:
+ return EChunkFormat::TableVersionedSimple;
+ case EOptimizeFor::Scan:
+ return EChunkFormat::TableVersionedColumnar;
+ default:
+ YT_ABORT();
+ }
+ } else {
+ switch (optimizeFor) {
+ case EOptimizeFor::Lookup:
+ return EChunkFormat::TableUnversionedSchemalessHorizontal;
+ case EOptimizeFor::Scan:
+ return EChunkFormat::TableUnversionedColumnar;
+ default:
+ YT_ABORT();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+void YTreeNodeToUnversionedValue(
+ TUnversionedOwningRowBuilder* builder,
+ const INodePtr& value,
+ int id,
+ EValueFlags flags)
+{
+ switch (value->GetType()) {
+ #define XX(type, cppType) \
+ case ENodeType::type: \
+ builder->AddValue(MakeUnversioned ## type ## Value(value->As ## type()->GetValue(), id, flags)); \
+ break;
+ ITERATE_SCALAR_YTREE_NODE_TYPES(XX)
+ #undef XX
+ case ENodeType::Entity:
+ builder->AddValue(MakeUnversionedSentinelValue(EValueType::Null, id, flags));
+ break;
+ default:
+ builder->AddValue(MakeUnversionedAnyValue(ConvertToYsonString(value).AsStringBuf(), id, flags));
+ break;
+ }
+}
+
+} // namespace
+
+TUnversionedOwningRow YsonToSchemafulRow(
+ const TString& yson,
+ const TTableSchema& tableSchema,
+ bool treatMissingAsNull,
+ NYson::EYsonType ysonType,
+ bool validateValues)
+{
+ auto nameTable = TNameTable::FromSchema(tableSchema);
+
+ auto rowParts = ConvertTo<THashMap<TString, INodePtr>>(
+ TYsonString(yson, ysonType));
+
+ TUnversionedOwningRowBuilder rowBuilder;
+ auto validateAndAddValue = [&rowBuilder, &validateValues] (const TUnversionedValue& value, const TColumnSchema& column) {
+ if (validateValues) {
+ ValidateValueType(
+ value,
+ column,
+ /*typeAnyAcceptsAllValues*/ true,
+ /*ignoreRequired*/ false,
+ /*validateAnyIsValidYson*/ false);
+ }
+
+ rowBuilder.AddValue(value);
+ };
+
+ auto addValue = [&] (int id, INodePtr value) {
+ try {
+ auto column = tableSchema.Columns()[id];
+ if (value->GetType() == ENodeType::Entity) {
+ validateAndAddValue(MakeUnversionedSentinelValue(
+ value->Attributes().Get<EValueType>("type", EValueType::Null), id), column);
+ return;
+ }
+
+ auto type = column.GetWireType();
+ switch (type) {
+ #define XX(type, cppType) \
+ case EValueType::type: \
+ validateAndAddValue(MakeUnversioned ## type ## Value(value->As ## type()->GetValue(), id), column); \
+ break;
+ ITERATE_SCALAR_YTREE_NODE_TYPES(XX)
+ #undef XX
+ case EValueType::Any:
+ validateAndAddValue(MakeUnversionedAnyValue(ConvertToYsonString(value).AsStringBuf(), id), column);
+ break;
+ case EValueType::Composite:
+ validateAndAddValue(MakeUnversionedCompositeValue(ConvertToYsonString(value).AsStringBuf(), id), column);
+ break;
+ default:
+ THROW_ERROR_EXCEPTION("Unsupported value type %Qlv",
+ type);
+ }
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error parsing value of column %Qv",
+ tableSchema.Columns()[id].Name())
+ << ex;
+ }
+ };
+
+ const auto& keyColumns = tableSchema.GetKeyColumns();
+
+ // Key
+ for (int id = 0; id < std::ssize(keyColumns); ++id) {
+ auto it = rowParts.find(nameTable->GetName(id));
+ if (it == rowParts.end()) {
+ validateAndAddValue(MakeUnversionedSentinelValue(EValueType::Null, id), tableSchema.Columns()[id]);
+ } else {
+ addValue(id, it->second);
+ }
+ }
+
+ // Fixed values
+ for (int id = std::ssize(keyColumns); id < std::ssize(tableSchema.Columns()); ++id) {
+ auto it = rowParts.find(nameTable->GetName(id));
+ if (it != rowParts.end()) {
+ addValue(id, it->second);
+ } else if (treatMissingAsNull) {
+ validateAndAddValue(MakeUnversionedSentinelValue(EValueType::Null, id), tableSchema.Columns()[id]);
+ } else if (validateValues && tableSchema.Columns()[id].Required()) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::SchemaViolation,
+ "Required column %v cannot have %Qlv value",
+ tableSchema.Columns()[id].GetDiagnosticNameString(),
+ EValueType::Null);
+ }
+ }
+
+ // Variable values
+ for (const auto& [name, value] : rowParts) {
+ int id = nameTable->GetIdOrRegisterName(name);
+ if (id >= std::ssize(tableSchema.Columns())) {
+ if (validateValues && tableSchema.GetStrict()) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::SchemaViolation,
+ "Unknown column %Qv in strict schema",
+ name);
+ }
+ YTreeNodeToUnversionedValue(&rowBuilder, value, id, EValueFlags::None);
+ }
+ }
+
+ return rowBuilder.FinishRow();
+}
+
+TUnversionedOwningRow YsonToSchemalessRow(const TString& valueYson)
+{
+ TUnversionedOwningRowBuilder builder;
+
+ auto values = ConvertTo<std::vector<INodePtr>>(TYsonString(valueYson, EYsonType::ListFragment));
+ for (const auto& value : values) {
+ int id = value->Attributes().Get<int>("id");
+ auto flags = EValueFlags::None;
+ if (value->Attributes().Get<bool>("aggregate", false)) {
+ flags |= EValueFlags::Aggregate;
+ }
+ YTreeNodeToUnversionedValue(&builder, value, id, flags);
+ }
+
+ return builder.FinishRow();
+}
+
+TVersionedRow YsonToVersionedRow(
+ const TRowBufferPtr& rowBuffer,
+ const TString& keyYson,
+ const TString& valueYson,
+ const std::vector<TTimestamp>& deleteTimestamps,
+ const std::vector<TTimestamp>& extraWriteTimestamps)
+{
+ TVersionedRowBuilder builder(rowBuffer);
+
+ auto keys = ConvertTo<std::vector<INodePtr>>(TYsonString(keyYson, EYsonType::ListFragment));
+
+ for (auto key : keys) {
+ int id = key->Attributes().Get<int>("id");
+ switch (key->GetType()) {
+ #define XX(type, cppType) \
+ case ENodeType::type: \
+ builder.AddKey(MakeUnversioned ## type ## Value(key->As ## type()->GetValue(), id)); \
+ break;
+ ITERATE_SCALAR_YTREE_NODE_TYPES(XX)
+ #undef XX
+ case ENodeType::Entity:
+ builder.AddKey(MakeUnversionedSentinelValue(EValueType::Null, id));
+ break;
+ default:
+ YT_ABORT();
+ break;
+ }
+ }
+
+ auto values = ConvertTo<std::vector<INodePtr>>(TYsonString(valueYson, EYsonType::ListFragment));
+ for (auto value : values) {
+ int id = value->Attributes().Get<int>("id");
+ auto timestamp = value->Attributes().Get<TTimestamp>("ts");
+ auto flags = EValueFlags::None;
+ if (value->Attributes().Get<bool>("aggregate", false)) {
+ flags |= EValueFlags::Aggregate;
+ }
+ switch (value->GetType()) {
+ #define XX(type, cppType) \
+ case ENodeType::type: \
+ builder.AddValue(MakeVersioned ## type ## Value(value->As ## type()->GetValue(), timestamp, id, flags)); \
+ break;
+ ITERATE_SCALAR_YTREE_NODE_TYPES(XX)
+ #undef XX
+ case ENodeType::Entity:
+ builder.AddValue(MakeVersionedSentinelValue(EValueType::Null, timestamp, id, flags));
+ break;
+ default:
+ builder.AddValue(MakeVersionedAnyValue(ConvertToYsonString(value).AsStringBuf(), timestamp, id, flags));
+ break;
+ }
+ }
+
+ for (auto timestamp : deleteTimestamps) {
+ builder.AddDeleteTimestamp(timestamp);
+ }
+
+ for (auto timestamp : extraWriteTimestamps) {
+ builder.AddWriteTimestamp(timestamp);
+ }
+
+ return builder.FinishRow();
+}
+
+TVersionedOwningRow YsonToVersionedRow(
+ const TString& keyYson,
+ const TString& valueYson,
+ const std::vector<TTimestamp>& deleteTimestamps,
+ const std::vector<TTimestamp>& extraWriteTimestamps)
+{
+ // NB: this implementation is extra slow, it is intended only for using in tests.
+ auto rowBuffer = New<TRowBuffer>();
+ auto row = YsonToVersionedRow(rowBuffer, keyYson, valueYson, deleteTimestamps, extraWriteTimestamps);
+ return TVersionedOwningRow(row);
+}
+
+TUnversionedOwningRow YsonToKey(const TString& yson)
+{
+ TUnversionedOwningRowBuilder keyBuilder;
+ auto keyParts = ConvertTo<std::vector<INodePtr>>(
+ TYsonString(yson, EYsonType::ListFragment));
+
+ for (int id = 0; id < std::ssize(keyParts); ++id) {
+ const auto& keyPart = keyParts[id];
+ switch (keyPart->GetType()) {
+ #define XX(type, cppType) \
+ case ENodeType::type: \
+ keyBuilder.AddValue( \
+ MakeUnversioned ## type ## Value(keyPart->As ## type()->GetValue(), \
+ id)); \
+ break;
+ ITERATE_SCALAR_YTREE_NODE_TYPES(XX)
+ #undef XX
+ case ENodeType::Entity:
+ keyBuilder.AddValue(MakeUnversionedSentinelValue(
+ keyPart->Attributes().Get<EValueType>("type", EValueType::Null),
+ id));
+ break;
+ default:
+ keyBuilder.AddValue(MakeUnversionedAnyValue(
+ ConvertToYsonString(keyPart).AsStringBuf(),
+ id));
+ break;
+ }
+ }
+
+ return keyBuilder.FinishRow();
+}
+
+TString KeyToYson(TUnversionedRow row)
+{
+ return ConvertToYsonString(row, EYsonFormat::Text).ToString();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ std::nullopt_t,
+ const TRowBufferPtr& /*rowBuffer*/,
+ int id,
+ EValueFlags flags)
+{
+ *unversionedValue = MakeUnversionedSentinelValue(EValueType::Null, id, flags);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ TGuid value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ std::array<char, MaxGuidStringSize> buffer;
+ auto* bufferEnd = WriteGuidToBuffer(buffer.data(), value);
+ TStringBuf bufferStr(buffer.begin(), bufferEnd);
+ *unversionedValue = value
+ ? rowBuffer->CaptureValue(MakeUnversionedStringValue(bufferStr, id, flags))
+ : MakeUnversionedSentinelValue(EValueType::Null, id, flags);
+}
+
+void FromUnversionedValue(TGuid* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ *value = TGuid();
+ return;
+ }
+ if (unversionedValue.Type != EValueType::String) {
+ THROW_ERROR_EXCEPTION("Cannot parse object id value from %Qlv",
+ unversionedValue.Type);
+ }
+ *value = TGuid::FromString(unversionedValue.AsStringBuf());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const TString& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ ToUnversionedValue(unversionedValue, static_cast<TStringBuf>(value), rowBuffer, id, flags);
+}
+
+void FromUnversionedValue(TString* value, TUnversionedValue unversionedValue)
+{
+ TStringBuf uncapturedValue;
+ FromUnversionedValue(&uncapturedValue, unversionedValue);
+ *value = TString(uncapturedValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ TStringBuf value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedStringValue(value, id, flags));
+}
+
+void FromUnversionedValue(TStringBuf* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ *value = TStringBuf{};
+ return;
+ }
+ if (unversionedValue.Type != EValueType::String) {
+ THROW_ERROR_EXCEPTION("Cannot parse string value from %Qlv",
+ unversionedValue.Type);
+ }
+ *value = unversionedValue.AsStringBuf();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const char* value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ ToUnversionedValue(unversionedValue, TStringBuf(value), rowBuffer, id, flags);
+}
+
+void FromUnversionedValue(const char** value, TUnversionedValue unversionedValue)
+{
+ *value = FromUnversionedValue<TStringBuf>(unversionedValue).data();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ bool value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedBooleanValue(value, id, flags));
+}
+
+void FromUnversionedValue(bool* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ *value = false;
+ return;
+ }
+ if (unversionedValue.Type != EValueType::Boolean) {
+ THROW_ERROR_EXCEPTION("Cannot parse \"boolean\" value from %Qlv",
+ unversionedValue.Type);
+ }
+ *value = unversionedValue.Data.Boolean;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const TYsonString& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ YT_ASSERT(value.GetType() == EYsonType::Node);
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedAnyValue(value.AsStringBuf(), id, flags));
+}
+
+void FromUnversionedValue(TYsonString* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION("Cannot parse YSON string from %Qlv",
+ unversionedValue.Type);
+ }
+ *value = TYsonString(unversionedValue.AsString());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const NYson::TYsonStringBuf& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ YT_ASSERT(value.GetType() == EYsonType::Node);
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedAnyValue(value.AsStringBuf(), id, flags));
+}
+
+void FromUnversionedValue(NYson::TYsonStringBuf* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION("Cannot parse YSON string from %Qlv",
+ unversionedValue.Type);
+ }
+ *value = TYsonStringBuf(unversionedValue.AsStringBuf());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define XX(cppType, codeType, humanReadableType) \
+ void ToUnversionedValue( \
+ TUnversionedValue* unversionedValue, \
+ cppType value, \
+ const TRowBufferPtr& /*rowBuffer*/, \
+ int id, \
+ EValueFlags flags) \
+ { \
+ *unversionedValue = MakeUnversioned ## codeType ## Value(value, id, flags); \
+ } \
+ \
+ void FromUnversionedValue(cppType* value, TUnversionedValue unversionedValue) \
+ { \
+ switch (unversionedValue.Type) { \
+ case EValueType::Int64: \
+ *value = CheckedIntegralCast<cppType>(unversionedValue.Data.Int64); \
+ break; \
+ case EValueType::Uint64: \
+ *value = CheckedIntegralCast<cppType>(unversionedValue.Data.Uint64); \
+ break; \
+ default: \
+ THROW_ERROR_EXCEPTION("Cannot parse \"" #humanReadableType "\" value from %Qlv", \
+ unversionedValue.Type); \
+ } \
+ }
+
+XX(i64, Int64, int64)
+XX(ui64, Uint64, uint64)
+XX(i32, Int64, int32)
+XX(ui32, Uint64, uint32)
+XX(i16, Int64, int32)
+XX(ui16, Uint64, uint16)
+XX(i8, Int64, int8)
+XX(ui8, Uint64, uint8)
+
+#undef XX
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ double value,
+ const TRowBufferPtr& /*rowBuffer*/,
+ int id,
+ EValueFlags flags)
+{
+ *unversionedValue = MakeUnversionedDoubleValue(value, id, flags);
+}
+
+void FromUnversionedValue(double* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type != EValueType::Double) {
+ THROW_ERROR_EXCEPTION("Cannot parse \"double\" value from %Qlv",
+ unversionedValue.Type);
+ }
+ *value = unversionedValue.Data.Double;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ TInstant value,
+ const TRowBufferPtr& /*rowBuffer*/,
+ int id,
+ EValueFlags flags)
+{
+ *unversionedValue = MakeUnversionedUint64Value(value.MicroSeconds(), id, flags);
+}
+
+void FromUnversionedValue(TInstant* value, TUnversionedValue unversionedValue)
+{
+ switch (unversionedValue.Type) {
+ case EValueType::Int64:
+ *value = TInstant::MicroSeconds(CheckedIntegralCast<ui64>(unversionedValue.Data.Int64));
+ break;
+ case EValueType::Uint64:
+ *value = TInstant::MicroSeconds(unversionedValue.Data.Uint64);
+ break;
+ case EValueType::String:
+ *value = TInstant::ParseIso8601(unversionedValue.AsStringBuf());
+ break;
+ default:
+ THROW_ERROR_EXCEPTION("Cannot parse instant from %Qlv",
+ unversionedValue.Type);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ TDuration value,
+ const TRowBufferPtr& /*rowBuffer*/,
+ int id,
+ EValueFlags flags)
+{
+ *unversionedValue = MakeUnversionedUint64Value(value.MicroSeconds(), id, flags);
+}
+
+void FromUnversionedValue(TDuration* value, TUnversionedValue unversionedValue)
+{
+ switch (unversionedValue.Type) {
+ case EValueType::Int64:
+ *value = TDuration::MicroSeconds(CheckedIntegralCast<ui64>(unversionedValue.Data.Int64));
+ break;
+ case EValueType::Uint64:
+ *value = TDuration::MicroSeconds(unversionedValue.Data.Uint64);
+ break;
+ default:
+ THROW_ERROR_EXCEPTION("Cannot parse duration from %Qlv",
+ unversionedValue.Type);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const IMapNodePtr& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedAnyValue(ConvertToYsonString(value).AsStringBuf(), id, flags));
+}
+
+void FromUnversionedValue(IMapNodePtr* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ *value = nullptr;
+ }
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION("Cannot parse YSON map from %Qlv",
+ unversionedValue.Type);
+ }
+ *value = ConvertTo<IMapNodePtr>(FromUnversionedValue<TYsonStringBuf>(unversionedValue));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const TIP6Address& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ ToUnversionedValue(unversionedValue, ToString(value), rowBuffer, id, flags);
+}
+
+void FromUnversionedValue(TIP6Address* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ *value = TIP6Address();
+ }
+ auto strValue = FromUnversionedValue<TString>(unversionedValue);
+ *value = TIP6Address::FromString(strValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const TError& value,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ auto errorYson = ConvertToYsonString(value);
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedAnyValue(errorYson.AsStringBuf(), id, flags));
+}
+
+void FromUnversionedValue(TError* value, TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ *value = {};
+ }
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION(
+ "Cannot parse error from value of type %Qlv",
+ unversionedValue.Type);
+ }
+ *value = ConvertTo<TError>(FromUnversionedValue<TYsonStringBuf>(unversionedValue));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ProtobufToUnversionedValueImpl(
+ TUnversionedValue* unversionedValue,
+ const Message& value,
+ const TProtobufMessageType* type,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ auto byteSize = value.ByteSizeLong();
+ auto* pool = rowBuffer->GetPool();
+ auto* wireBuffer = pool->AllocateUnaligned(byteSize);
+ YT_VERIFY(value.SerializePartialToArray(wireBuffer, byteSize));
+ ArrayInputStream inputStream(wireBuffer, byteSize);
+ TString ysonBytes;
+ TStringOutput outputStream(ysonBytes);
+ TYsonWriter ysonWriter(&outputStream);
+ ParseProtobuf(&ysonWriter, &inputStream, type);
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedAnyValue(ysonBytes, id, flags));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void UnversionedValueToProtobufImpl(
+ Message* value,
+ const TProtobufMessageType* type,
+ TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ value->Clear();
+ return;
+ }
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION("Cannot parse a protobuf message from %Qlv",
+ unversionedValue.Type);
+ }
+ TString wireBytes;
+ StringOutputStream outputStream(&wireBytes);
+ TProtobufWriterOptions options;
+ options.UnknownYsonFieldModeResolver = TProtobufWriterOptions::CreateConstantUnknownYsonFieldModeResolver(EUnknownYsonFieldsMode::Keep);
+ auto protobufWriter = CreateProtobufWriter(&outputStream, type, options);
+ ParseYsonStringBuffer(
+ unversionedValue.AsStringBuf(),
+ EYsonType::Node,
+ protobufWriter.get());
+ if (!value->ParseFromArray(wireBytes.data(), wireBytes.size())) {
+ THROW_ERROR_EXCEPTION("Error parsing %v from wire bytes",
+ value->GetTypeName());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ListToUnversionedValueImpl(
+ TUnversionedValue* unversionedValue,
+ const std::function<bool(TUnversionedValue*)> producer,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ TString ysonBytes;
+ TStringOutput outputStream(ysonBytes);
+ NYT::NYson::TYsonWriter writer(&outputStream);
+ writer.OnBeginList();
+
+ TUnversionedValue itemValue;
+ while (true) {
+ writer.OnListItem();
+ if (!producer(&itemValue)) {
+ break;
+ }
+ UnversionedValueToYson(itemValue, &writer);
+ }
+ writer.OnEndList();
+
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedAnyValue(ysonBytes, id, flags));
+}
+
+void UnversionedValueToListImpl(
+ std::function<google::protobuf::Message*()> appender,
+ const TProtobufMessageType* type,
+ TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ return;
+ }
+
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION("Cannot parse vector from %Qlv",
+ unversionedValue.Type);
+ }
+
+ class TConsumer
+ : public IYsonConsumer
+ {
+ public:
+ TConsumer(
+ std::function<google::protobuf::Message*()> appender,
+ const TProtobufMessageType* type)
+ : Appender_(std::move(appender))
+ , Type_(type)
+ , OutputStream_(&WireBytes_)
+ { }
+
+ void OnStringScalar(TStringBuf value) override
+ {
+ GetUnderlying()->OnStringScalar(value);
+ }
+
+ void OnInt64Scalar(i64 value) override
+ {
+ GetUnderlying()->OnInt64Scalar(value);
+ }
+
+ void OnUint64Scalar(ui64 value) override
+ {
+ GetUnderlying()->OnUint64Scalar(value);
+ }
+
+ void OnDoubleScalar(double value) override
+ {
+ GetUnderlying()->OnDoubleScalar(value);
+ }
+
+ void OnBooleanScalar(bool value) override
+ {
+ GetUnderlying()->OnBooleanScalar(value);
+ }
+
+ void OnEntity() override
+ {
+ GetUnderlying()->OnEntity();
+ }
+
+ void OnBeginList() override
+ {
+ if (Depth_ > 0) {
+ GetUnderlying()->OnBeginList();
+ }
+ ++Depth_;
+ }
+
+ void OnListItem() override
+ {
+ if (Depth_ == 1) {
+ NextElement();
+ } else {
+ GetUnderlying()->OnListItem();
+ }
+ }
+
+ void OnEndList() override
+ {
+ --Depth_;
+ if (Depth_ == 0) {
+ FlushElement();
+ } else {
+ GetUnderlying()->OnEndList();
+ }
+ }
+
+ void OnBeginMap() override
+ {
+ ++Depth_;
+ GetUnderlying()->OnBeginMap();
+ }
+
+ void OnKeyedItem(TStringBuf key) override
+ {
+ GetUnderlying()->OnKeyedItem(key);
+ }
+
+ void OnEndMap() override
+ {
+ --Depth_;
+ GetUnderlying()->OnEndMap();
+ }
+
+ void OnBeginAttributes() override
+ {
+ GetUnderlying()->OnBeginAttributes();
+ }
+
+ void OnEndAttributes() override
+ {
+ GetUnderlying()->OnEndAttributes();
+ }
+
+ void OnRaw(TStringBuf yson, EYsonType type) override
+ {
+ GetUnderlying()->OnRaw(yson, type);
+ }
+
+ private:
+ const std::function<google::protobuf::Message*()> Appender_;
+ const TProtobufMessageType* const Type_;
+
+ std::unique_ptr<IYsonConsumer> Underlying_;
+ int Depth_ = 0;
+
+ TString WireBytes_;
+ StringOutputStream OutputStream_;
+
+
+ IYsonConsumer* GetUnderlying()
+ {
+ if (!Underlying_) {
+ THROW_ERROR_EXCEPTION("YSON value must be a list without attributes");
+ }
+ return Underlying_.get();
+ }
+
+ void NextElement()
+ {
+ FlushElement();
+ WireBytes_.clear();
+ Underlying_ = CreateProtobufWriter(&OutputStream_, Type_);
+ }
+
+ void FlushElement()
+ {
+ if (!Underlying_) {
+ return;
+ }
+ auto* value = Appender_();
+ if (!value->ParseFromArray(WireBytes_.data(), WireBytes_.size())) {
+ THROW_ERROR_EXCEPTION("Error parsing %v from wire bytes",
+ value->GetTypeName());
+ }
+ Underlying_.reset();
+ }
+ } consumer(std::move(appender), type);
+
+ ParseYsonStringBuffer(
+ unversionedValue.AsStringBuf(),
+ EYsonType::Node,
+ &consumer);
+}
+
+void UnversionedValueToListImpl(
+ std::function<void(TUnversionedValue)> appender,
+ TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ return;
+ }
+
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION("Cannot parse a vector from %Qlv",
+ unversionedValue.Type);
+ }
+
+ class TConsumer
+ : public TYsonConsumerBase
+ {
+ public:
+ explicit TConsumer(std::function<void(TUnversionedValue)> appender)
+ : Appender_(std::move(appender))
+ { }
+
+ void OnStringScalar(TStringBuf value) override
+ {
+ EnsureInList();
+ Appender_(MakeUnversionedStringValue(value));
+ }
+
+ void OnInt64Scalar(i64 value) override
+ {
+ EnsureInList();
+ Appender_(MakeUnversionedInt64Value(value));
+ }
+
+ void OnUint64Scalar(ui64 value) override
+ {
+ EnsureInList();
+ Appender_(MakeUnversionedUint64Value(value));
+ }
+
+ void OnDoubleScalar(double value) override
+ {
+ EnsureInList();
+ Appender_(MakeUnversionedDoubleValue(value));
+ }
+
+ void OnBooleanScalar(bool value) override
+ {
+ EnsureInList();
+ Appender_(MakeUnversionedBooleanValue(value));
+ }
+
+ void OnEntity() override
+ {
+ THROW_ERROR_EXCEPTION("YSON entities are not supported");
+ }
+
+ void OnBeginList() override
+ {
+ EnsureNotInList();
+ InList_ = true;
+ }
+
+ void OnListItem() override
+ { }
+
+ void OnEndList() override
+ { }
+
+ void OnBeginMap() override
+ {
+ THROW_ERROR_EXCEPTION("YSON maps are not supported");
+ }
+
+ void OnKeyedItem(TStringBuf /*key*/) override
+ {
+ YT_ABORT();
+ }
+
+ void OnEndMap() override
+ {
+ YT_ABORT();
+ }
+
+ void OnBeginAttributes() override
+ {
+ THROW_ERROR_EXCEPTION("YSON attributes are not supported");
+ }
+
+ void OnEndAttributes() override
+ {
+ YT_ABORT();
+ }
+
+ private:
+ const std::function<void(TUnversionedValue)> Appender_;
+
+ bool InList_ = false;
+
+ void EnsureInList()
+ {
+ if (!InList_) {
+ THROW_ERROR_EXCEPTION("YSON list expected");
+ }
+ }
+
+ void EnsureNotInList()
+ {
+ if (InList_) {
+ THROW_ERROR_EXCEPTION("YSON list is unexpected");
+ }
+ }
+ } consumer(std::move(appender));
+
+ ParseYsonStringBuffer(
+ unversionedValue.AsStringBuf(),
+ EYsonType::Node,
+ &consumer);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void MapToUnversionedValueImpl(
+ TUnversionedValue* unversionedValue,
+ const std::function<bool(TString*, TUnversionedValue*)> producer,
+ const TRowBufferPtr& rowBuffer,
+ int id,
+ EValueFlags flags)
+{
+ TString ysonBytes;
+ TStringOutput outputStream(ysonBytes);
+ NYT::NYson::TYsonWriter writer(&outputStream);
+ writer.OnBeginMap();
+
+ TString itemKey;
+ TUnversionedValue itemValue;
+ while (true) {
+ if (!producer(&itemKey, &itemValue)) {
+ break;
+ }
+ writer.OnKeyedItem(itemKey);
+ UnversionedValueToYson(itemValue, &writer);
+ }
+ writer.OnEndMap();
+
+ *unversionedValue = rowBuffer->CaptureValue(MakeUnversionedAnyValue(ysonBytes, id, flags));
+}
+
+void UnversionedValueToMapImpl(
+ std::function<google::protobuf::Message*(TString)> appender,
+ const TProtobufMessageType* type,
+ TUnversionedValue unversionedValue)
+{
+ if (unversionedValue.Type == EValueType::Null) {
+ return;
+ }
+
+ if (unversionedValue.Type != EValueType::Any) {
+ THROW_ERROR_EXCEPTION("Cannot parse map from %Qlv",
+ unversionedValue.Type);
+ }
+
+ class TConsumer
+ : public IYsonConsumer
+ {
+ public:
+ TConsumer(
+ std::function<google::protobuf::Message*(TString)> appender,
+ const TProtobufMessageType* type)
+ : Appender_(std::move(appender))
+ , Type_(type)
+ , OutputStream_(&WireBytes_)
+ { }
+
+ void OnStringScalar(TStringBuf value) override
+ {
+ GetUnderlying()->OnStringScalar(value);
+ }
+
+ void OnInt64Scalar(i64 value) override
+ {
+ GetUnderlying()->OnInt64Scalar(value);
+ }
+
+ void OnUint64Scalar(ui64 value) override
+ {
+ GetUnderlying()->OnUint64Scalar(value);
+ }
+
+ void OnDoubleScalar(double value) override
+ {
+ GetUnderlying()->OnDoubleScalar(value);
+ }
+
+ void OnBooleanScalar(bool value) override
+ {
+ GetUnderlying()->OnBooleanScalar(value);
+ }
+
+ void OnEntity() override
+ {
+ GetUnderlying()->OnEntity();
+ }
+
+ void OnBeginList() override
+ {
+ ++Depth_;
+ GetUnderlying()->OnBeginList();
+ }
+
+ void OnListItem() override
+ {
+ GetUnderlying()->OnListItem();
+ }
+
+ void OnEndList() override
+ {
+ --Depth_;
+ GetUnderlying()->OnEndList();
+ }
+
+ void OnBeginMap() override
+ {
+ if (Depth_ > 0) {
+ GetUnderlying()->OnBeginMap();
+ }
+ ++Depth_;
+ }
+
+ void OnKeyedItem(TStringBuf key) override
+ {
+ if (Depth_ == 1) {
+ NextElement(key);
+ } else {
+ GetUnderlying()->OnKeyedItem(key);
+ }
+ }
+
+ void OnEndMap() override
+ {
+ --Depth_;
+ if (Depth_ == 0) {
+ FlushElement();
+ } else {
+ GetUnderlying()->OnEndMap();
+ }
+ }
+
+ void OnBeginAttributes() override
+ {
+ GetUnderlying()->OnBeginAttributes();
+ }
+
+ void OnEndAttributes() override
+ {
+ GetUnderlying()->OnEndAttributes();
+ }
+
+ void OnRaw(TStringBuf yson, EYsonType type) override
+ {
+ GetUnderlying()->OnRaw(yson, type);
+ }
+
+ private:
+ const std::function<google::protobuf::Message*(TString)> Appender_;
+ const TProtobufMessageType* const Type_;
+
+ std::optional<TString> Key_;
+ std::unique_ptr<IYsonConsumer> Underlying_;
+ int Depth_ = 0;
+
+ TString WireBytes_;
+ StringOutputStream OutputStream_;
+
+
+ IYsonConsumer* GetUnderlying()
+ {
+ if (!Underlying_) {
+ THROW_ERROR_EXCEPTION("YSON value must be a list without attributes");
+ }
+ return Underlying_.get();
+ }
+
+ void NextElement(TStringBuf key)
+ {
+ FlushElement();
+ WireBytes_.clear();
+ Key_ = TString(key);
+ Underlying_ = CreateProtobufWriter(&OutputStream_, Type_);
+ }
+
+ void FlushElement()
+ {
+ if (!Underlying_) {
+ return;
+ }
+ auto* value = Appender_(*Key_);
+ if (!value->ParseFromArray(WireBytes_.data(), WireBytes_.size())) {
+ THROW_ERROR_EXCEPTION("Error parsing %v from wire bytes",
+ value->GetTypeName());
+ }
+ Underlying_.reset();
+ Key_.reset();
+ }
+ } consumer(std::move(appender), type);
+
+ ParseYsonStringBuffer(
+ unversionedValue.AsStringBuf(),
+ EYsonType::Node,
+ &consumer);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void UnversionedValueToYson(TUnversionedValue unversionedValue, TCheckedInDebugYsonTokenWriter* tokenWriter)
+{
+ switch (unversionedValue.Type) {
+ case EValueType::Int64:
+ tokenWriter->WriteBinaryInt64(unversionedValue.Data.Int64);
+ return;
+ case EValueType::Uint64:
+ tokenWriter->WriteBinaryUint64(unversionedValue.Data.Uint64);
+ return;
+ case EValueType::Double:
+ tokenWriter->WriteBinaryDouble(unversionedValue.Data.Double);
+ return;
+ case EValueType::String:
+ tokenWriter->WriteBinaryString(unversionedValue.AsStringBuf());
+ return;
+ case EValueType::Any:
+ case EValueType::Composite:
+ tokenWriter->WriteRawNodeUnchecked(unversionedValue.AsStringBuf());
+ return;
+ case EValueType::Boolean:
+ tokenWriter->WriteBinaryBoolean(unversionedValue.Data.Boolean);
+ return;
+ case EValueType::Null:
+ tokenWriter->WriteEntity();
+ return;
+ case EValueType::TheBottom:
+ case EValueType::Min:
+ case EValueType::Max:
+ YT_ABORT();
+ }
+ ThrowUnexpectedValueType(unversionedValue.Type);
+}
+
+void UnversionedValueToYson(TUnversionedValue unversionedValue, IYsonConsumer* consumer)
+{
+ switch (unversionedValue.Type) {
+ case EValueType::Int64:
+ consumer->OnInt64Scalar(unversionedValue.Data.Int64);
+ return;
+ case EValueType::Uint64:
+ consumer->OnUint64Scalar(unversionedValue.Data.Uint64);
+ return;
+ case EValueType::Double:
+ consumer->OnDoubleScalar(unversionedValue.Data.Double);
+ return;
+ case EValueType::String:
+ consumer->OnStringScalar(unversionedValue.AsStringBuf());
+ return;
+ case EValueType::Any:
+ case EValueType::Composite:
+ consumer->OnRaw(unversionedValue.AsStringBuf(), EYsonType::Node);
+ return;
+ case EValueType::Boolean:
+ consumer->OnBooleanScalar(unversionedValue.Data.Boolean);
+ return;
+ case EValueType::Null:
+ consumer->OnEntity();
+ return;
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ YT_ABORT();
+ }
+ ThrowUnexpectedValueType(unversionedValue.Type);
+}
+
+TYsonString UnversionedValueToYson(TUnversionedValue unversionedValue, bool enableRaw)
+{
+ TString data;
+ data.reserve(GetYsonSize(unversionedValue));
+ TStringOutput output(data);
+ TYsonWriter writer(&output, EYsonFormat::Binary, EYsonType::Node, /* enableRaw */ enableRaw);
+ UnversionedValueToYson(unversionedValue, &writer);
+ return TYsonString(std::move(data));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedValue EncodeUnversionedAnyValue(
+ TUnversionedValue value,
+ TChunkedMemoryPool* memoryPool)
+{
+ YT_ASSERT(None(value.Flags));
+ switch (value.Type) {
+ case EValueType::Any:
+ case EValueType::Composite:
+ return value;
+
+ case EValueType::Null: {
+ auto size = 1;
+ char* begin = memoryPool->AllocateUnaligned(size);
+ char* current = begin;
+ *current++ = '#';
+ return MakeUnversionedAnyValue(TStringBuf(begin, current), value.Id, value.Flags);
+ }
+
+ case EValueType::String: {
+ auto size = 1 + MaxVarInt32Size + value.Length;
+ char* begin = memoryPool->AllocateUnaligned(size);
+ char* current = begin;
+ *current++ = NYson::NDetail::StringMarker;
+ current += WriteVarInt32(current, value.Length);
+ WriteRef(current, TRef(value.Data.String, value.Length));
+ return MakeUnversionedAnyValue(TStringBuf(begin, current), value.Id, value.Flags);
+ }
+
+ case EValueType::Int64: {
+ auto size = 1 + MaxVarInt64Size;
+ char* begin = memoryPool->AllocateUnaligned(size);
+ char* current = begin;
+ *current++ = NYson::NDetail::Int64Marker;
+ current += WriteVarInt64(current, value.Data.Int64);
+ return MakeUnversionedAnyValue(TStringBuf(begin, current), value.Id, value.Flags);
+ }
+
+ case EValueType::Uint64: {
+ auto size = 1 + MaxVarUint64Size;
+ char* begin = memoryPool->AllocateUnaligned(size);
+ char* current = begin;
+ *current++ = NYson::NDetail::Uint64Marker;
+ current += WriteVarUint64(current, value.Data.Uint64);
+ return MakeUnversionedAnyValue(TStringBuf(begin, current), value.Id, value.Flags);
+ }
+
+ case EValueType::Double: {
+ auto size = 1 + sizeof(double);
+ char* begin = memoryPool->AllocateUnaligned(size);
+ char* current = begin;
+ *current++ = NYson::NDetail::DoubleMarker;
+ WritePod(current, value.Data.Double);
+ return MakeUnversionedAnyValue(TStringBuf(begin, current), value.Id, value.Flags);
+ }
+
+ case EValueType::Boolean: {
+ auto size = 1;
+ char* begin = memoryPool->AllocateUnaligned(size);
+ char* current = begin;
+ *current++ = value.Data.Boolean ? NYson::NDetail::TrueMarker : NYson::NDetail::FalseMarker;
+ return MakeUnversionedAnyValue(TStringBuf(begin, current), value.Id, value.Flags);
+ }
+
+ default:
+ YT_ABORT();
+ }
+}
+
+TUnversionedValue TryDecodeUnversionedAnyValue(
+ TUnversionedValue value,
+ const TRowBufferPtr& rowBuffer)
+{
+ YT_VERIFY(value.Type == EValueType::Any);
+ YT_VERIFY(None(value.Flags & EValueFlags::Hunk));
+
+ TStatelessLexer lexer; // this will not allocate on happy path
+ TToken token;
+ lexer.ParseToken(value.AsStringBuf(), &token);
+ YT_VERIFY(!token.IsEmpty());
+
+ switch (token.GetType()) {
+ case ETokenType::Int64:
+ return MakeUnversionedInt64Value(token.GetInt64Value(), value.Id, value.Flags);
+
+ case ETokenType::Uint64:
+ return MakeUnversionedUint64Value(token.GetUint64Value(), value.Id, value.Flags);
+
+ case ETokenType::String: {
+ auto decodedValue = MakeUnversionedStringValue(token.GetStringValue(), value.Id, value.Flags);
+ if (!token.IsBinaryString()) {
+ if (!rowBuffer) {
+ THROW_ERROR_EXCEPTION("Cannot decode non-binary YSON string in \"any\"-typed column %v",
+ value.Id);
+ }
+ decodedValue = rowBuffer->CaptureValue(decodedValue);
+ }
+ return decodedValue;
+ }
+
+ case ETokenType::Double:
+ return MakeUnversionedDoubleValue(token.GetDoubleValue(), value.Id, value.Flags);
+
+ case ETokenType::Boolean:
+ return MakeUnversionedBooleanValue(token.GetBooleanValue(), value.Id, value.Flags);
+
+ case ETokenType::Hash:
+ return MakeUnversionedSentinelValue(EValueType::Null, value.Id, value.Flags);
+
+ default:
+ return value;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TDefaultUnversionedRowsBuilderTag
+{ };
+
+TUnversionedRowsBuilder::TUnversionedRowsBuilder()
+ : TUnversionedRowsBuilder(New<TRowBuffer>(TDefaultUnversionedRowsBuilderTag()))
+{ }
+
+TUnversionedRowsBuilder::TUnversionedRowsBuilder(TRowBufferPtr rowBuffer)
+ : RowBuffer_(std::move(rowBuffer))
+{ }
+
+void TUnversionedRowsBuilder::ReserveRows(int rowCount)
+{
+ Rows_.reserve(rowCount);
+}
+
+void TUnversionedRowsBuilder::AddRow(TUnversionedRow row)
+{
+ Rows_.push_back(RowBuffer_->CaptureRow(row));
+}
+
+void TUnversionedRowsBuilder::AddRow(TMutableUnversionedRow row)
+{
+ AddRow(TUnversionedRow(row));
+}
+
+void TUnversionedRowsBuilder::AddProtoRow(const TString& protoRow)
+{
+ auto& row = Rows_.emplace_back();
+ FromProto(&row, protoRow, RowBuffer_);
+}
+
+TSharedRange<TUnversionedRow> TUnversionedRowsBuilder::Build()
+{
+ return MakeSharedRange(std::move(Rows_), std::move(RowBuffer_));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+REGISTER_INTERMEDIATE_PROTO_INTEROP_BYTES_FIELD_REPRESENTATION(
+ NProto::TDataBlockMeta,
+ /*last_key*/ 9,
+ TUnversionedOwningRow)
+
+REGISTER_INTERMEDIATE_PROTO_INTEROP_BYTES_FIELD_REPRESENTATION(
+ NProto::TBoundaryKeysExt,
+ /*min*/ 1,
+ TUnversionedOwningRow)
+REGISTER_INTERMEDIATE_PROTO_INTEROP_BYTES_FIELD_REPRESENTATION(
+ NProto::TBoundaryKeysExt,
+ /*max*/ 2,
+ TUnversionedOwningRow)
+
+REGISTER_INTERMEDIATE_PROTO_INTEROP_BYTES_FIELD_REPRESENTATION(
+ NProto::TSamplesExt,
+ /*entries*/ 1,
+ TUnversionedOwningRow)
+
+REGISTER_INTERMEDIATE_PROTO_INTEROP_BYTES_FIELD_REPRESENTATION(
+ NProto::THeavyColumnStatisticsExt,
+ /*column_data_weights*/ 5,
+ TUnversionedOwningRow)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/helpers.h b/yt/yt/client/table_client/helpers.h
new file mode 100644
index 0000000000..872543527e
--- /dev/null
+++ b/yt/yt/client/table_client/helpers.h
@@ -0,0 +1,349 @@
+#pragma once
+
+#include "versioned_row.h"
+#include "unversioned_row.h"
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/net/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsValidTableChunkFormat(NChunkClient::EChunkFormat chunkFormat);
+bool IsTableChunkFormatVersioned(NChunkClient::EChunkFormat chunkFormat);
+
+void ValidateTableChunkFormat(NChunkClient::EChunkFormat chunkFormat);
+void ValidateTableChunkFormatAndOptimizeFor(
+ NChunkClient::EChunkFormat chunkFormat,
+ EOptimizeFor optimizeFor);
+void ValidateTableChunkFormatVersioned(
+ NChunkClient::EChunkFormat chunkFormat,
+ bool versioned);
+
+EOptimizeFor OptimizeForFromFormat(NChunkClient::EChunkFormat chunkFormat);
+NChunkClient::EChunkFormat DefaultFormatFromOptimizeFor(
+ EOptimizeFor optimizeFor,
+ bool versioned);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Mostly used in unittests and for debugging purposes.
+// Quite inefficient.
+TUnversionedOwningRow YsonToSchemafulRow(
+ const TString& yson,
+ const TTableSchema& tableSchema,
+ bool treatMissingAsNull,
+ NYson::EYsonType ysonType = NYson::EYsonType::MapFragment,
+ bool validateValues = false);
+TUnversionedOwningRow YsonToSchemalessRow(
+ const TString& yson);
+TVersionedRow YsonToVersionedRow(
+ const TRowBufferPtr& rowBuffer,
+ const TString& keyYson,
+ const TString& valueYson,
+ const std::vector<TTimestamp>& deleteTimestamps = {},
+ const std::vector<TTimestamp>& extraWriteTimestamps = {});
+TVersionedOwningRow YsonToVersionedRow(
+ const TString& keyYson,
+ const TString& valueYson,
+ const std::vector<TTimestamp>& deleteTimestamps = {},
+ const std::vector<TTimestamp>& extraWriteTimestamps = {});
+TUnversionedOwningRow YsonToKey(const TString& yson);
+TString KeyToYson(TUnversionedRow row);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, class = void>
+struct TUnversionedValueConversionTraits
+{
+ // These are conservative defaults.
+ static constexpr bool Scalar = false;
+ static constexpr bool Inline = false;
+};
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, std::nullopt_t, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, TGuid value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(TGuid* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, const TString& value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(TString* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, TStringBuf value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(TStringBuf* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, const char* value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(const char** value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, bool value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(bool* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, const NYson::TYsonString& value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(NYson::TYsonString* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, const NYson::TYsonStringBuf& value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(NYson::TYsonStringBuf* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, i64 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(i64* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, ui64 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(ui64* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, i32 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(i32* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, ui32 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(ui32* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, i16 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(i16* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, ui16 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(ui16* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, i8 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(i8* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, ui8 value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(ui8* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, double value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(double* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, TInstant value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(TInstant* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, TDuration value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(TDuration* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, const NYTree::IMapNodePtr& value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(NYTree::IMapNodePtr* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, const NNet::TIP6Address& value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(NNet::TIP6Address* value, TUnversionedValue unversionedValue);
+
+void ToUnversionedValue(TUnversionedValue* unversionedValue, const TError& value, const TRowBufferPtr& rowBuffer, int id = 0, EValueFlags flags = EValueFlags::None);
+void FromUnversionedValue(TError* value, TUnversionedValue unversionedValue);
+
+template <class T>
+ requires TEnumTraits<T>::IsEnum
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ T value,
+ const TRowBufferPtr& rowBuffer,
+ int id = 0,
+ EValueFlags flags = EValueFlags::None);
+template <class T>
+ requires TEnumTraits<T>::IsEnum
+void FromUnversionedValue(
+ T* value,
+ TUnversionedValue unversionedValue);
+
+template <class T>
+TUnversionedValue ToUnversionedValue(
+ T&& value,
+ const TRowBufferPtr& rowBuffer,
+ int id = 0,
+ EValueFlags flags = EValueFlags::None);
+template <class T>
+T FromUnversionedValue(TUnversionedValue unversionedValue);
+
+template <class T>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const T& value,
+ const TRowBufferPtr& rowBuffer,
+ int id = 0,
+ EValueFlags flags = EValueFlags::None)
+ requires std::is_convertible<T*, ::google::protobuf::Message*>::value;
+template <class T>
+void FromUnversionedValue(
+ T* value,
+ TUnversionedValue unversionedValue)
+ requires std::is_convertible<T*, ::google::protobuf::Message*>::value;
+
+template <class T>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const std::optional<T>& value,
+ const TRowBufferPtr& rowBuffer,
+ int id = 0,
+ EValueFlags flags = EValueFlags::None);
+template <class T>
+void FromUnversionedValue(
+ std::optional<T>* value,
+ TUnversionedValue unversionedValue);
+
+template <class T>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const std::vector<T>& values,
+ const TRowBufferPtr& rowBuffer,
+ int id = 0,
+ EValueFlags flags = EValueFlags::None);
+template <class T>
+void FromUnversionedValue(
+ std::vector<T>* values,
+ TUnversionedValue unversionedValue)
+ requires std::is_convertible<T*, ::google::protobuf::Message*>::value;
+template <class T>
+void FromUnversionedValue(
+ std::vector<T>* values,
+ TUnversionedValue unversionedValue)
+ requires TUnversionedValueConversionTraits<T>::Scalar;
+
+template <class TKey, class TValue>
+void ToUnversionedValue(
+ TUnversionedValue* unversionedValue,
+ const THashMap<TKey, TValue>& map,
+ const TRowBufferPtr& rowBuffer,
+ int id = 0,
+ EValueFlags flags = EValueFlags::None);
+template <class TKey, class TValue>
+void FromUnversionedValue(
+ THashMap<TKey, TValue>* map,
+ TUnversionedValue unversionedValue)
+ requires std::is_convertible<TValue*, ::google::protobuf::Message*>::value;
+
+//! Values get sequential ids 0..N-1 (unless wrapped into TValueWithId).
+template <class... Ts>
+auto ToUnversionedValues(
+ const TRowBufferPtr& rowBuffer,
+ Ts&&... values)
+-> std::array<TUnversionedValue, sizeof...(Ts)>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void ToVersionedValue(
+ TVersionedValue* versionedValue,
+ T&& value,
+ const TRowBufferPtr& rowBuffer,
+ NTransactionClient::TTimestamp timestamp,
+ int id,
+ EValueFlags flags = EValueFlags::None);
+template <class T>
+TVersionedValue ToVersionedValue(
+ T&& value,
+ const TRowBufferPtr& rowBuffer,
+ NTransactionClient::TTimestamp timestamp,
+ int id,
+ EValueFlags flags = EValueFlags::None);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Unpacks individual values in #row to respective #values.
+//! The number of values in #row must be greater than or equal to the number of #values.
+template <class... Ts>
+void FromUnversionedRow(
+ TUnversionedRow row,
+ Ts*... values);
+
+//! Same as above but returns a tuple instead of placing values into the arguments.
+template <class... Ts>
+std::tuple<Ts...> FromUnversionedRow(TUnversionedRow row);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Enables annotating values with id and flags.
+template <class T>
+struct TAnnotatedValue
+{
+ //! For an implicitly-generated deduction guide.
+ TAnnotatedValue(
+ const T& value,
+ int id = 0,
+ EValueFlags flags = EValueFlags::None)
+ : Value(value)
+ , Id(id)
+ , Flags(flags)
+ { }
+
+ const T& Value;
+ int Id;
+ EValueFlags Flags;
+};
+
+//! Constructs an owning row from arbitrarily-typed values.
+//! Values get sequential ids 0..N-1 (unless wrapped into TValueWithId).
+template <class... Ts>
+TUnversionedOwningRow MakeUnversionedOwningRow(Ts&&... values);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnversionedRowsBuilder
+ : private TNonCopyable
+{
+public:
+ TUnversionedRowsBuilder();
+ explicit TUnversionedRowsBuilder(TRowBufferPtr rowBuffer);
+
+ void ReserveRows(int rowCount);
+
+ void AddRow(TUnversionedRow row);
+ void AddRow(TMutableUnversionedRow row);
+ void AddProtoRow(const TString& protoRow);
+
+ //! Values get sequential ids 0..N-1 (unless wrapped into TValueWithId).
+ template <class... Ts>
+ void AddRow(Ts&&... values);
+
+ TSharedRange<TUnversionedRow> Build();
+
+private:
+ const TRowBufferPtr RowBuffer_;
+
+ std::vector<TUnversionedRow> Rows_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A helper encapsulating Read/GetReadyEvent calls for a typical row batch reader.
+/*!
+ * Invokes |Read| method and checks the result for emptiness.
+ * If empty, waits for the ready event and loops.
+ * Returns either a non-empty batch or null (indicating end-of-stream).
+ *
+ * All additional parameters are forwarded to |Read| call.
+ */
+template <class TReader, class... TArgs>
+auto ReadRowBatch(const TIntrusivePtr<TReader>& reader, TArgs&&... args);
+
+//! A helper encapsulating Write/GetReadyEvent calls for a typical row batch writer.
+/*!
+ * Invokes |Write| method and checks the result.
+ * If false, waits for the ready event.
+ *
+ * All additional parameters are forwarded to |Write| call.
+ */
+template <class TWriter, class... TArgs>
+auto WriteRowBatch(const TIntrusivePtr<TWriter>& writer, TArgs&&... args);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void UnversionedValueToYson(TUnversionedValue unversionedValue, NYson::IYsonConsumer* consumer);
+void UnversionedValueToYson(TUnversionedValue unversionedValue, NYson::TCheckedInDebugYsonTokenWriter* tokenWriter);
+NYson::TYsonString UnversionedValueToYson(TUnversionedValue unversionedValue, bool enableRaw = false);
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedValue EncodeUnversionedAnyValue(
+ TUnversionedValue value,
+ TChunkedMemoryPool* memoryPool);
+
+TUnversionedValue TryDecodeUnversionedAnyValue(
+ TUnversionedValue value,
+ const TRowBufferPtr& rowBuffer = nullptr);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+#define HELPERS_INL_H_
+#include "helpers-inl.h"
+#undef HELPERS_INL_H_
diff --git a/yt/yt/client/table_client/key.cpp b/yt/yt/client/table_client/key.cpp
new file mode 100644
index 0000000000..6710dd1e81
--- /dev/null
+++ b/yt/yt/client/table_client/key.cpp
@@ -0,0 +1,190 @@
+#include "key.h"
+
+#include "serialize.h"
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NTableClient {
+
+using namespace NLogging;
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Used only for YT_LOG_FATAL below.
+static const TLogger Logger("TableClientKey");
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKey::TKey(TUnversionedValueRange range)
+ : Elements_(range)
+{ }
+
+TKey TKey::FromRow(TUnversionedRow row, std::optional<int> length)
+{
+ if (!row) {
+ return TKey();
+ }
+
+ int keyLength = length.value_or(row.GetCount());
+ YT_VERIFY(keyLength <= static_cast<int>(row.GetCount()));
+
+ ValidateValueTypes(row.FirstNElements(keyLength));
+
+ return TKey(row.FirstNElements(keyLength));
+}
+
+TKey TKey::FromRowUnchecked(TUnversionedRow row, std::optional<int> length)
+{
+ if (!row) {
+ return TKey();
+ }
+
+ int keyLength = length.value_or(row.GetCount());
+ YT_VERIFY(keyLength <= static_cast<int>(row.GetCount()));
+
+#ifndef NDEBUG
+ try {
+ ValidateValueTypes(row.FirstNElements(keyLength));
+ } catch (const std::exception& ex) {
+ YT_LOG_FATAL(ex, "Unexpected exception while building key from row");
+ }
+#endif
+
+ return TKey(row.FirstNElements(keyLength));
+}
+
+TKey::operator bool() const
+{
+ return static_cast<bool>(Elements_);
+}
+
+TUnversionedOwningRow TKey::AsOwningRow() const
+{
+ return *this ? TUnversionedOwningRow(Elements()) : TUnversionedOwningRow();
+}
+
+const TUnversionedValue& TKey::operator[](int index) const
+{
+ return Elements_[index];
+}
+
+int TKey::GetLength() const
+{
+ return std::ssize(Elements_);
+}
+
+const TUnversionedValue* TKey::Begin() const
+{
+ return Elements_.begin();
+}
+
+const TUnversionedValue* TKey::End() const
+{
+ return Elements_.end();
+}
+
+TUnversionedValueRange TKey::Elements() const
+{
+ return Elements_;
+}
+
+void TKey::ValidateValueTypes(TUnversionedValueRange range)
+{
+ for (const auto& value : range) {
+ ValidateDataValueType(value.Type);
+ }
+}
+
+void TKey::Persist(const TPersistenceContext& context)
+{
+ if (context.IsSave()) {
+ auto representation = *this
+ ? SerializeToString(Elements())
+ : SerializedNullRow;
+ NYT::Save(context.SaveContext(), representation);
+ } else {
+ TUnversionedRow row;
+ Load(context.LoadContext(), row);
+ if (row) {
+ // Row lifetime is ensured by row buffer in load context.
+ Elements_ = row.Elements();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TKey& lhs, const TKey& rhs)
+{
+ if (!lhs || !rhs) {
+ return static_cast<bool>(lhs) == static_cast<bool>(rhs);
+ }
+
+ return CompareValueRanges(lhs.Elements(), rhs.Elements()) == 0;
+}
+
+bool operator!=(const TKey& lhs, const TKey& rhs)
+{
+ return !(lhs == rhs);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TKey& key, TStringBuf /*format*/)
+{
+ if (key) {
+ builder->AppendFormat("[%v]", JoinToString(key.Begin(), key.End()));
+ } else {
+ builder->AppendString("#");
+ }
+}
+
+TString ToString(const TKey& key)
+{
+ return ToStringViaBuilder(key);
+}
+
+void Serialize(const TKey& key, IYsonConsumer* consumer)
+{
+ if (key) {
+ BuildYsonFluently(consumer)
+ .DoListFor(MakeRange(key.Begin(), key.End()), [&](TFluentList fluent, const TUnversionedValue& value) {
+ fluent
+ .Item()
+ .Value(value);
+ });
+ } else {
+ BuildYsonFluently(consumer)
+ .Entity();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedOwningRow LegacyKeyToKeyFriendlyOwningRow(TUnversionedRow row, int keyLength)
+{
+ if (!row) {
+ return TUnversionedOwningRow();
+ }
+
+ TUnversionedOwningRowBuilder builder;
+ for (int index = 0; index < keyLength; ++index) {
+ TUnversionedValue value;
+ if (index < static_cast<int>(row.GetCount())) {
+ value = row[index];
+ if (value.Type == EValueType::Min || value.Type == EValueType::Max) {
+ value.Type = EValueType::Null;
+ }
+ } else {
+ value = MakeUnversionedNullValue();
+ }
+ builder.AddValue(value);
+ }
+ auto result = builder.FinishRow();
+ YT_VERIFY(result.GetCount() == keyLength);
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/key.h b/yt/yt/client/table_client/key.h
new file mode 100644
index 0000000000..58f6bfd6e0
--- /dev/null
+++ b/yt/yt/client/table_client/key.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include "unversioned_row.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! This class represents a (contextually) schemaful comparable row. It behaves
+//! similarly to TUnversionedRow.
+class TKey
+{
+public:
+ //! A special null key, use it instead of enclosing TKey in std::optional.
+ TKey() = default;
+ explicit TKey(TUnversionedValueRange range);
+
+ //! Returns true if key is non-null and false otherwise.
+ explicit operator bool() const;
+
+ //! Construct from a given row and possibly key length and validate that row does not contain
+ //! setntinels of types Min, Max and Bottom. If key length is not specified, row length will be used instead.
+ static TKey FromRow(TUnversionedRow row, std::optional<int> length = {});
+
+ //! Same as above, but does not check that row does not contain sentinels.
+ //! NB: in debug mode value type check is still performed, but results in YT_ABORT().
+ static TKey FromRowUnchecked(TUnversionedRow row, std::optional<int> length = {});
+
+ //! Performs a deep copy of underlying values into owning row.
+ TUnversionedOwningRow AsOwningRow() const;
+
+ const TUnversionedValue& operator[](int index) const;
+
+ int GetLength() const;
+
+ //! Helpers for printing and hashing.
+ const TUnversionedValue* Begin() const;
+ const TUnversionedValue* End() const;
+ TUnversionedValueRange Elements() const;
+
+ void Persist(const TPersistenceContext& context);
+
+private:
+ TUnversionedValueRange Elements_;
+
+ static void ValidateValueTypes(TUnversionedValueRange range);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TKey& lhs, const TKey& rhs);
+bool operator!=(const TKey& lhs, const TKey& rhs);
+
+void FormatValue(TStringBuilderBase* builder, const TKey& key, TStringBuf format);
+TString ToString(const TKey& key);
+
+void Serialize(const TKey& key, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Helper interop function for fixing master's last_key returned from BeginUpload() RPC call
+//! and some other places.
+//!
+//! This is closely related to TKey since original key returned by master may contain sentinels
+//! or be of wrong length, so it is not suitable for TKey construction. We fix it by
+//! replacing all sentinels to <null> and by padding row with nulls or shortening it so that
+//! row length is exactly keyLength.
+//!
+//! NB: this method is inefficient (as it deals with owning rows), do not use it on hot path.
+TUnversionedOwningRow LegacyKeyToKeyFriendlyOwningRow(TUnversionedRow row, int keyLength);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+//! A hasher for TKey.
+template <>
+struct THash<NYT::NTableClient::TKey>
+{
+ inline size_t operator()(const NYT::NTableClient::TKey& key) const
+ {
+ return NYT::NTableClient::TDefaultUnversionedValueRangeHash()(key.Elements());
+ }
+};
diff --git a/yt/yt/client/table_client/key_bound.cpp b/yt/yt/client/table_client/key_bound.cpp
new file mode 100644
index 0000000000..6428d87e4e
--- /dev/null
+++ b/yt/yt/client/table_client/key_bound.cpp
@@ -0,0 +1,530 @@
+#include "key_bound.h"
+
+#include "helpers.h"
+#include "row_buffer.h"
+#include "serialize.h"
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NTableClient {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NLogging;
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Used only for YT_LOG_FATAL below.
+static const TLogger Logger("TableClientKey");
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::pair<bool, bool> RelationToIsUpperAndIsInclusive(TStringBuf relation)
+{
+ if (relation == "<=") {
+ return {/* isInclusive */ true, /* isUpper */ true};
+ } else if (relation == ">=") {
+ return {/* isInclusive */ true, /* isUpper */ false};
+ } else if (relation == "<") {
+ return {/* isInclusive */ false, /* isUpper */ true};
+ } else if (relation == ">") {
+ return {/* isInclusive */ false, /* isUpper */ false};
+ } else {
+ THROW_ERROR_EXCEPTION(
+ "Error parsing relation literal %Qv; one of \"<=\", \">=\", \"<\", \">\" expected",
+ relation);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::FromRow(const TRow& row, bool isInclusive, bool isUpper)
+{
+ YT_VERIFY(row);
+
+ ValidateValueTypes(row);
+ TKeyBound result;
+ result.Prefix = row;
+ result.IsInclusive = isInclusive;
+ result.IsUpper = isUpper;
+ return result;
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::FromRow(TRow&& row, bool isInclusive, bool isUpper)
+{
+ YT_VERIFY(row);
+
+ ValidateValueTypes(row);
+ TKeyBound result;
+ result.Prefix = row;
+ result.IsInclusive = isInclusive;
+ result.IsUpper = isUpper;
+ return result;
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::FromRowUnchecked(const TRow& row, bool isInclusive, bool isUpper)
+{
+ YT_VERIFY(row);
+
+#ifndef NDEBUG
+ try {
+ ValidateValueTypes(row);
+ } catch (const std::exception& ex) {
+ YT_LOG_FATAL(ex, "Unexpected exception while building key bound from row");
+ }
+#endif
+
+ TKeyBound result;
+ result.Prefix = row;
+ result.IsInclusive = isInclusive;
+ result.IsUpper = isUpper;
+ return result;
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::FromRowUnchecked(TRow&& row, bool isInclusive, bool isUpper)
+{
+ YT_VERIFY(row);
+
+#ifndef NDEBUG
+ try {
+ ValidateValueTypes(row);
+ } catch (const std::exception& ex) {
+ YT_LOG_FATAL(ex, "Unexpected exception while building key bound from row");
+ }
+#endif
+
+ TKeyBound result;
+ result.Prefix = row;
+ result.IsInclusive = isInclusive;
+ result.IsUpper = isUpper;
+ return result;
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::MakeUniversal(bool isUpper)
+{
+ return TKeyBoundImpl<TRow, TKeyBound>::FromRow(EmptyKey(), /* isInclusive */ true, isUpper);
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::MakeEmpty(bool isUpper)
+{
+ return TKeyBoundImpl<TRow, TKeyBound>::FromRow(EmptyKey(), /* isInclusive */ false, isUpper);
+}
+
+template <class TRow, class TKeyBound>
+void TKeyBoundImpl<TRow, TKeyBound>::ValidateValueTypes(const TRow& row)
+{
+ YT_VERIFY(row);
+
+ for (const auto& value : row) {
+ ValidateDataValueType(value.Type);
+ }
+}
+
+template <class TRow, class TKeyBound>
+void TKeyBoundImpl<TRow, TKeyBound>::FormatValue(TStringBuilderBase* builder) const
+{
+ if (!Prefix) {
+ builder->AppendChar('#');
+ } else {
+ builder->AppendChar(IsUpper ? '<' : '>');
+ if (IsInclusive) {
+ builder->AppendChar('=');
+ }
+ builder->AppendString(ToString(Prefix, /*valuesOnly*/ true));
+ }
+}
+
+template <class TRow, class TKeyBound>
+TKeyBoundImpl<TRow, TKeyBound>::operator bool() const
+{
+ return static_cast<bool>(Prefix);
+}
+
+template <class TRow, class TKeyBound>
+bool TKeyBoundImpl<TRow, TKeyBound>::IsUniversal() const
+{
+ return IsInclusive && Prefix && Prefix.GetCount() == 0;
+}
+
+template <class TRow, class TKeyBound>
+bool TKeyBoundImpl<TRow, TKeyBound>::IsEmpty() const
+{
+ return !IsInclusive && Prefix && Prefix.GetCount() == 0;
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::Invert() const
+{
+ YT_VERIFY(Prefix);
+ return TKeyBound::FromRowUnchecked(Prefix, !IsInclusive, !IsUpper);
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::ToggleInclusiveness() const
+{
+ YT_VERIFY(Prefix);
+ return TKeyBound::FromRowUnchecked(Prefix, !IsInclusive, IsUpper);
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::UpperCounterpart() const
+{
+ YT_VERIFY(Prefix);
+ return IsUpper ? *static_cast<const TKeyBound*>(this) : Invert();
+}
+
+template <class TRow, class TKeyBound>
+TKeyBound TKeyBoundImpl<TRow, TKeyBound>::LowerCounterpart() const
+{
+ YT_VERIFY(Prefix);
+ return IsUpper ? Invert() : *static_cast<const TKeyBound*>(this);
+}
+
+template <class TRow, class TKeyBound>
+TStringBuf TKeyBoundImpl<TRow, TKeyBound>::GetRelation() const
+{
+ if (IsUpper && IsInclusive) {
+ return "<=";
+ } else if (IsUpper && !IsInclusive) {
+ return "<";
+ } else if (!IsUpper && IsInclusive) {
+ return ">=";
+ } else if (!IsUpper && !IsInclusive) {
+ return ">";
+ } else {
+ Y_UNREACHABLE();
+ }
+}
+
+template <class TRow, class TKeyBound>
+void TKeyBoundImpl<TRow, TKeyBound>::Persist(const TPersistenceContext& context)
+{
+ using NYT::Persist;
+
+ Persist(context, Prefix);
+ Persist(context, IsInclusive);
+ Persist(context, IsUpper);
+}
+
+template <class TRow, class TKeyBound>
+void TKeyBoundImpl<TRow, TKeyBound>::Serialize(IYsonConsumer* consumer) const
+{
+ if (*this) {
+ BuildYsonFluently(consumer)
+ .BeginList()
+ .Item().Value(GetRelation())
+ .Item().Value(Prefix)
+ .EndList();
+ } else {
+ BuildYsonFluently(consumer)
+ .Entity();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template class TKeyBoundImpl<TUnversionedRow, TKeyBound>;
+template class TKeyBoundImpl<TUnversionedOwningRow, TOwningKeyBound>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOwningKeyBound::operator TKeyBound() const
+{
+ TKeyBound result;
+ result.Prefix = Prefix;
+ result.IsInclusive = IsInclusive;
+ result.IsUpper = IsUpper;
+ return result;
+}
+
+void FormatValue(TStringBuilderBase* builder, const TOwningKeyBound& keyBound, TStringBuf /*format*/)
+{
+ return keyBound.FormatValue(builder);
+}
+
+TString ToString(const TOwningKeyBound& keyBound)
+{
+ return ToStringViaBuilder(keyBound);
+}
+
+void PrintTo(const TOwningKeyBound& keyBound, ::std::ostream* os)
+{
+ *os << ToString(keyBound);
+}
+
+void Serialize(const TKeyBound& keyBound, IYsonConsumer* consumer)
+{
+ keyBound.Serialize(consumer);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOwningKeyBound TKeyBound::ToOwning() const
+{
+ TOwningKeyBound result;
+ result.Prefix = TUnversionedOwningRow(Prefix);
+ result.IsInclusive = IsInclusive;
+ result.IsUpper = IsUpper;
+ return result;
+}
+
+void FormatValue(TStringBuilderBase* builder, const TKeyBound& keyBound, TStringBuf /*format*/)
+{
+ return keyBound.FormatValue(builder);
+}
+
+TString ToString(const TKeyBound& keyBound)
+{
+ return ToStringViaBuilder(keyBound);
+}
+
+void PrintTo(const TKeyBound& keyBound, ::std::ostream* os)
+{
+ *os << ToString(keyBound);
+}
+
+void Serialize(const TOwningKeyBound& keyBound, IYsonConsumer* consumer)
+{
+ keyBound.Serialize(consumer);
+}
+
+void Deserialize(TOwningKeyBound& keyBound, const NYTree::INodePtr& node)
+{
+ if (node->GetType() == ENodeType::Entity) {
+ keyBound = TOwningKeyBound();
+ } else if (node->GetType() == ENodeType::List) {
+ auto listNode = node->AsList();
+ if (listNode->GetChildCount() != 2) {
+ THROW_ERROR_EXCEPTION(
+ "Error parsing key bound: list node must have exactly two elements, "
+ "first of which is a relation string literal, and second is a row; "
+ "%v elements found",
+ listNode->GetChildCount());
+ }
+ auto relationNode = listNode->GetChildOrThrow(0);
+ auto rowNode = listNode->GetChildOrThrow(1);
+
+ if (relationNode->GetType() != ENodeType::String) {
+ THROW_ERROR_EXCEPTION(
+ "Error parsing key bound: first element must be a string node; actual %Qv node",
+ relationNode->GetType());
+ }
+
+ auto relation = relationNode->GetValue<TString>();
+ auto [isInclusive, isUpper] = NDetail::RelationToIsUpperAndIsInclusive(relation);
+
+ TUnversionedOwningRow row;
+ Deserialize(row, rowNode);
+
+ keyBound = TOwningKeyBound::FromRow(row, isInclusive, isUpper);
+ } else {
+ THROW_ERROR_EXCEPTION("Error parsing key bound: expected %Qlv node, actual %Qlv node",
+ NYTree::ENodeType::List,
+ node->GetType());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator ==(const TKeyBound& lhs, const TKeyBound& rhs)
+{
+ return
+ lhs.Prefix == rhs.Prefix &&
+ lhs.IsInclusive == rhs.IsInclusive &&
+ lhs.IsUpper == rhs.IsUpper;
+}
+
+bool operator ==(const TOwningKeyBound& lhs, const TOwningKeyBound& rhs)
+{
+ return static_cast<TKeyBound>(lhs) == static_cast<TKeyBound>(rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Common implementation for owning case and non-owning case over row buffer.
+// Returns pair {prefixKeyLength, inclusive flag} describing how to transform
+// legacy key into key bound.
+std::pair<int, bool> GetBoundPrefixAndInclusiveness(TUnversionedRow row, bool isUpper, int keyLength)
+{
+ YT_VERIFY(row);
+
+ // Flag indicating that row starts with #keyLength non-sentinel values followed by at least one arbitrary value.
+ bool isLongRow = false;
+
+ // If row contains at least one sentinel on first #keyLength positions, type of leftmost of them.
+ std::optional<EValueType> leftmostSentinelType;
+
+ // Length of the longest prefix of row which is free of sentinels. Prefix length is limited by #keyLength.
+ int prefixLength = 0;
+ for (int index = 0; index < static_cast<int>(row.GetCount()) && index <= keyLength; ++index) {
+ if (index == keyLength) {
+ isLongRow = true;
+ break;
+ }
+ if (row[index].Type != EValueType::Min && row[index].Type != EValueType::Max) {
+ ++prefixLength;
+ } else {
+ leftmostSentinelType = row[index].Type;
+ break;
+ }
+ }
+
+ // When dealing with legacy rows, upper limit is always exclusive and lower limit is always inclusive.
+ // We will call this kind of inclusiveness standard. This implies following cases for key bounds.
+ //
+ // (A) If row is long, upper limit will be inclusive and lower limit will be exclusive, i.e. inclusiveness is toggled.
+ // (B) Otherwise, if row has exactly length of #keyLength and does not contain sentinels, inclusiveness is standard.
+ //
+ // Suppose none of (A) and (B) happened. We know that prefix is strictly shorter than #keyLength. If may or may not be
+ // followed by a sentinel. Actually there is no difference if prefix is followed by Min or if it is not followed by sentinel.
+ // To prove this fact, consider row R = prefix + [Min], length(R) < #keyLength and key K, length(K) == #keyLength.
+ // It is easy to see that R is compared to K in exactly the same way as prefix is compared to K; this case
+ // corresponds to a key bound with standard inclusiveness.
+ //
+ // Similar argument shows that if prefix is followed by Max, key bound inclusiveness should be toggled.
+ //
+ // So, we have only two more cases:
+ //
+ // (C) Otherwise, if prefix is followed by Min or no sentinel, inclusiveness is standard.
+ // (D) Otherwise (prefix is followed by Max), inclusiveness is toggled.
+
+ // Cases (A) and (D).
+ bool toggleInclusiveness = isLongRow || leftmostSentinelType == EValueType::Max;
+
+ bool isInclusive = (isUpper && toggleInclusiveness) || (!isUpper && !toggleInclusiveness);
+
+ return {prefixLength, isInclusive};
+}
+
+TOwningKeyBound KeyBoundFromLegacyRow(TUnversionedRow row, bool isUpper, int keyLength)
+{
+ if (!row) {
+ return TOwningKeyBound::MakeUniversal(isUpper);
+ }
+
+ auto [prefixLength, isInclusive] = GetBoundPrefixAndInclusiveness(row, isUpper, keyLength);
+ return TOwningKeyBound::FromRow(
+ TUnversionedOwningRow(row.FirstNElements(prefixLength)),
+ isInclusive,
+ isUpper);
+}
+
+TKeyBound KeyBoundFromLegacyRow(TUnversionedRow row, bool isUpper, int keyLength, const TRowBufferPtr& rowBuffer)
+{
+ if (!row) {
+ return TKeyBound::MakeUniversal(isUpper);
+ }
+
+ auto [prefixLength, isInclusive] = GetBoundPrefixAndInclusiveness(row, isUpper, keyLength);
+ YT_VERIFY(prefixLength <= static_cast<int>(row.GetCount()));
+ row = rowBuffer->CaptureRow(MakeRange(row.Begin(), prefixLength));
+
+ return TKeyBound::FromRow(
+ row,
+ isInclusive,
+ isUpper);
+}
+
+TUnversionedOwningRow KeyBoundToLegacyRow(TKeyBound keyBound)
+{
+ if (!keyBound) {
+ return TUnversionedOwningRow();
+ }
+
+ TUnversionedOwningRowBuilder builder;
+ for (const auto& value : keyBound.Prefix) {
+ builder.AddValue(value);
+ }
+ auto shouldAddMax = (keyBound.IsUpper && keyBound.IsInclusive) || (!keyBound.IsUpper && !keyBound.IsInclusive);
+ if (shouldAddMax) {
+ builder.AddValue(MakeUnversionedSentinelValue(EValueType::Max));
+ }
+ return builder.FinishRow();
+}
+
+TUnversionedRow KeyBoundToLegacyRow(TKeyBound keyBound, const TRowBufferPtr& rowBuffer)
+{
+ if (!keyBound) {
+ return TUnversionedRow();
+ }
+
+ auto shouldAddMax = (keyBound.IsUpper && keyBound.IsInclusive) || (!keyBound.IsUpper && !keyBound.IsInclusive);
+
+ auto row = rowBuffer->AllocateUnversioned(keyBound.Prefix.GetCount() + (shouldAddMax ? 1 : 0));
+ memcpy(row.Begin(), keyBound.Prefix.Begin(), sizeof(TUnversionedValue) * keyBound.Prefix.GetCount());
+ if (shouldAddMax) {
+ row[keyBound.Prefix.GetCount()] = MakeUnversionedSentinelValue(EValueType::Max, keyBound.Prefix.GetCount());
+ }
+ for (auto& value : row) {
+ rowBuffer->CaptureValue(&value);
+ }
+
+ return row;
+}
+
+TKeyBound ShortenKeyBound(TKeyBound keyBound, int length, const TRowBufferPtr& rowBuffer)
+{
+ if (!keyBound) {
+ return TKeyBound();
+ }
+
+ if (static_cast<int>(keyBound.Prefix.GetCount()) <= length) {
+ // No need to change anything.
+ return keyBound;
+ }
+
+ // If we do perform shortening, resulting key bound is going to be inclusive despite the original inclusiveness.
+
+ auto result = TKeyBound::FromRowUnchecked(
+ rowBuffer->CaptureRow(MakeRange(keyBound.Prefix.Begin(), length)),
+ /* isInclusive */ true,
+ keyBound.IsUpper);
+
+ return result;
+}
+
+//! Owning version of #ShortenKeyBound.
+TOwningKeyBound ShortenKeyBound(TOwningKeyBound keyBound, int length)
+{
+ if (keyBound.Prefix.GetCount() <= length) {
+ // No need to change anything.
+ return keyBound;
+ }
+
+ // If we do perform shortening, resulting key bound is going to be inclusive despite the original inclusiveness.
+
+ return TOwningKeyBound::FromRowUnchecked(
+ TUnversionedOwningRow(keyBound.Prefix.FirstNElements(length)),
+ /* isInclusive */ true,
+ keyBound.IsUpper);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+size_t THash<NYT::NTableClient::TKeyBound>::operator()(const NYT::NTableClient::TKeyBound& keyBound) const
+{
+ using NYT::HashCombine;
+
+ size_t result = 0;
+ HashCombine(result, keyBound.Prefix);
+ HashCombine(result, keyBound.IsInclusive);
+ HashCombine(result, keyBound.IsUpper);
+
+ return result;
+}
diff --git a/yt/yt/client/table_client/key_bound.h b/yt/yt/client/table_client/key_bound.h
new file mode 100644
index 0000000000..cad65bc5dd
--- /dev/null
+++ b/yt/yt/client/table_client/key_bound.h
@@ -0,0 +1,190 @@
+#pragma once
+
+#include "unversioned_row.h"
+#include "key.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! This class represents a (contextually) schemaful key bound. It defines
+//! an open or closed ray in a space of all possible keys.
+//! This is a CRTP base for common boilerplate code for owning and non-owning versions.
+template <class TRow, class TKeyBound>
+class TKeyBoundImpl
+{
+public:
+ // If #Prefix is a null row, key bound has a special meaning of being null bound.
+ // It is different from universal or empty bound; use it as a replacement for
+ // std::nullopt.
+ TRow Prefix;
+ bool IsInclusive = false;
+ bool IsUpper = false;
+
+ //! Construct from a given row and validate that row does not contain
+ //! setntinels of types Min, Max and Bottom.
+ static TKeyBound FromRow(const TRow& row, bool isInclusive, bool isUpper);
+
+ //! Same as previous but for rvalue refs.
+ static TKeyBound FromRow(TRow&& row, bool isInclusive, bool isUpper);
+
+ //! Construct from a given row without checking presence of types Min, Max and Bottom.
+ //! NB: in debug mode value type check is still performed, but results in YT_ABORT().
+ static TKeyBound FromRowUnchecked(const TRow& row, bool isInclusive, bool isUpper);
+
+ //! Same as previous but for rvalue refs.
+ static TKeyBound FromRowUnchecked(TRow&& row, bool isInclusive, bool isUpper);
+
+ // In order to reduce amount of repetitive constructions like
+ //
+ // TKeyBound::FromRow(row, /* isInclusive */ true, /* isUpper */ false)
+ //
+ // with two flags known in compile-time, we introduce a helper class
+ // allowing you to build key bounds as follows:
+ //
+ // TKeyBound::FromRow() >= row
+ //
+ // These helpers also support unchecked variant of static constructors above.
+ // They work both for owning and non-owning kinds of key bound.
+ #define XX(suffix) \
+ struct TBuilder ## suffix { \
+ TKeyBound operator > (const TRow& row) { return TKeyBound::FromRow ## suffix(row, false, false); } \
+ TKeyBound operator >=(const TRow& row) { return TKeyBound::FromRow ## suffix(row, true , false); } \
+ TKeyBound operator < (const TRow& row) { return TKeyBound::FromRow ## suffix(row, false, true ); } \
+ TKeyBound operator <=(const TRow& row) { return TKeyBound::FromRow ## suffix(row, true , true ); } \
+ TKeyBound operator > (TRow&& row) { return TKeyBound::FromRow ## suffix(row, false, false); } \
+ TKeyBound operator >=(TRow&& row) { return TKeyBound::FromRow ## suffix(row, true , false); } \
+ TKeyBound operator < (TRow&& row) { return TKeyBound::FromRow ## suffix(row, false, true ); } \
+ TKeyBound operator <=(TRow&& row) { return TKeyBound::FromRow ## suffix(row, true , true ); } \
+ }; \
+ TBuilder ## suffix static FromRow ## suffix() { return TBuilder##suffix(); }
+
+ XX()
+ XX(Unchecked)
+
+ #undef XX
+
+ //! Return a key bound that allows any key.
+ static TKeyBound MakeUniversal(bool isUpper);
+
+ //! Return a key bound that does not allow any key.
+ static TKeyBound MakeEmpty(bool isUpper);
+
+ void FormatValue(TStringBuilderBase* builder) const;
+
+ explicit operator bool() const;
+
+ //! Test if this key bound allows any key.
+ bool IsUniversal() const;
+
+ //! Test if this key bound allows no keys.
+ bool IsEmpty() const;
+
+ //! Return key bound which is complementary to current.
+ TKeyBound Invert() const;
+
+ //! Return key bound with same prefix and direction but toggled inclusiveness.
+ TKeyBound ToggleInclusiveness() const;
+
+ //! Return key bound that is upper among {*this, this->Invert()}.
+ TKeyBound UpperCounterpart() const;
+ //! Return key bound that is lower among {*this, this->Invert()}.
+ TKeyBound LowerCounterpart() const;
+
+ //! Returns string among {">=", ">", "<=", "<"} defining this key bound kind.
+ TStringBuf GetRelation() const;
+
+ void Persist(const TPersistenceContext& context);
+
+ void Serialize(NYson::IYsonConsumer* consumer) const;
+
+private:
+ static void ValidateValueTypes(const TRow& row);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TKeyBound
+ : public NDetail::TKeyBoundImpl<TUnversionedRow, TKeyBound>
+{
+public:
+ TOwningKeyBound ToOwning() const;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TKeyBound& keyBound, TStringBuf format);
+TString ToString(const TKeyBound& keyBound);
+
+void Serialize(const TKeyBound& keyBound, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TOwningKeyBound
+ : public NDetail::TKeyBoundImpl<TUnversionedOwningRow, TOwningKeyBound>
+{
+public:
+ operator TKeyBound() const;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TOwningKeyBound& keyBound, TStringBuf format);
+TString ToString(const TOwningKeyBound& keyBound);
+
+void Serialize(const TOwningKeyBound& keyBound, NYson::IYsonConsumer* consumer);
+void Deserialize(TOwningKeyBound& keyBound, const NYTree::INodePtr& node);
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator ==(const TKeyBound& lhs, const TKeyBound& rhs);
+bool operator ==(const TOwningKeyBound& lhs, const TOwningKeyBound& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Interop functions.
+
+//! Returns significant prefix length and inclusiveness.
+std::pair<int, bool> GetBoundPrefixAndInclusiveness(TUnversionedRow row, bool isUpper, int keyLength);
+
+//! Convert legacy key bound expressed as a row possibly containing Min/Max to owning key bound.
+//! NB: key length is needed to properly distinguish if K + [min] is an inclusive K or exclusive K.
+TOwningKeyBound KeyBoundFromLegacyRow(TUnversionedRow row, bool isUpper, int keyLength);
+
+//! Same as previous, but non-owning variant over row buffer.
+TKeyBound KeyBoundFromLegacyRow(TUnversionedRow row, bool isUpper, int keyLength, const TRowBufferPtr& rowBuffer);
+
+//! Convert key bound to legacy key bound.
+TUnversionedOwningRow KeyBoundToLegacyRow(TKeyBound keyBound);
+
+//! Same as previous, but non-owning variant over row buffer.
+TUnversionedRow KeyBoundToLegacyRow(TKeyBound keyBound, const TRowBufferPtr& rowBuffer);
+
+//! Build the most accurate key bound of length #length corresponding to the ray containing
+//! ray corresponding to #keyBound.
+TKeyBound ShortenKeyBound(TKeyBound keyBound, int length, const TRowBufferPtr& rowBuffer);
+
+//! Owning version of #ShortenKeyBound.
+TOwningKeyBound ShortenKeyBound(TOwningKeyBound keyBound, int length);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Debug printers for Gtest unittests.
+
+void PrintTo(const TKeyBound& key, ::std::ostream* os);
+void PrintTo(const TOwningKeyBound& key, ::std::ostream* os);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+//! A hasher for TKeyBound.
+template <>
+struct THash<NYT::NTableClient::TKeyBound>
+{
+ size_t operator()(const NYT::NTableClient::TKeyBound& keyBound) const;
+};
diff --git a/yt/yt/client/table_client/key_bound_compressor.cpp b/yt/yt/client/table_client/key_bound_compressor.cpp
new file mode 100644
index 0000000000..35b87c7190
--- /dev/null
+++ b/yt/yt/client/table_client/key_bound_compressor.cpp
@@ -0,0 +1,164 @@
+#include "key_bound_compressor.h"
+
+#include <yt/yt/core/misc/collection_helpers.h>
+
+#include <yt/yt/core/logging/fluent_log.h>
+
+namespace NYT::NTableClient {
+
+using namespace NLogging;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyBoundCompressor::TKeyBoundCompressor(const TComparator& comparator)
+ : Comparator_(comparator)
+{ }
+
+void TKeyBoundCompressor::Add(TKeyBound keyBound)
+{
+ YT_VERIFY(keyBound);
+
+ AddedKeyBounds_.insert(keyBound);
+ AddedKeyBounds_.insert(keyBound.Invert());
+}
+
+void TKeyBoundCompressor::InitializeMapping()
+{
+ YT_VERIFY(!MappingInitialized_);
+ MappingInitialized_ = true;
+
+ SortedKeyBounds_.reserve(AddedKeyBounds_.size());
+ SortedKeyBounds_.insert(SortedKeyBounds_.end(), AddedKeyBounds_.begin(), AddedKeyBounds_.end());
+ std::sort(
+ SortedKeyBounds_.begin(),
+ SortedKeyBounds_.end(),
+ [&] (const TKeyBound& lhs, const TKeyBound& rhs) {
+ return Comparator_.CompareKeyBounds(lhs, rhs) < 0;
+ });
+
+ // Prepare images.
+ for (const auto& keyBound : AddedKeyBounds_) {
+ Mapping_[keyBound];
+ }
+
+ // Prepare component-wise image prefixes.
+ ComponentWisePrefixes_.resize(SortedKeyBounds_.size());
+ for (ssize_t index = 0; index < ssize(SortedKeyBounds_); ++index) {
+ ComponentWisePrefixes_[index] = RowBuffer_->AllocateUnversioned(SortedKeyBounds_[index].Prefix.GetCount());
+ }
+
+ // First, calculate global images.
+ // Recall that we are identifying (assuming comparator of length 2):
+ // * >=[foo, 2], >[foo, 2], <=[foo, 2], <[foo, 2]
+ // * >=[foo], <[foo]
+ // * >[foo], <=[foo]
+ // * but not >=[foo] with [foo].
+ for (
+ ssize_t beginIndex = 0, endIndex = 0, currentImage = 0;
+ beginIndex < ssize(SortedKeyBounds_);
+ beginIndex = endIndex, ++currentImage)
+ {
+ auto beginBound = SortedKeyBounds_[beginIndex];
+
+ while (true) {
+ if (endIndex >= ssize(SortedKeyBounds_)) {
+ break;
+ }
+ auto endBound = SortedKeyBounds_[endIndex];
+ if (beginBound != endBound &&
+ beginBound.Invert() != endBound &&
+ (beginBound.Prefix != endBound.Prefix ||
+ static_cast<int>(beginBound.Prefix.GetCount()) != Comparator_.GetLength()))
+ {
+ break;
+ }
+ Mapping_[endBound].Global = currentImage;
+ ++endIndex;
+ }
+ }
+
+ // Second, calculate component-wise images.
+ CalculateComponentWise(/*fromIndex*/ 0, /*toIndex*/ SortedKeyBounds_.size(), /*componentIndex*/ 0);
+ for (ssize_t index = 0; index < ssize(SortedKeyBounds_); ++index) {
+ const auto& keyBound = SortedKeyBounds_[index];
+ auto& componentWiseImage = Mapping_[keyBound].ComponentWise;
+ componentWiseImage.Prefix = ComponentWisePrefixes_[index];
+ componentWiseImage.IsInclusive = SortedKeyBounds_[index].IsInclusive;
+ componentWiseImage.IsUpper = SortedKeyBounds_[index].IsUpper;
+ }
+}
+
+void TKeyBoundCompressor::CalculateComponentWise(ssize_t fromIndex, ssize_t toIndex, ssize_t componentIndex)
+{
+ if (fromIndex == toIndex || componentIndex == Comparator_.GetLength()) {
+ return;
+ }
+ for (
+ ssize_t beginIndex = fromIndex, endIndex = fromIndex, currentImage = 0;
+ beginIndex < toIndex;
+ beginIndex = endIndex, ++currentImage)
+ {
+ // Skip a bunch of key bounds that are too short to have currently processed component.
+ while (true) {
+ if (beginIndex >= toIndex) {
+ break;
+ }
+ auto beginBound = SortedKeyBounds_[beginIndex];
+ if (beginBound.Prefix.GetCount() > componentIndex) {
+ break;
+ }
+ ++beginIndex;
+ }
+ if (beginIndex >= toIndex) {
+ break;
+ }
+
+ // Extract a contiguous segment of key bounds sharing the same value in current component.
+ endIndex = beginIndex;
+ auto beginBound = SortedKeyBounds_[beginIndex];
+ while (true) {
+ if (endIndex >= toIndex) {
+ break;
+ }
+ auto endBound = SortedKeyBounds_[endIndex];
+ if (endBound.Prefix.GetCount() <= componentIndex) {
+ break;
+ }
+ if (endBound.Prefix[componentIndex] != beginBound.Prefix[componentIndex]) {
+ break;
+ }
+ ComponentWisePrefixes_[endIndex][componentIndex] = MakeUnversionedInt64Value(currentImage, componentIndex);
+ ++endIndex;
+ }
+
+ CalculateComponentWise(beginIndex, endIndex, componentIndex + 1);
+ }
+}
+
+TKeyBoundCompressor::TImage TKeyBoundCompressor::GetImage(TKeyBound keyBound) const
+{
+ YT_VERIFY(MappingInitialized_);
+ return GetOrCrash(Mapping_, keyBound);
+}
+
+void TKeyBoundCompressor::Dump(const TLogger& logger)
+{
+ YT_VERIFY(MappingInitialized_);
+
+ TStructuredLogBatcher batcher(logger);
+
+ for (const auto& keyBound : SortedKeyBounds_) {
+ auto image = GetImage(keyBound);
+ batcher.AddItemFluently()
+ .BeginList()
+ .Item().Value(keyBound)
+ .Item().Value(image.ComponentWise)
+ .Item().Value(image.Global)
+ .EndList();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/key_bound_compressor.h b/yt/yt/client/table_client/key_bound_compressor.h
new file mode 100644
index 0000000000..98b0bffb02
--- /dev/null
+++ b/yt/yt/client/table_client/key_bound_compressor.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "key_bound.h"
+#include "comparator.h"
+#include "row_buffer.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Helper for isomorphic "compression" of key bounds for easier perception.
+//! Example:
+//!
+//! Preimage ComponentWise Global
+//! >= [] >= [] 0
+//! >= [bar] >= [0] 1
+//! >= [bar, 42] >= [0, 0] 2
+//! <= [bar, 57] <= [0, 1] 3
+//! <= [bar] <= [0] 4
+//! < [foo] < [1] 5
+//! >= [foo] >= [1] 5
+//! > [foo; 30] > [1, 0] 6
+//! < [foo; 99] < [1, 1] 7
+//! <= [foo; 99] <= [1, 1] 7
+//! > [foo; 99] > [1, 1] 7
+//! < [qux; 18] < [2, 0] 8
+//! < [zzz] < [3] 9
+//! <= [] < [] 10
+//! > [] > [] 10
+//!
+//! Note that ComponentWise is a TKeyBound -> TKeyBound mapping,
+//! while Global is a TKeyBound -> int mapping which identifies
+//! complementary key bounds and also full-length key bounds with
+//! coinciding prefixes.
+class TKeyBoundCompressor
+{
+public:
+ explicit TKeyBoundCompressor(const TComparator& comparator);
+
+ void Add(TKeyBound keyBound);
+
+ void InitializeMapping();
+
+ void Dump(const NLogging::TLogger& logger);
+
+ struct TImage
+ {
+ TKeyBound ComponentWise;
+ int Global = -1;
+ };
+
+ TImage GetImage(TKeyBound keyBound) const;
+
+private:
+ TComparator Comparator_;
+
+ THashSet<TKeyBound> AddedKeyBounds_;
+
+ std::vector<TKeyBound> SortedKeyBounds_;
+ std::vector<TMutableUnversionedRow> ComponentWisePrefixes_;
+
+ THashMap<TKeyBound, TImage> Mapping_;
+ bool MappingInitialized_ = false;
+
+ TRowBufferPtr RowBuffer_ = New<TRowBuffer>();
+
+ //! Recursive method for calculating component-wise images.
+ void CalculateComponentWise(ssize_t fromIndex, ssize_t toIndex, ssize_t componentIndex);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/logical_type-inl.h b/yt/yt/client/table_client/logical_type-inl.h
new file mode 100644
index 0000000000..28bd5b17a6
--- /dev/null
+++ b/yt/yt/client/table_client/logical_type-inl.h
@@ -0,0 +1,145 @@
+#ifndef LOGICAL_TYPE_INL_H_
+#error "Direct inclusion of this file is not allowed, include logical_type.h"
+// For the sake of sane code completion.
+#include "logical_type.h"
+#endif
+#undef LOGICAL_TYPE_INL_H_
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ELogicalMetatype TLogicalType::GetMetatype() const
+{
+ return Metatype_;
+}
+
+const TSimpleLogicalType& TLogicalType::UncheckedAsSimpleTypeRef() const
+{
+ return static_cast<const TSimpleLogicalType&>(*this);
+}
+
+const TDecimalLogicalType& TLogicalType::UncheckedAsDecimalTypeRef() const
+{
+ return static_cast<const TDecimalLogicalType&>(*this);
+}
+
+const TOptionalLogicalType& TLogicalType::UncheckedAsOptionalTypeRef() const
+{
+ return static_cast<const TOptionalLogicalType&>(*this);
+}
+
+const TListLogicalType& TLogicalType::UncheckedAsListTypeRef() const
+{
+ return static_cast<const TListLogicalType&>(*this);
+}
+
+const TStructLogicalType& TLogicalType::UncheckedAsStructTypeRef() const
+{
+ return static_cast<const TStructLogicalType&>(*this);
+}
+
+const TTupleLogicalType& TLogicalType::UncheckedAsTupleTypeRef() const
+{
+ return static_cast<const TTupleLogicalType&>(*this);
+}
+
+const TVariantTupleLogicalType& TLogicalType::UncheckedAsVariantTupleTypeRef() const
+{
+ return static_cast<const TVariantTupleLogicalType&>(*this);
+}
+
+const TVariantStructLogicalType& TLogicalType::UncheckedAsVariantStructTypeRef() const
+{
+ return static_cast<const TVariantStructLogicalType&>(*this);
+}
+
+const TDictLogicalType& TLogicalType::UncheckedAsDictTypeRef() const
+{
+ return static_cast<const TDictLogicalType&>(*this);
+}
+
+const TTaggedLogicalType& TLogicalType::UncheckedAsTaggedTypeRef() const
+{
+ return static_cast<const TTaggedLogicalType&>(*this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int TDecimalLogicalType::GetPrecision() const
+{
+ return Precision_;
+}
+
+int TDecimalLogicalType::GetScale() const
+{
+ return Scale_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TLogicalTypePtr& TOptionalLogicalType::GetElement() const
+{
+ return Element_;
+}
+
+bool TOptionalLogicalType::IsElementNullable() const
+{
+ return ElementIsNullable_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ESimpleLogicalValueType TSimpleLogicalType::GetElement() const
+{
+ return Element_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TLogicalTypePtr& TListLogicalType::GetElement() const
+{
+ return Element_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const std::vector<TStructField>& TStructLogicalTypeBase::GetFields() const
+{
+ return Fields_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const std::vector<TLogicalTypePtr>& TTupleLogicalTypeBase::GetElements() const
+{
+ return Elements_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TLogicalTypePtr& TDictLogicalType::GetKey() const
+{
+ return Key_;
+}
+
+const TLogicalTypePtr& TDictLogicalType::GetValue() const
+{
+ return Value_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString& TTaggedLogicalType::GetTag() const
+{
+ return Tag_;
+}
+
+const TLogicalTypePtr& TTaggedLogicalType::GetElement() const
+{
+ return Element_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient \ No newline at end of file
diff --git a/yt/yt/client/table_client/logical_type.cpp b/yt/yt/client/table_client/logical_type.cpp
new file mode 100644
index 0000000000..8d7687c755
--- /dev/null
+++ b/yt/yt/client/table_client/logical_type.cpp
@@ -0,0 +1,2214 @@
+#include "logical_type.h"
+#include "schema.h"
+#include "yt/yt/client/table_client/row_base.h"
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/finally.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/node.h>
+
+#include <util/charset/utf8.h>
+
+namespace NYT::NTableClient {
+
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWalkContext
+{
+ std::vector<TComplexTypeFieldDescriptor> Stack;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void WalkImpl(
+ TWalkContext* walkContext,
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::function<void(const TWalkContext&, const TComplexTypeFieldDescriptor&)>& onElement)
+{
+ onElement(*walkContext, descriptor);
+ walkContext->Stack.push_back(descriptor);
+ auto g = Finally([&]() {
+ walkContext->Stack.pop_back();
+ });
+ const auto metatype = descriptor.GetType()->GetMetatype();
+ switch (metatype) {
+ case ELogicalMetatype::Simple:
+ case ELogicalMetatype::Decimal:
+ return;
+ case ELogicalMetatype::Optional:
+ WalkImpl(walkContext, descriptor.OptionalElement(), onElement);
+ return;
+ case ELogicalMetatype::List:
+ WalkImpl(walkContext, descriptor.ListElement(), onElement);
+ return;
+ case ELogicalMetatype::Struct:
+ for (size_t i = 0; i < descriptor.GetType()->AsStructTypeRef().GetFields().size(); ++i) {
+ WalkImpl(walkContext, descriptor.StructField(i), onElement);
+ }
+ return;
+ case ELogicalMetatype::Tuple:
+ for (size_t i = 0; i < descriptor.GetType()->AsTupleTypeRef().GetElements().size(); ++i) {
+ WalkImpl(walkContext, descriptor.TupleElement(i), onElement);
+ }
+ return;
+ case ELogicalMetatype::VariantStruct: {
+ for (size_t i = 0; i < descriptor.GetType()->AsVariantStructTypeRef().GetFields().size(); ++i) {
+ WalkImpl(walkContext, descriptor.VariantStructField(i), onElement);
+ }
+ return;
+ }
+ case ELogicalMetatype::VariantTuple:
+ for (size_t i = 0; i < descriptor.GetType()->AsVariantTupleTypeRef().GetElements().size(); ++i) {
+ WalkImpl(walkContext, descriptor.VariantTupleElement(i), onElement);
+ }
+ return;
+ case ELogicalMetatype::Dict:
+ WalkImpl(walkContext, descriptor.DictKey(), onElement);
+ WalkImpl(walkContext, descriptor.DictValue(), onElement);
+ return;
+ case ELogicalMetatype::Tagged:
+ WalkImpl(walkContext, descriptor.TaggedElement(), onElement);
+ return;
+ }
+ YT_ABORT();
+}
+
+static void Walk(
+ const TComplexTypeFieldDescriptor& descriptor,
+ const std::function<void(const TWalkContext&, const TComplexTypeFieldDescriptor&)>& onElement)
+{
+ TWalkContext walkContext;
+ WalkImpl(&walkContext, descriptor, onElement);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T, typename F>
+const T& VerifiedCast(const F& from)
+{
+ const T* to = dynamic_cast<const T*>(&from);
+ YT_VERIFY(to != nullptr);
+ return *to;
+}
+
+TLogicalType::TLogicalType(ELogicalMetatype type)
+ : Metatype_(type)
+{}
+
+const TSimpleLogicalType& TLogicalType::AsSimpleTypeRef() const
+{
+ return VerifiedCast<TSimpleLogicalType>(*this);
+}
+
+const TDecimalLogicalType& TLogicalType::AsDecimalTypeRef() const
+{
+ return VerifiedCast<TDecimalLogicalType>(*this);
+}
+
+const TOptionalLogicalType& TLogicalType::AsOptionalTypeRef() const
+{
+ return VerifiedCast<TOptionalLogicalType>(*this);
+}
+
+const TListLogicalType& TLogicalType::AsListTypeRef() const
+{
+ return VerifiedCast<TListLogicalType>(*this);
+}
+
+const TStructLogicalType& TLogicalType::AsStructTypeRef() const
+{
+ return VerifiedCast<TStructLogicalType>(*this);
+}
+
+const TTupleLogicalType& TLogicalType::AsTupleTypeRef() const
+{
+ return VerifiedCast<TTupleLogicalType>(*this);
+}
+
+const TVariantTupleLogicalType& TLogicalType::AsVariantTupleTypeRef() const
+{
+ return VerifiedCast<TVariantTupleLogicalType>(*this);
+}
+
+const TVariantStructLogicalType& TLogicalType::AsVariantStructTypeRef() const
+{
+ return VerifiedCast<TVariantStructLogicalType>(*this);
+}
+
+const TDictLogicalType& TLogicalType::AsDictTypeRef() const
+{
+ return VerifiedCast<TDictLogicalType>(*this);
+}
+
+const TTaggedLogicalType& TLogicalType::AsTaggedTypeRef() const
+{
+ return VerifiedCast<TTaggedLogicalType>(*this);
+}
+
+const TLogicalTypePtr& TLogicalType::GetElement() const
+{
+ switch (Metatype_) {
+ case ELogicalMetatype::Optional:
+ return AsOptionalTypeRef().GetElement();
+ case ELogicalMetatype::List:
+ return AsListTypeRef().GetElement();
+ case ELogicalMetatype::Tagged:
+ return AsTaggedTypeRef().GetElement();
+ default:
+ YT_ABORT();
+ }
+}
+
+const std::vector<TLogicalTypePtr>& TLogicalType::GetElements() const
+{
+ switch (Metatype_) {
+ case ELogicalMetatype::Tuple:
+ return AsTupleTypeRef().GetElements();
+ case ELogicalMetatype::VariantTuple:
+ return AsVariantTupleTypeRef().GetElements();
+ default:
+ YT_ABORT();
+ }
+}
+
+const std::vector<TStructField>& TLogicalType::GetFields() const
+{
+ switch (Metatype_) {
+ case ELogicalMetatype::Struct:
+ return AsStructTypeRef().GetFields();
+ case ELogicalMetatype::VariantStruct:
+ return AsVariantStructTypeRef().GetFields();
+ default:
+ YT_ABORT();
+ }
+}
+
+static bool operator == (const TStructField& lhs, const TStructField& rhs)
+{
+ return (lhs.Name == rhs.Name) && (*lhs.Type == *rhs.Type);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TLogicalType& logicalType)
+{
+ switch (logicalType.GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return CamelCaseToUnderscoreCase(ToString(logicalType.AsSimpleTypeRef().GetElement()));
+ case ELogicalMetatype::Decimal:
+ return Format("decimal(%v,%v)",
+ logicalType.AsDecimalTypeRef().GetPrecision(),
+ logicalType.AsDecimalTypeRef().GetScale());
+ case ELogicalMetatype::Optional:
+ return Format("optional<%v>", *logicalType.AsOptionalTypeRef().GetElement());
+ case ELogicalMetatype::List:
+ return Format("list<%v>", *logicalType.AsListTypeRef().GetElement());
+ case ELogicalMetatype::Struct: {
+ TStringStream out;
+ out << "struct<";
+ bool first = true;
+ for (const auto& structItem : logicalType.AsStructTypeRef().GetFields()) {
+ if (first) {
+ first = false;
+ } else {
+ out << ';';
+ }
+ out << structItem.Name << '=' << ToString(*structItem.Type);
+ }
+ out << '>';
+ return out.Str();
+ }
+ case ELogicalMetatype::Tuple: {
+ TStringStream out;
+ out << "tuple<";
+ bool first = true;
+ for (const auto& element : logicalType.AsTupleTypeRef().GetElements()) {
+ if (first) {
+ first = false;
+ } else {
+ out << ';';
+ }
+ out << ToString(*element);
+ }
+ out << '>';
+ return out.Str();
+ }
+ case ELogicalMetatype::VariantTuple: {
+ TStringStream out;
+ out << "variant<";
+ bool first = true;
+ for (const auto& element : logicalType.AsVariantTupleTypeRef().GetElements()) {
+ if (first) {
+ first = false;
+ } else {
+ out << ';';
+ }
+ out << ToString(*element);
+ }
+ out << '>';
+ return out.Str();
+ }
+ case ELogicalMetatype::VariantStruct: {
+ TStringStream out;
+ out << "named_variant<";
+ bool first = true;
+ for (const auto& field : logicalType.AsVariantStructTypeRef().GetFields()) {
+ if (first) {
+ first = false;
+ } else {
+ out << ';';
+ }
+ out << field.Name << '=' << ToString(*field.Type);
+ }
+ out << '>';
+ return out.Str();
+
+ }
+ case ELogicalMetatype::Dict: {
+ const auto& dictType = logicalType.AsDictTypeRef();
+ TStringStream out;
+ out << "dict<" << ToString(*dictType.GetKey()) << ';' << ToString(*dictType.GetValue()) << '>';
+ return out.Str();
+ }
+ case ELogicalMetatype::Tagged: {
+ const auto& taggedType = logicalType.AsTaggedTypeRef();
+ TStringStream out;
+ out << "tagged<\"" << ToString(taggedType.GetTag()) << "\";" << ToString(*taggedType.GetElement()) << '>';
+ return out.Str();
+ }
+ }
+ YT_ABORT();
+}
+
+void PrintTo(ELogicalMetatype metatype, std::ostream* os)
+{
+ *os << ToString(metatype);
+}
+
+void PrintTo(const TLogicalType& type, std::ostream* os)
+{
+ *os << ToString(type);
+}
+
+void PrintTo(const TLogicalTypePtr& type, std::ostream* os)
+{
+ if (type) {
+ PrintTo(*type, os);
+ } else {
+ (*os) << "<nullptr>";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDecimalLogicalType::TDecimalLogicalType(int precision, int scale)
+ : TLogicalType(ELogicalMetatype::Decimal)
+ , Precision_(precision)
+ , Scale_(scale)
+{ }
+
+size_t TDecimalLogicalType::GetMemoryUsage() const
+{
+ return sizeof(*this);
+}
+
+int TDecimalLogicalType::GetTypeComplexity() const
+{
+ return 1;
+}
+
+void TDecimalLogicalType::ValidateNode(const TWalkContext& /*context*/) const
+{
+ if (Precision_ < MinPrecision || Precision_ > MaxPrecision) {
+ THROW_ERROR_EXCEPTION("Decimal precision %Qv is not in range [%v, %v]",
+ Precision_,
+ MinPrecision,
+ MaxPrecision);
+ }
+ if (Scale_ < 0) {
+ THROW_ERROR_EXCEPTION("Decimal scale %Qv is negative",
+ Scale_);
+ }
+
+ if (Scale_ > Precision_) {
+ THROW_ERROR_EXCEPTION("Decimal scale %Qv exceeds precision %Qv",
+ Scale_,
+ Precision_);
+ }
+}
+
+bool TDecimalLogicalType::IsNullable() const
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOptionalLogicalType::TOptionalLogicalType(TLogicalTypePtr element)
+ : TLogicalType(ELogicalMetatype::Optional)
+ , Element_(std::move(element))
+ , ElementIsNullable_(Element_->IsNullable())
+{ }
+
+std::optional<ESimpleLogicalValueType> TOptionalLogicalType::Simplify() const
+{
+ if (!IsElementNullable() && GetElement()->GetMetatype() == ELogicalMetatype::Simple) {
+ return GetElement()->AsSimpleTypeRef().GetElement();
+ } else {
+ return std::nullopt;
+ }
+}
+
+size_t TOptionalLogicalType::GetMemoryUsage() const
+{
+ if (Element_->GetMetatype() == ELogicalMetatype::Simple) {
+ // All optionals of simple logical types are signletons and therefore we assume they use no space.
+ return 0;
+ } else {
+ return sizeof(*this) + Element_->GetMemoryUsage();
+ }
+}
+
+int TOptionalLogicalType::GetTypeComplexity() const
+{
+ if (Element_->GetMetatype() == ELogicalMetatype::Simple) {
+ return 1;
+ } else {
+ return 1 + Element_->GetTypeComplexity();
+ }
+}
+
+void TOptionalLogicalType::ValidateNode(const TWalkContext&) const
+{ }
+
+bool TOptionalLogicalType::IsNullable() const
+{
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSimpleLogicalType::TSimpleLogicalType(ESimpleLogicalValueType element)
+ : TLogicalType(ELogicalMetatype::Simple)
+ , Element_(element)
+{ }
+
+size_t TSimpleLogicalType::GetMemoryUsage() const
+{
+ // All simple logical types are signletons and therefore we assume they use no space.
+ return 0;
+}
+
+int TSimpleLogicalType::GetTypeComplexity() const
+{
+ return 1;
+}
+
+void TSimpleLogicalType::ValidateNode(const TWalkContext& context) const
+{
+ if (Element_ == ESimpleLogicalValueType::Any) {
+ if (context.Stack.empty() || context.Stack.back().GetType()->GetMetatype() != ELogicalMetatype::Optional) {
+ THROW_ERROR_EXCEPTION("Type %Qv is disallowed outside of optional",
+ ESimpleLogicalValueType::Any);
+ }
+ }
+}
+
+bool TSimpleLogicalType::IsNullable() const
+{
+ return GetPhysicalType(Element_) == EValueType::Null;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TListLogicalType::TListLogicalType(TLogicalTypePtr element)
+ : TLogicalType(ELogicalMetatype::List)
+ , Element_(std::move(element))
+{ }
+
+size_t TListLogicalType::GetMemoryUsage() const
+{
+ return sizeof(*this) + Element_->GetMemoryUsage();
+}
+
+int TListLogicalType::GetTypeComplexity() const
+{
+ return 1 + Element_->GetTypeComplexity();
+}
+
+void TListLogicalType::ValidateNode(const TWalkContext& /*context*/) const
+{ }
+
+bool TListLogicalType::IsNullable() const
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TComplexTypeFieldDescriptor::TComplexTypeFieldDescriptor(TLogicalTypePtr type)
+ : Type_(std::move(type))
+{ }
+
+TComplexTypeFieldDescriptor::TComplexTypeFieldDescriptor(const NYT::NTableClient::TColumnSchema& column)
+ : TComplexTypeFieldDescriptor(column.Name(), column.LogicalType())
+{ }
+
+TComplexTypeFieldDescriptor::TComplexTypeFieldDescriptor(TString columnName, TLogicalTypePtr type)
+ : Descriptor_(std::move(columnName))
+ , Type_(std::move(type))
+{ }
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::OptionalElement() const
+{
+ return TComplexTypeFieldDescriptor(Descriptor_ + ".<optional-element>", Type_->AsOptionalTypeRef().GetElement());
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::ListElement() const
+{
+ return TComplexTypeFieldDescriptor(Descriptor_ + ".<list-element>", Type_->AsListTypeRef().GetElement());
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::Field(size_t i) const
+{
+ switch (Type_->GetMetatype()) {
+ case ELogicalMetatype::Struct:
+ return StructField(i);
+ case ELogicalMetatype::VariantStruct:
+ return VariantStructField(i);
+ default:
+ YT_ABORT();
+ }
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::StructField(size_t i) const
+{
+ const auto& fields = Type_->AsStructTypeRef().GetFields();
+ YT_VERIFY(i < fields.size());
+ const auto& field = fields[i];
+ return TComplexTypeFieldDescriptor(Descriptor_ + "." + field.Name, field.Type);
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::Element(size_t i) const
+{
+ switch (Type_->GetMetatype()) {
+ case ELogicalMetatype::Tuple:
+ return TupleElement(i);
+ case ELogicalMetatype::VariantTuple:
+ return VariantTupleElement(i);
+ default:
+ YT_ABORT();
+ }
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::TupleElement(size_t i) const
+{
+ const auto& elements = Type_->AsTupleTypeRef().GetElements();
+ YT_VERIFY(i < elements.size());
+ return TComplexTypeFieldDescriptor(Descriptor_ + Format(".<tuple-element-%v>", i), elements[i]);
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::VariantTupleElement(size_t i) const
+{
+ const auto& elements = Type_->AsVariantTupleTypeRef().GetElements();
+ YT_VERIFY(i < elements.size());
+ return TComplexTypeFieldDescriptor(Descriptor_ + Format(".<variant-element-%v>", i), elements[i]);
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::VariantStructField(size_t i) const
+{
+ const auto& fields = Type_->AsVariantStructTypeRef().GetFields();
+ YT_VERIFY(i < fields.size());
+ const auto& field = fields[i];
+ return TComplexTypeFieldDescriptor(Descriptor_ + "." + field.Name, field.Type);
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::DictKey() const
+{
+ return TComplexTypeFieldDescriptor(Descriptor_ + ".<key>", Type_->AsDictTypeRef().GetKey());
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::DictValue() const
+{
+ return TComplexTypeFieldDescriptor(Descriptor_ + ".<value>", Type_->AsDictTypeRef().GetValue());
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::TaggedElement() const
+{
+ return TComplexTypeFieldDescriptor(Descriptor_ + ".<tagged-element>", Type_->AsTaggedTypeRef().GetElement());
+}
+
+TComplexTypeFieldDescriptor TComplexTypeFieldDescriptor::Detag() const
+{
+ return TComplexTypeFieldDescriptor(Descriptor_, DetagLogicalType(Type_));
+}
+
+const TString& TComplexTypeFieldDescriptor::GetDescription() const
+{
+ return Descriptor_;
+}
+
+const TLogicalTypePtr& TComplexTypeFieldDescriptor::GetType() const
+{
+ return Type_;
+}
+
+static std::pair<std::vector<TStructField>, bool> DetagFields(const std::vector<TStructField>& fields)
+{
+ bool changed = false;
+ std::vector<TStructField> result;
+ for (const auto& field : fields) {
+ result.emplace_back();
+ result.back().Name = field.Name;
+ result.back().Type = DetagLogicalType(field.Type);
+ if (result.back().Type.Get() != field.Type.Get()) {
+ changed = true;
+ }
+ }
+ return std::pair(result, changed);
+}
+
+static std::pair<std::vector<TLogicalTypePtr>, bool> DetagElements(const std::vector<TLogicalTypePtr>& elements)
+{
+ std::vector<TLogicalTypePtr> result;
+ bool changed = false;
+ for (const auto& element : elements) {
+ result.emplace_back(DetagLogicalType(element));
+ if (result.back().Get() != element.Get()) {
+ changed = true;
+ }
+ }
+ return std::pair(result, changed);
+}
+
+TLogicalTypePtr DetagLogicalType(const TLogicalTypePtr& type)
+{
+ switch (type->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ case ELogicalMetatype::Decimal:
+ return type;
+ case ELogicalMetatype::Optional: {
+ const auto& element = type->AsOptionalTypeRef().GetElement();
+ auto detaggedElement = DetagLogicalType(element);
+ if (element.Get() != detaggedElement.Get()) {
+ return OptionalLogicalType(detaggedElement);
+ } else {
+ return type;
+ }
+ }
+ case ELogicalMetatype::List: {
+ const auto& element = type->AsListTypeRef().GetElement();
+ auto detaggedElement = DetagLogicalType(element);
+ if (element.Get() != detaggedElement.Get()) {
+ return ListLogicalType(detaggedElement);
+ } else {
+ return type;
+ }
+ }
+ case ELogicalMetatype::Struct: {
+ const auto [fields, changed] = DetagFields(type->AsStructTypeRef().GetFields());
+ if (changed) {
+ return StructLogicalType(fields);
+ } else {
+ return type;
+ }
+ }
+ case ELogicalMetatype::Tuple: {
+ const auto [elements, changed] = DetagElements(type->AsTupleTypeRef().GetElements());
+ if (changed) {
+ return TupleLogicalType(elements);
+ } else {
+ return type;
+ }
+ }
+ case ELogicalMetatype::VariantStruct: {
+ const auto [fields, changed] = DetagFields(type->AsVariantStructTypeRef().GetFields());
+ if (changed) {
+ return VariantStructLogicalType(fields);
+ } else {
+ return type;
+ }
+ }
+ case ELogicalMetatype::VariantTuple: {
+ const auto [elements, changed] = DetagElements(type->AsVariantTupleTypeRef().GetElements());
+ if (changed) {
+ return VariantTupleLogicalType(elements);
+ } else {
+ return type;
+ }
+ }
+ case ELogicalMetatype::Dict: {
+ const auto& dictType = type->AsDictTypeRef();
+ const auto& key = dictType.GetKey();
+ const auto& value = dictType.GetValue();
+ const auto detaggedKey = DetagLogicalType(key);
+ const auto detaggedValue = DetagLogicalType(value);
+ if (detaggedKey.Get() != key.Get() || detaggedValue.Get() != value.Get()) {
+ return DictLogicalType(
+ detaggedKey,
+ detaggedValue);
+ } else {
+ return type;
+ }
+ }
+ case ELogicalMetatype::Tagged:
+ return DetagLogicalType(type->AsTaggedTypeRef().GetElement());
+ }
+ YT_ABORT();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStructLogicalTypeBase::TStructLogicalTypeBase(ELogicalMetatype metatype, std::vector<TStructField> fields)
+ : TLogicalType(metatype)
+ , Fields_(std::move(fields))
+{ }
+
+size_t TStructLogicalTypeBase::GetMemoryUsage() const
+{
+ size_t result = sizeof(*this);
+ result += sizeof(TStructField) * Fields_.size();
+ for (const auto& field : Fields_) {
+ result += field.Type->GetMemoryUsage();
+ }
+ return result;
+}
+
+int TStructLogicalTypeBase::GetTypeComplexity() const
+{
+ int result = 1;
+ for (const auto& field : Fields_) {
+ result += field.Type->GetTypeComplexity();
+ }
+ return result;
+}
+
+void TStructLogicalTypeBase::ValidateNode(const TWalkContext& /*context*/) const
+{
+ THashSet<TStringBuf> usedNames;
+ for (size_t i = 0; i < Fields_.size(); ++i) {
+ const auto& field = Fields_[i];
+ if (field.Name.empty()) {
+ THROW_ERROR_EXCEPTION("Name of struct field #%v is empty",
+ i);
+ }
+ if (usedNames.contains(field.Name)) {
+ THROW_ERROR_EXCEPTION("Struct field name %Qv is used twice",
+ field.Name);
+ }
+ usedNames.emplace(field.Name);
+ if (field.Name.size() > MaxColumnNameLength) {
+ THROW_ERROR_EXCEPTION("Name of struct field #%v exceeds limit: %v > %v",
+ i,
+ field.Name.size(),
+ MaxColumnNameLength);
+ }
+ if (!IsUtf(field.Name)) {
+ THROW_ERROR_EXCEPTION("Name of struct field #%v is not valid utf8",
+ i);
+ }
+ }
+}
+
+bool TStructLogicalTypeBase::IsNullable() const
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTupleLogicalTypeBase::TTupleLogicalTypeBase(ELogicalMetatype metatype, std::vector<NYT::NTableClient::TLogicalTypePtr> elements)
+ : TLogicalType(metatype)
+ , Elements_(std::move(elements))
+{ }
+
+size_t TTupleLogicalTypeBase::GetMemoryUsage() const
+{
+ size_t result = sizeof(*this);
+ result += sizeof(TLogicalTypePtr) * Elements_.size();
+ for (const auto& element : Elements_) {
+ result += element->GetMemoryUsage();
+ }
+ return result;
+}
+
+int TTupleLogicalTypeBase::GetTypeComplexity() const
+{
+ int result = 1;
+ for (const auto& element : Elements_) {
+ result += element->GetTypeComplexity();
+ }
+ return result;
+}
+
+void TTupleLogicalTypeBase::ValidateNode(const TWalkContext& /*context*/) const
+{ }
+
+bool TTupleLogicalTypeBase::IsNullable() const
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStructLogicalType::TStructLogicalType(std::vector<NYT::NTableClient::TStructField> fields)
+ : TStructLogicalTypeBase(ELogicalMetatype::Struct, std::move(fields))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTupleLogicalType::TTupleLogicalType(std::vector<NYT::NTableClient::TLogicalTypePtr> elements)
+ : TTupleLogicalTypeBase(ELogicalMetatype::Tuple, std::move(elements))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TVariantStructLogicalType::TVariantStructLogicalType(std::vector<NYT::NTableClient::TStructField> fields)
+ : TStructLogicalTypeBase(ELogicalMetatype::VariantStruct, std::move(fields))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TVariantTupleLogicalType::TVariantTupleLogicalType(std::vector<NYT::NTableClient::TLogicalTypePtr> elements)
+ : TTupleLogicalTypeBase(ELogicalMetatype::VariantTuple, std::move(elements))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDictLogicalType::TDictLogicalType(TLogicalTypePtr key, TLogicalTypePtr value)
+ : TLogicalType(ELogicalMetatype::Dict)
+ , Key_(std::move(key))
+ , Value_(std::move(value))
+{ }
+
+size_t TDictLogicalType::GetMemoryUsage() const
+{
+ return sizeof(*this) + Key_->GetMemoryUsage() + Value_->GetMemoryUsage();
+}
+
+int TDictLogicalType::GetTypeComplexity() const
+{
+ return 1 + Key_->GetTypeComplexity() + Value_->GetTypeComplexity();
+}
+
+void TDictLogicalType::ValidateNode(const TWalkContext&) const
+{
+ TComplexTypeFieldDescriptor descriptor("<dict-key>", GetKey());
+ Walk(descriptor, [](const TWalkContext&, const TComplexTypeFieldDescriptor& descriptor) {
+ const auto& logicalType = descriptor.GetType();
+
+ // NB. We intentionally list all metatypes and simple types here.
+ // We want careful decision if type can be used as dictionary key each time new type is added.
+ // Compiler will warn you (with error) if some enum is not handled.
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ switch (auto simpleType = logicalType->AsSimpleTypeRef().GetElement()) {
+ case ESimpleLogicalValueType::Any:
+ case ESimpleLogicalValueType::Json:
+ THROW_ERROR_EXCEPTION("%Qv is of type %Qv that is not allowed in dict key",
+ descriptor.GetDescription(),
+ simpleType);
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ case ESimpleLogicalValueType::Boolean:
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+ case ESimpleLogicalValueType::Float:
+ case ESimpleLogicalValueType::Double:
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Utf8:
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ case ESimpleLogicalValueType::Interval:
+ case ESimpleLogicalValueType::Uuid:
+ return;
+ }
+ YT_ABORT();
+ case ELogicalMetatype::Optional:
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantStruct:
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::VariantTuple:
+ case ELogicalMetatype::List:
+ case ELogicalMetatype::Dict:
+ case ELogicalMetatype::Tagged:
+ case ELogicalMetatype::Decimal:
+ return;
+ }
+ YT_ABORT();
+ });
+}
+
+bool TDictLogicalType::IsNullable() const
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTaggedLogicalType::TTaggedLogicalType(TString tag, NYT::NTableClient::TLogicalTypePtr element)
+ : TLogicalType(ELogicalMetatype::Tagged)
+ , Tag_(std::move(tag))
+ , Element_(std::move(element))
+{ }
+
+size_t TTaggedLogicalType::GetMemoryUsage() const
+{
+ return sizeof(*this) + GetElement()->GetMemoryUsage();
+}
+
+int TTaggedLogicalType::GetTypeComplexity() const
+{
+ return 1 + GetElement()->GetTypeComplexity();
+}
+
+void TTaggedLogicalType::ValidateNode(const TWalkContext&) const
+{
+ if (Tag_.empty()) {
+ THROW_ERROR_EXCEPTION("Tag is empty");
+ }
+
+ if (Tag_.size() > MaxColumnNameLength) {
+ THROW_ERROR_EXCEPTION("Tag is too big");
+ }
+
+ if (!IsUtf(Tag_)) {
+ THROW_ERROR_EXCEPTION("Tag is not valid utf8");
+ }
+}
+
+bool TTaggedLogicalType::IsNullable() const
+{
+ return GetElement()->IsNullable();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTypeV3Info
+{
+ ESimpleLogicalValueType V1Type;
+ EValueType WireType;
+ bool Required;
+ bool IsPureV1Type;
+};
+
+static TTypeV3Info GetTypeV3Info(const TLogicalTypePtr& logicalType)
+{
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple: {
+ auto element = logicalType->AsSimpleTypeRef().GetElement();
+ return {element, GetPhysicalType(element), !logicalType->IsNullable(), true};
+ }
+ case ELogicalMetatype::Decimal:
+ return {ESimpleLogicalValueType::String, EValueType::String, true, false};
+ case ELogicalMetatype::Optional: {
+ const auto& element = logicalType->AsOptionalTypeRef().GetElement();
+ if (element->IsNullable()) {
+ return {ESimpleLogicalValueType::Any, EValueType::Composite, false, false};
+ } else {
+ auto elementInfo = GetTypeV3Info(element);
+ elementInfo.Required = false;
+ return elementInfo;
+ }
+ }
+ case ELogicalMetatype::Tagged:
+ return GetTypeV3Info(logicalType->AsTaggedTypeRef().GetElement());
+ case ELogicalMetatype::List:
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::VariantStruct:
+ case ELogicalMetatype::VariantTuple:
+ case ELogicalMetatype::Dict:
+ return {ESimpleLogicalValueType::Any, EValueType::Composite, true, false};
+ }
+ YT_ABORT();
+}
+
+std::pair<ESimpleLogicalValueType, bool> CastToV1Type(const TLogicalTypePtr& logicalType)
+{
+ auto info = GetTypeV3Info(logicalType);
+ return {info.V1Type, info.Required};
+}
+
+bool IsV1Type(const TLogicalTypePtr& logicalType)
+{
+ return GetTypeV3Info(logicalType).IsPureV1Type;
+}
+
+EValueType GetWireType(const TLogicalTypePtr& logicalType)
+{
+ return GetTypeV3Info(logicalType).WireType;
+}
+
+bool IsV3Composite(const TLogicalTypePtr& logicalType)
+{
+ return GetWireType(logicalType) == EValueType::Composite;
+}
+
+TLogicalTypePtr DenullifyLogicalType(const TLogicalTypePtr& type)
+{
+ auto detagged = DetagLogicalType(type);
+ if (detagged->GetMetatype() == ELogicalMetatype::Optional) {
+ const auto& optional = detagged->AsOptionalTypeRef();
+ if (!optional.IsElementNullable()) {
+ return optional.GetElement();
+ }
+ }
+ return detagged;
+}
+
+bool operator != (const TLogicalType& lhs, const TLogicalType& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool operator == (const std::vector<TLogicalTypePtr>& lhs, const std::vector<TLogicalTypePtr>& rhs)
+{
+ if (lhs.size() != rhs.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < lhs.size(); ++i) {
+ if (*lhs[i] != *rhs[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool operator == (const TLogicalType& lhs, const TLogicalType& rhs)
+{
+ if (&lhs == &rhs) {
+ return true;
+ }
+
+ if (lhs.GetMetatype() != rhs.GetMetatype()) {
+ return false;
+ }
+
+ switch (lhs.GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return lhs.AsSimpleTypeRef().GetElement() == rhs.AsSimpleTypeRef().GetElement();
+ case ELogicalMetatype::Optional:
+ return *lhs.AsOptionalTypeRef().GetElement() == *rhs.AsOptionalTypeRef().GetElement();
+ case ELogicalMetatype::List:
+ return *lhs.AsListTypeRef().GetElement() == *rhs.AsListTypeRef().GetElement();
+ case ELogicalMetatype::Struct:
+ return lhs.AsStructTypeRef().GetFields() == rhs.AsStructTypeRef().GetFields();
+ case ELogicalMetatype::Tuple:
+ return lhs.AsTupleTypeRef().GetElements() == rhs.AsTupleTypeRef().GetElements();
+ case ELogicalMetatype::VariantStruct:
+ return lhs.AsVariantStructTypeRef().GetFields() == rhs.AsVariantStructTypeRef().GetFields();
+ case ELogicalMetatype::VariantTuple:
+ return lhs.AsVariantTupleTypeRef().GetElements() == rhs.AsVariantTupleTypeRef().GetElements();
+ case ELogicalMetatype::Dict:
+ return *lhs.AsDictTypeRef().GetKey() == *rhs.AsDictTypeRef().GetKey() &&
+ *lhs.AsDictTypeRef().GetValue() == *rhs.AsDictTypeRef().GetValue();
+ case ELogicalMetatype::Tagged:
+ return lhs.AsTaggedTypeRef().GetTag() == rhs.AsTaggedTypeRef().GetTag() &&
+ *lhs.AsTaggedTypeRef().GetElement() == *rhs.AsTaggedTypeRef().GetElement();
+ case ELogicalMetatype::Decimal:
+ return lhs.AsDecimalTypeRef().GetPrecision() == rhs.AsDecimalTypeRef().GetPrecision() &&
+ lhs.AsDecimalTypeRef().GetScale() == rhs.AsDecimalTypeRef().GetScale();
+ }
+ YT_ABORT();
+}
+
+void ValidateLogicalType(const TComplexTypeFieldDescriptor& rootDescriptor, std::optional<int> depthLimit)
+{
+ Walk(rootDescriptor, [&] (const TWalkContext& context, const TComplexTypeFieldDescriptor& descriptor) {
+ try {
+ descriptor.GetType()->ValidateNode(context);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("%Qv has bad type",
+ descriptor.GetDescription())
+ << ex;
+ }
+ if (depthLimit && std::ssize(context.Stack) > *depthLimit) {
+ THROW_ERROR_EXCEPTION("%Qv exceeds type depth limit",
+ descriptor.GetDescription())
+ << TErrorAttribute("limit", *depthLimit);
+ }
+ });
+}
+
+void ToProto(NProto::TLogicalType* protoLogicalType, const TLogicalTypePtr& logicalType)
+{
+ switch (logicalType->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ protoLogicalType->set_simple(static_cast<int>(logicalType->AsSimpleTypeRef().GetElement()));
+ return;
+ case ELogicalMetatype::Decimal:
+ protoLogicalType->mutable_decimal()->set_precision(logicalType->AsDecimalTypeRef().GetPrecision());
+ protoLogicalType->mutable_decimal()->set_scale(logicalType->AsDecimalTypeRef().GetScale());
+ return;
+ case ELogicalMetatype::Optional:
+ ToProto(protoLogicalType->mutable_optional(), logicalType->AsOptionalTypeRef().GetElement());
+ return;
+ case ELogicalMetatype::List:
+ ToProto(protoLogicalType->mutable_list(), logicalType->AsListTypeRef().GetElement());
+ return;
+ case ELogicalMetatype::Struct: {
+ auto protoStruct = protoLogicalType->mutable_struct_();
+ for (const auto& structField : logicalType->AsStructTypeRef().GetFields()) {
+ auto protoStructField = protoStruct->add_fields();
+ protoStructField->set_name(structField.Name);
+ ToProto(protoStructField->mutable_type(), structField.Type);
+ }
+ return;
+ }
+ case ELogicalMetatype::Tuple: {
+ auto protoTuple = protoLogicalType->mutable_tuple();
+ for (const auto& element : logicalType->AsTupleTypeRef().GetElements()) {
+ auto protoElement = protoTuple->add_elements();
+ ToProto(protoElement, element);
+ }
+ return;
+ }
+ case ELogicalMetatype::VariantStruct: {
+ auto protoVariantStruct = protoLogicalType->mutable_variant_struct();
+ for (const auto& field : logicalType->AsVariantStructTypeRef().GetFields()) {
+ auto protoField = protoVariantStruct->add_fields();
+ protoField->set_name(field.Name);
+ ToProto(protoField->mutable_type(), field.Type);
+ }
+ return;
+ }
+ case ELogicalMetatype::VariantTuple: {
+ auto protoVariantTuple = protoLogicalType->mutable_variant_tuple();
+ for (const auto& element : logicalType->AsVariantTupleTypeRef().GetElements()) {
+ auto protoElement = protoVariantTuple->add_elements();
+ ToProto(protoElement, element);
+ }
+ return;
+ }
+ case ELogicalMetatype::Dict: {
+ auto protoDict = protoLogicalType->mutable_dict();
+ const auto& dictLogicalType = logicalType->AsDictTypeRef();
+ ToProto(protoDict->mutable_key(), dictLogicalType.GetKey());
+ ToProto(protoDict->mutable_value(), dictLogicalType.GetValue());
+ return;
+ }
+ case ELogicalMetatype::Tagged: {
+ auto protoTagged = protoLogicalType->mutable_tagged();
+ const auto& taggedLogicalType = logicalType->AsTaggedTypeRef();
+ protoTagged->set_tag(taggedLogicalType.GetTag());
+ ToProto(protoTagged->mutable_element(), taggedLogicalType.GetElement());
+ return;
+ }
+ }
+ YT_ABORT();
+}
+
+void FromProto(TLogicalTypePtr* logicalType, const NProto::TLogicalType& protoLogicalType)
+{
+ switch (protoLogicalType.type_case()) {
+ case NProto::TLogicalType::TypeCase::kSimple:
+ *logicalType = SimpleLogicalType(CheckedEnumCast<ESimpleLogicalValueType>(protoLogicalType.simple()));
+ return;
+ case NProto::TLogicalType::TypeCase::kDecimal:
+ *logicalType = DecimalLogicalType(protoLogicalType.decimal().precision(), protoLogicalType.decimal().scale());
+ return;
+ case NProto::TLogicalType::TypeCase::kOptional: {
+ TLogicalTypePtr element;
+ FromProto(&element, protoLogicalType.optional());
+ *logicalType = OptionalLogicalType(element);
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::kList: {
+ TLogicalTypePtr element;
+ FromProto(&element, protoLogicalType.list());
+ *logicalType = ListLogicalType(element);
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::kStruct: {
+ std::vector<TStructField> fields;
+ for (const auto& protoField : protoLogicalType.struct_().fields()) {
+ TLogicalTypePtr fieldType;
+ FromProto(&fieldType, protoField.type());
+ fields.emplace_back(TStructField{protoField.name(), std::move(fieldType)});
+ }
+ *logicalType = StructLogicalType(std::move(fields));
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::kTuple: {
+ std::vector<TLogicalTypePtr> elements;
+ for (const auto& protoField : protoLogicalType.tuple().elements()) {
+ elements.emplace_back();
+ FromProto(&elements.back(), protoField);
+ }
+ *logicalType = TupleLogicalType(std::move(elements));
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::kVariantTuple: {
+ std::vector<TLogicalTypePtr> elements;
+ for (const auto& protoElement : protoLogicalType.variant_tuple().elements()) {
+ elements.emplace_back();
+ FromProto(&elements.back(), protoElement);
+ }
+ *logicalType = VariantTupleLogicalType(std::move(elements));
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::kVariantStruct: {
+ std::vector<TStructField> fields;
+ for (const auto& protoField : protoLogicalType.variant_struct().fields()) {
+ TLogicalTypePtr fieldType;
+ FromProto(&fieldType, protoField.type());
+ fields.emplace_back(TStructField{protoField.name(), std::move(fieldType)});
+ }
+ *logicalType = VariantStructLogicalType(std::move(fields));
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::kDict: {
+ TLogicalTypePtr keyType;
+ TLogicalTypePtr valueType;
+ FromProto(&keyType, protoLogicalType.dict().key());
+ FromProto(&valueType, protoLogicalType.dict().value());
+ *logicalType = DictLogicalType(keyType, valueType);
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::kTagged: {
+ TLogicalTypePtr element;
+ FromProto(&element, protoLogicalType.tagged().element());
+ *logicalType = TaggedLogicalType(protoLogicalType.tagged().tag(), std::move(element));
+ return;
+ }
+ case NProto::TLogicalType::TypeCase::TYPE_NOT_SET:
+ THROW_ERROR_EXCEPTION("Cannot parse unknown logical type from proto");
+ }
+ YT_ABORT();
+}
+
+void Serialize(const TStructField& structElement, NYson::IYsonConsumer* consumer)
+{
+ NYTree::BuildYsonFluently(consumer).BeginMap()
+ .Item("name").Value(structElement.Name)
+ .Item("type").Value(structElement.Type)
+ .EndMap();
+}
+
+void Deserialize(TStructField& structElement, NYTree::INodePtr node)
+{
+ const auto& mapNode = node->AsMap();
+ structElement.Name = NYTree::ConvertTo<TString>(mapNode->GetChildOrThrow("name"));
+ structElement.Type = NYTree::ConvertTo<TLogicalTypePtr>(mapNode->GetChildOrThrow("type"));
+}
+
+// TODO(levysotsky): Get rid of this variable when we are sure type_v2 is dead for good.
+static constexpr bool UseTypeV3ForSerialization = true;
+
+void Serialize(const TLogicalTypePtr& logicalType, NYson::IYsonConsumer* consumer)
+{
+ if (UseTypeV3ForSerialization) {
+ Serialize(TTypeV3LogicalTypeWrapper{logicalType}, consumer);
+ return;
+ }
+
+ const auto metatype = logicalType->GetMetatype();
+ switch (metatype) {
+ case ELogicalMetatype::Simple:
+ NYTree::BuildYsonFluently(consumer)
+ .Value(logicalType->AsSimpleTypeRef().GetElement());
+ return;
+ case ELogicalMetatype::Decimal:
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("metatype").Value(metatype)
+ .Item("precision").Value(logicalType->AsDecimalTypeRef().GetPrecision())
+ .Item("scale").Value(logicalType->AsDecimalTypeRef().GetScale())
+ .EndMap();
+ return;
+ case ELogicalMetatype::Optional:
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("metatype").Value(metatype)
+ .Item("element").Value(logicalType->AsOptionalTypeRef().GetElement())
+ .EndMap();
+ return;
+ case ELogicalMetatype::List:
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("metatype").Value(metatype)
+ .Item("element").Value(logicalType->AsListTypeRef().GetElement())
+ .EndMap();
+ return;
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantStruct: {
+ const auto& fields =
+ metatype == ELogicalMetatype::Struct ?
+ logicalType->AsStructTypeRef().GetFields() :
+ logicalType->AsVariantStructTypeRef().GetFields();
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("metatype").Value(metatype)
+ .Item("fields").Value(fields)
+ .EndMap();
+ return;
+ }
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::VariantTuple: {
+ const auto& elements =
+ metatype == ELogicalMetatype::Tuple ?
+ logicalType->AsTupleTypeRef().GetElements() :
+ logicalType->AsVariantTupleTypeRef().GetElements();
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("metatype").Value(metatype)
+ .Item("elements").Value(elements)
+ .EndMap();
+ return;
+ }
+ case ELogicalMetatype::Dict: {
+ const auto& key = logicalType->AsDictTypeRef().GetKey();
+ const auto& value = logicalType->AsDictTypeRef().GetValue();
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("metatype").Value(metatype)
+ .Item("key").Value(key)
+ .Item("value").Value(value)
+ .EndMap();
+ return;
+ }
+ case ELogicalMetatype::Tagged: {
+ const auto& element = logicalType->AsTaggedTypeRef().GetElement();
+ const auto& tag = logicalType->AsTaggedTypeRef().GetTag();
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("metatype").Value(metatype)
+ .Item("tag").Value(tag)
+ .Item("element").Value(element)
+ .EndMap();
+ return;
+ }
+ }
+ YT_ABORT();
+}
+
+void Deserialize(TLogicalTypePtr& logicalType, NYTree::INodePtr node)
+{
+ if (UseTypeV3ForSerialization) {
+ TTypeV3LogicalTypeWrapper wrapper;
+ Deserialize(wrapper, node);
+ logicalType = wrapper.LogicalType;
+ return;
+ }
+
+ if (node->GetType() == NYTree::ENodeType::String) {
+ auto simpleLogicalType = NYTree::ConvertTo<ESimpleLogicalValueType>(node);
+ logicalType = SimpleLogicalType(simpleLogicalType);
+ return;
+ }
+ if (node->GetType() != NYTree::ENodeType::Map) {
+ THROW_ERROR_EXCEPTION("Error parsing logical type: expected %Qlv or %Qlv, actual %Qlv",
+ NYTree::ENodeType::Map,
+ NYTree::ENodeType::String,
+ node->GetType());
+ }
+ auto mapNode = node->AsMap();
+
+ ELogicalMetatype metatype;
+ {
+ auto metatypeNode = mapNode->GetChildOrThrow("metatype");
+ metatype = NYTree::ConvertTo<ELogicalMetatype>(metatypeNode);
+ }
+ switch (metatype) {
+ case ELogicalMetatype::Simple: {
+ THROW_ERROR_EXCEPTION("Error parsing logical type: cannot parse simple type from %Qv",
+ NYTree::ENodeType::Map);
+ }
+ case ELogicalMetatype::Decimal: {
+ auto precision = mapNode->GetChildValueOrThrow<i64>("precision");
+ auto scale = mapNode->GetChildValueOrThrow<i64>("scale");
+ logicalType = DecimalLogicalType(precision, scale);
+ return;
+ }
+ case ELogicalMetatype::Optional: {
+ auto elementNode = mapNode->GetChildOrThrow("element");
+ auto element = NYTree::ConvertTo<TLogicalTypePtr>(elementNode);
+ logicalType = OptionalLogicalType(std::move(element));
+ return;
+ }
+ case ELogicalMetatype::List: {
+ auto elementNode = mapNode->GetChildOrThrow("element");
+ auto element = NYTree::ConvertTo<TLogicalTypePtr>(elementNode);
+ logicalType = ListLogicalType(std::move(element));
+ return;
+ }
+ case ELogicalMetatype::Struct: {
+ auto fieldsNode = mapNode->GetChildOrThrow("fields");
+ auto fields = NYTree::ConvertTo<std::vector<TStructField>>(fieldsNode);
+ logicalType = StructLogicalType(std::move(fields));
+ return;
+ }
+ case ELogicalMetatype::Tuple: {
+ auto elementsNode = mapNode->GetChildOrThrow("elements");
+ auto elements = NYTree::ConvertTo<std::vector<TLogicalTypePtr>>(elementsNode);
+ logicalType = TupleLogicalType(std::move(elements));
+ return;
+ }
+ case ELogicalMetatype::VariantStruct: {
+ auto fieldsNode = mapNode->GetChildOrThrow("fields");
+ auto fields = NYTree::ConvertTo<std::vector<TStructField>>(fieldsNode);
+ logicalType = VariantStructLogicalType(std::move(fields));
+ return;
+ }
+ case ELogicalMetatype::VariantTuple: {
+ auto elementsNode = mapNode->GetChildOrThrow("elements");
+ auto elements = NYTree::ConvertTo<std::vector<TLogicalTypePtr>>(elementsNode);
+ logicalType = VariantTupleLogicalType(std::move(elements));
+ return;
+ }
+ case ELogicalMetatype::Dict: {
+ auto keyNode = mapNode->GetChildOrThrow("key");
+ auto key = NYTree::ConvertTo<TLogicalTypePtr>(keyNode);
+ auto valueNode = mapNode->GetChildOrThrow("value");
+ auto value = NYTree::ConvertTo<TLogicalTypePtr>(valueNode);
+ logicalType = DictLogicalType(std::move(key), std::move(value));
+ return;
+ }
+ case ELogicalMetatype::Tagged: {
+ auto tagNode = mapNode->GetChildOrThrow("tag");
+ auto tag = NYTree::ConvertTo<TString>(tagNode);
+ auto elementNode = mapNode->GetChildOrThrow("element");
+ auto element = NYTree::ConvertTo<TLogicalTypePtr>(elementNode);
+ logicalType = TaggedLogicalType(std::move(tag), std::move(element));
+ return;
+ }
+ }
+ YT_ABORT();
+}
+
+bool IsComparable(const TLogicalTypePtr& type)
+{
+ switch (type->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ switch (type->AsSimpleTypeRef().GetElement()) {
+ case ESimpleLogicalValueType::Int64:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int8:
+
+ case ESimpleLogicalValueType::Uint64:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint8:
+
+ case ESimpleLogicalValueType::Boolean:
+
+ case ESimpleLogicalValueType::Double:
+ case ESimpleLogicalValueType::Float:
+
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Utf8:
+
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ case ESimpleLogicalValueType::Interval:
+
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+
+ case ESimpleLogicalValueType::Uuid:
+ return true;
+
+ case ESimpleLogicalValueType::Any:
+ case ESimpleLogicalValueType::Json:
+ return false;
+ }
+ Y_FAIL();
+ case ELogicalMetatype::Decimal:
+ return true;
+ case ELogicalMetatype::Optional:
+ case ELogicalMetatype::List:
+ case ELogicalMetatype::Tagged:
+ return IsComparable(type->GetElement());
+
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::VariantTuple: {
+ for (const auto& element : type->GetElements()) {
+ if (!IsComparable(element)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantStruct:
+ case ELogicalMetatype::Dict:
+ return false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TV3Variant
+{ };
+
+using TV3TypeName = std::variant<ESimpleLogicalValueType, ELogicalMetatype, TV3Variant>;
+
+static const std::pair<ESimpleLogicalValueType, TString> V3SimpleLogicalValueTypeEncoding[] =
+{
+ {ESimpleLogicalValueType::Null, "null"},
+ {ESimpleLogicalValueType::Int64, "int64"},
+ {ESimpleLogicalValueType::Uint64, "uint64"},
+ {ESimpleLogicalValueType::Double, "double"},
+ {ESimpleLogicalValueType::Float, "float"},
+ {ESimpleLogicalValueType::Boolean, "bool"}, // NB. diff
+ {ESimpleLogicalValueType::String, "string"},
+ {ESimpleLogicalValueType::Any, "yson"}, // NB. diff
+ {ESimpleLogicalValueType::Json, "json"},
+ {ESimpleLogicalValueType::Int8, "int8"},
+ {ESimpleLogicalValueType::Uint8, "uint8"},
+
+ {ESimpleLogicalValueType::Int16, "int16"},
+ {ESimpleLogicalValueType::Uint16, "uint16"},
+
+ {ESimpleLogicalValueType::Int32, "int32"},
+ {ESimpleLogicalValueType::Uint32, "uint32"},
+
+ {ESimpleLogicalValueType::Utf8, "utf8"},
+
+ {ESimpleLogicalValueType::Date, "date"},
+ {ESimpleLogicalValueType::Datetime, "datetime"},
+ {ESimpleLogicalValueType::Timestamp, "timestamp"},
+ {ESimpleLogicalValueType::Interval, "interval"},
+
+ {ESimpleLogicalValueType::Void, "void"},
+
+ {ESimpleLogicalValueType::Uuid, "uuid"},
+};
+static_assert(std::size(V3SimpleLogicalValueTypeEncoding) == TEnumTraits<ESimpleLogicalValueType>::GetDomainSize());
+
+std::pair<ELogicalMetatype, TString> V3LogicalMetatypeEncoding[] =
+{
+ // NB. following metatypes are not included:
+ // - ELogicalMetatype::Simple
+ // - ELogicalMetatype::VariantStruct
+ // - ELogicalMetatype::VariantTuple
+ {ELogicalMetatype::Optional, "optional"},
+ {ELogicalMetatype::List, "list"},
+ {ELogicalMetatype::Struct, "struct"},
+ {ELogicalMetatype::Tuple, "tuple"},
+ {ELogicalMetatype::Dict, "dict"},
+ {ELogicalMetatype::Tagged, "tagged"},
+ {ELogicalMetatype::Decimal, "decimal"},
+};
+
+// NB ELogicalMetatype::{Simple,VariantStruct,VariantTuple} are not encoded therefore we have `-3` in static_assert below.
+static_assert(std::size(V3LogicalMetatypeEncoding) == TEnumTraits<ELogicalMetatype>::GetDomainSize() - 3);
+
+TV3TypeName FromTypeV3(TStringBuf stringBuf)
+{
+ static const auto map = [] {
+ THashMap<TStringBuf, TV3TypeName> res;
+ for (const auto& [value, string] : V3SimpleLogicalValueTypeEncoding) {
+ res[string] = value;
+ }
+ for (const auto& [value, string] : V3LogicalMetatypeEncoding) {
+ res[string] = value;
+ }
+ res["variant"] = TV3Variant{};
+ return res;
+ }();
+
+ auto it = map.find(stringBuf);
+ if (it == map.end()) {
+ THROW_ERROR_EXCEPTION("%Qv is not valid type_v3 simple type",
+ stringBuf);
+ }
+ return it->second;
+}
+
+TStringBuf ToTypeV3(ESimpleLogicalValueType value)
+{
+ for (const auto& [type, string] : V3SimpleLogicalValueTypeEncoding) {
+ if (type == value) {
+ return string;
+ }
+ }
+ YT_ABORT();
+}
+
+TStringBuf ToTypeV3(ELogicalMetatype value)
+{
+ YT_VERIFY(value != ELogicalMetatype::Simple);
+ for (const auto& [type, string] : V3LogicalMetatypeEncoding) {
+ if (type == value) {
+ return string;
+ }
+ }
+ YT_VERIFY(value == ELogicalMetatype::VariantStruct || value == ELogicalMetatype::VariantTuple);
+ return "variant";
+}
+
+struct TTypeV3MemberWrapper
+{
+ TStructField Member;
+};
+
+void Serialize(const TTypeV3MemberWrapper& wrapper, NYson::IYsonConsumer* consumer)
+{
+ NYTree::BuildYsonFluently(consumer).BeginMap()
+ .Item("type").Value(TTypeV3LogicalTypeWrapper{wrapper.Member.Type})
+ .Item("name").Value(wrapper.Member.Name)
+ .EndMap();
+}
+
+void Deserialize(TTypeV3MemberWrapper& wrapper, NYTree::INodePtr node)
+{
+ const auto& mapNode = node->AsMap();
+ auto wrappedType = NYTree::ConvertTo<TTypeV3LogicalTypeWrapper>(mapNode->GetChildOrThrow("type"));
+ wrapper.Member.Type = wrappedType.LogicalType;
+ wrapper.Member.Name = NYTree::ConvertTo<TString>(mapNode->GetChildOrThrow("name"));
+}
+
+struct TTypeV3ElementWrapper
+{
+ TLogicalTypePtr Element;
+};
+
+void Serialize(const TTypeV3ElementWrapper& wrapper, NYson::IYsonConsumer* consumer)
+{
+ NYTree::BuildYsonFluently(consumer).BeginMap()
+ .Item("type").Value(TTypeV3LogicalTypeWrapper{wrapper.Element})
+ .EndMap();
+}
+
+void Deserialize(TTypeV3ElementWrapper& wrapper, NYTree::INodePtr node)
+{
+ const auto& mapNode = node->AsMap();
+ auto wrappedElement = NYTree::ConvertTo<TTypeV3LogicalTypeWrapper>(mapNode->GetChildOrThrow("type"));
+ wrapper.Element = wrappedElement.LogicalType;
+}
+
+void Serialize(const TTypeV3LogicalTypeWrapper& wrapper, NYson::IYsonConsumer* consumer)
+{
+ using TWrapper = TTypeV3LogicalTypeWrapper;
+
+ const auto metatype = wrapper.LogicalType->GetMetatype();
+ switch (metatype) {
+ case ELogicalMetatype::Simple:
+ NYTree::BuildYsonFluently(consumer)
+ .Value(ToTypeV3(wrapper.LogicalType->AsSimpleTypeRef().GetElement()));
+ return;
+ case ELogicalMetatype::Decimal:
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("type_name").Value(ToTypeV3(metatype))
+ .Item("precision").Value(wrapper.LogicalType->AsDecimalTypeRef().GetPrecision())
+ .Item("scale").Value(wrapper.LogicalType->AsDecimalTypeRef().GetScale())
+ .EndMap();
+ return;
+ case ELogicalMetatype::Optional:
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("type_name").Value(ToTypeV3(metatype))
+ .Item("item").Value(TWrapper{wrapper.LogicalType->AsOptionalTypeRef().GetElement()})
+ .EndMap();
+ return;
+ case ELogicalMetatype::List:
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("type_name").Value(ToTypeV3(metatype))
+ .Item("item").Value(TWrapper{wrapper.LogicalType->AsListTypeRef().GetElement()})
+ .EndMap();
+ return;
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantStruct: {
+ const auto& fields = wrapper.LogicalType->GetFields();
+
+ std::vector<TTypeV3MemberWrapper> wrappedMembers;
+ wrappedMembers.reserve(fields.size());
+ for (const auto& f : fields) {
+ wrappedMembers.emplace_back(TTypeV3MemberWrapper{f});
+ }
+
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("type_name").Value(ToTypeV3(metatype))
+ .Item("members").Value(wrappedMembers)
+ .EndMap();
+ return;
+ }
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::VariantTuple: {
+ const auto& elements = wrapper.LogicalType->GetElements();
+
+ std::vector<TTypeV3ElementWrapper> wrappedElements;
+ wrappedElements.reserve(elements.size());
+ for (const auto& e : elements) {
+ wrappedElements.emplace_back(TTypeV3ElementWrapper{e});
+ }
+
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("type_name").Value(ToTypeV3(metatype))
+ .Item("elements").Value(wrappedElements)
+ .EndMap();
+ return;
+ }
+ case ELogicalMetatype::Dict: {
+ const auto& key = wrapper.LogicalType->AsDictTypeRef().GetKey();
+ const auto& value = wrapper.LogicalType->AsDictTypeRef().GetValue();
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("type_name").Value(ToTypeV3(metatype))
+ .Item("key").Value(TWrapper{key})
+ .Item("value").Value(TWrapper{value})
+ .EndMap();
+ return;
+ }
+ case ELogicalMetatype::Tagged: {
+ const auto& element = wrapper.LogicalType->AsTaggedTypeRef().GetElement();
+ const auto& tag = wrapper.LogicalType->AsTaggedTypeRef().GetTag();
+ NYTree::BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("type_name").Value(ToTypeV3(metatype))
+ .Item("tag").Value(tag)
+ .Item("item").Value(TWrapper{element})
+ .EndMap();
+ return;
+ }
+ }
+ YT_ABORT();
+}
+
+void Deserialize(TTypeV3LogicalTypeWrapper& wrapper, NYTree::INodePtr node)
+{
+ if (node->GetType() == NYTree::ENodeType::String) {
+ auto typeNameString = node->AsString()->GetValue();
+ auto typeName = FromTypeV3(typeNameString);
+ std::visit([&](const auto& arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, ESimpleLogicalValueType>) {
+ wrapper.LogicalType = SimpleLogicalType(arg);
+ } else {
+ static_assert(std::is_same_v<T, ELogicalMetatype> || std::is_same_v<T, TV3Variant>);
+ THROW_ERROR_EXCEPTION("Type %Qv must be represented by map, not a string",
+ typeNameString);
+ }
+ }, typeName);
+ return;
+ }
+ if (node->GetType() != NYTree::ENodeType::Map) {
+ THROW_ERROR_EXCEPTION("Error parsing logical type: expected %Qlv or %Qlv, actual %Qlv",
+ NYTree::ENodeType::Map,
+ NYTree::ENodeType::String,
+ node->GetType());
+ }
+
+ auto mapNode = node->AsMap();
+ auto typeNameString = mapNode->GetChildValueOrThrow<TString>("type_name");
+ auto typeName = FromTypeV3(typeNameString);
+ std::visit([&](const auto& typeName) {
+ using T = std::decay_t<decltype(typeName)>;
+ if constexpr (std::is_same_v<T, ESimpleLogicalValueType>) {
+ wrapper.LogicalType = SimpleLogicalType(typeName);
+ } else {
+ ELogicalMetatype type;
+ if constexpr (std::is_same_v<T, ELogicalMetatype>) {
+ type = typeName;
+ } else {
+ const bool hasMembers = static_cast<bool>(mapNode->FindChild("members"));
+ const bool hasElements = static_cast<bool>(mapNode->FindChild("elements"));
+ if (hasMembers && hasElements) {
+ THROW_ERROR_EXCEPTION("\"variant\" cannot have both children \"elements\" and \"members\"");
+ } else if (hasMembers) {
+ type = ELogicalMetatype::VariantStruct;
+ } else if (hasElements) {
+ type = ELogicalMetatype::VariantTuple;
+ } else {
+ THROW_ERROR_EXCEPTION("\"variant\" must have \"elements\" or \"members\" child");
+ }
+ }
+ switch (type) {
+ case ELogicalMetatype::Simple:
+ // NB. FromTypeV3 never returns this value.
+ YT_ABORT();
+ case ELogicalMetatype::Decimal: {
+ auto precision = mapNode->GetChildValueOrThrow<int>("precision");
+ auto scale = mapNode->GetChildValueOrThrow<int>("scale");
+ wrapper.LogicalType = DecimalLogicalType(precision, scale);
+ return;
+ }
+ case ELogicalMetatype::Optional: {
+ auto itemNode = mapNode->GetChildOrThrow("item");
+ auto item = NYTree::ConvertTo<TTypeV3LogicalTypeWrapper>(itemNode);
+ wrapper.LogicalType = OptionalLogicalType(std::move(item.LogicalType));
+ return;
+ }
+ case ELogicalMetatype::List: {
+ auto itemNode = mapNode->GetChildOrThrow("item");
+ auto wrappedItem = NYTree::ConvertTo<TTypeV3LogicalTypeWrapper>(itemNode);
+ wrapper.LogicalType = ListLogicalType(std::move(wrappedItem.LogicalType));
+ return;
+ }
+ case ELogicalMetatype::Struct:
+ case ELogicalMetatype::VariantStruct: {
+ auto membersNode = mapNode->GetChildOrThrow("members");
+ auto wrappedMembers = NYTree::ConvertTo<std::vector<TTypeV3MemberWrapper>>(membersNode);
+
+ std::vector<TStructField> members;
+ members.reserve(wrappedMembers.size());
+ for (auto& w : wrappedMembers) {
+ members.emplace_back(w.Member);
+ }
+
+ if (type == ELogicalMetatype::Struct) {
+ wrapper.LogicalType = StructLogicalType(std::move(members));
+ } else {
+ wrapper.LogicalType = VariantStructLogicalType(std::move(members));
+ }
+ return;
+ }
+ case ELogicalMetatype::Tuple:
+ case ELogicalMetatype::VariantTuple: {
+ auto elementsNode = mapNode->GetChildOrThrow("elements");
+ auto elementsV3 = NYTree::ConvertTo<std::vector<TTypeV3ElementWrapper>>(elementsNode);
+
+ std::vector<TLogicalTypePtr> elements;
+ elements.reserve(elementsV3.size());
+ for (auto& e : elementsV3) {
+ elements.emplace_back(std::move(e.Element));
+ }
+ if (type == ELogicalMetatype::Tuple) {
+ wrapper.LogicalType = TupleLogicalType(std::move(elements));
+ } else {
+ wrapper.LogicalType = VariantTupleLogicalType(std::move(elements));
+ }
+ return;
+ }
+ case ELogicalMetatype::Dict: {
+ auto keyNode = mapNode->GetChildOrThrow("key");
+ auto key = NYTree::ConvertTo<TTypeV3LogicalTypeWrapper>(keyNode);
+ auto valueNode = mapNode->GetChildOrThrow("value");
+ auto value = NYTree::ConvertTo<TTypeV3LogicalTypeWrapper>(valueNode);
+ wrapper.LogicalType = DictLogicalType(std::move(key.LogicalType), std::move(value.LogicalType));
+ return;
+ }
+ case ELogicalMetatype::Tagged: {
+ auto tagNode = mapNode->GetChildOrThrow("tag");
+ auto tag = NYTree::ConvertTo<TString>(tagNode);
+ auto elementNode = mapNode->GetChildOrThrow("item");
+ auto element = NYTree::ConvertTo<TTypeV3LogicalTypeWrapper>(elementNode);
+ wrapper.LogicalType = TaggedLogicalType(std::move(tag), std::move(element.LogicalType));
+ return;
+ }
+ }
+ YT_ABORT();
+ }
+ }, typeName);
+}
+
+void DeserializeV3Impl(TLogicalTypePtr& type, TYsonPullParserCursor* cursor, int depth)
+{
+ // Check depth early to avoid stack overflow.
+ if (depth > MaxSchemaDepth) {
+ THROW_ERROR_EXCEPTION("Logical type exceeds depth limit during parsing")
+ << TErrorAttribute("limit", MaxSchemaDepth);
+ }
+
+ if ((*cursor)->GetType() == EYsonItemType::StringValue) {
+ auto typeNameString = (*cursor)->UncheckedAsString();
+ auto typeName = FromTypeV3(typeNameString);
+ std::visit([&](const auto& arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, ESimpleLogicalValueType>) {
+ type = SimpleLogicalType(arg);
+ } else {
+ static_assert(std::is_same_v<T, ELogicalMetatype> || std::is_same_v<T, TV3Variant>);
+ THROW_ERROR_EXCEPTION("Type %Qv must be represented by map, not a string",
+ typeNameString);
+ }
+ }, typeName);
+ cursor->Next();
+ return;
+ }
+ if ((*cursor)->GetType() != EYsonItemType::BeginMap) {
+ THROW_ERROR_EXCEPTION("Error parsing logical type: expected %Qlv or %Qlv, actual %Qlv",
+ EYsonItemType::BeginMap,
+ EYsonItemType::BeginList,
+ (*cursor)->GetType());
+ }
+
+ std::optional<TV3TypeName> typeName;
+ std::optional<std::vector<TStructField>> members;
+ std::optional<std::vector<TLogicalTypePtr>> elements;
+ TLogicalTypePtr item, keyType, valueType;
+ std::optional<i64> precision, scale;
+ std::optional<TString> tag;
+
+ cursor->ParseMap([&] (TYsonPullParserCursor* cursor) {
+ EnsureYsonToken(TStringBuf("logical type attribute key"), *cursor, EYsonItemType::StringValue);
+ auto key = (*cursor)->UncheckedAsString();
+ if (key == TStringBuf("type_name")) {
+ cursor->Next();
+ EnsureYsonToken(TStringBuf("logical type name"), *cursor, EYsonItemType::StringValue);
+ typeName = FromTypeV3((*cursor)->UncheckedAsString());
+ cursor->Next();
+ } else if (key == TStringBuf("item")) {
+ cursor->Next();
+ DeserializeV3Impl(item, cursor, depth + 1);
+ } else if (key == TStringBuf("members")) {
+ cursor->Next();
+ members.emplace();
+ cursor->ParseList([&] (TYsonPullParserCursor* cursor) {
+ std::optional<TString> name;
+ TLogicalTypePtr type;
+ cursor->ParseMap([&] (TYsonPullParserCursor* cursor) {
+ EnsureYsonToken(TStringBuf("logical type member attribute key"), *cursor, EYsonItemType::StringValue);
+ auto key = (*cursor)->UncheckedAsString();
+ if (key == TStringBuf("name")) {
+ cursor->Next();
+ name = ExtractTo<TString>(cursor);
+ } else if (key == TStringBuf("type")) {
+ cursor->Next();
+ DeserializeV3Impl(type, cursor, depth + 1);
+ } else {
+ cursor->Next();
+ cursor->SkipComplexValue();
+ }
+ });
+ if (!name) {
+ THROW_ERROR_EXCEPTION("Name is required");
+ }
+ if (!type) {
+ THROW_ERROR_EXCEPTION("Type is required");
+ }
+ members->push_back({std::move(*name), std::move(type)});
+ });
+ } else if (key == TStringBuf("elements")) {
+ cursor->Next();
+ elements.emplace();
+ cursor->ParseList([&] (TYsonPullParserCursor* cursor) {
+ TLogicalTypePtr type;
+ cursor->ParseMap([&] (TYsonPullParserCursor* cursor) {
+ EnsureYsonToken(TStringBuf("logical type member attribute key"), *cursor, EYsonItemType::StringValue);
+ auto key = (*cursor)->UncheckedAsString();
+ if (key == TStringBuf("type")) {
+ cursor->Next();
+ DeserializeV3Impl(type, cursor, depth + 1);
+ } else {
+ cursor->Next();
+ cursor->SkipComplexValue();
+ }
+ });
+ if (!type) {
+ THROW_ERROR_EXCEPTION("Type is required");
+ }
+ elements->push_back(std::move(type));
+ });
+ } else if (key == TStringBuf("precision")) {
+ cursor->Next();
+ precision = ExtractTo<i64>(cursor);
+ } else if (key == TStringBuf("scale")) {
+ cursor->Next();
+ scale = ExtractTo<i64>(cursor);
+ } else if (key == TStringBuf("key")) {
+ cursor->Next();
+ DeserializeV3Impl(keyType, cursor, depth + 1);
+ } else if (key == TStringBuf("value")) {
+ cursor->Next();
+ DeserializeV3Impl(valueType, cursor, depth + 1);
+ } else if (key == TStringBuf("tag")) {
+ cursor->Next();
+ tag = ExtractTo<TString>(cursor);
+ } else {
+ cursor->Next();
+ cursor->SkipComplexValue();
+ }
+ });
+
+ if (!typeName) {
+ THROW_ERROR_EXCEPTION("\"type_name\" is required");
+ }
+
+ type = std::visit([&](const auto& typeName) {
+ using T = std::decay_t<decltype(typeName)>;
+ if constexpr (std::is_same_v<T, ESimpleLogicalValueType>) {
+ return SimpleLogicalType(typeName);
+ } else {
+ ELogicalMetatype type;
+ if constexpr (std::is_same_v<T, ELogicalMetatype>) {
+ type = typeName;
+ } else {
+ if (members && elements) {
+ THROW_ERROR_EXCEPTION("\"variant\" cannot have both children \"elements\" and \"members\"");
+ } else if (members) {
+ type = ELogicalMetatype::VariantStruct;
+ } else if (elements) {
+ type = ELogicalMetatype::VariantTuple;
+ } else {
+ THROW_ERROR_EXCEPTION("\"variant\" must have \"elements\" or \"members\" child");
+ }
+ }
+ auto ensureIsPresent = [&] (const char* fieldName, const auto& value) {
+ if (!value) {
+ THROW_ERROR_EXCEPTION("Field %Qv is required for logical type %Qlv",
+ fieldName,
+ type);
+ }
+ };
+ switch (type) {
+ case ELogicalMetatype::Simple:
+ // NB. FromTypeV3 never returns this value.
+ YT_ABORT();
+ case ELogicalMetatype::Decimal:
+ ensureIsPresent("precision", precision);
+ ensureIsPresent("scale", scale);
+ return DecimalLogicalType(*precision, *scale);
+ case ELogicalMetatype::Optional:
+ ensureIsPresent("item", item);
+ return OptionalLogicalType(std::move(item));
+ case ELogicalMetatype::List:
+ ensureIsPresent("item", item);
+ return ListLogicalType(std::move(item));
+ case ELogicalMetatype::Struct:
+ ensureIsPresent("members", members);
+ return StructLogicalType(std::move(*members));
+ case ELogicalMetatype::VariantStruct:
+ ensureIsPresent("members", members);
+ return VariantStructLogicalType(std::move(*members));
+ case ELogicalMetatype::Tuple:
+ ensureIsPresent("elements", elements);
+ return TupleLogicalType(std::move(*elements));
+ case ELogicalMetatype::VariantTuple:
+ ensureIsPresent("elements", elements);
+ return VariantTupleLogicalType(std::move(*elements));
+ case ELogicalMetatype::Dict:
+ ensureIsPresent("key", keyType);
+ ensureIsPresent("value", valueType);
+ return DictLogicalType(std::move(keyType), std::move(valueType));
+ case ELogicalMetatype::Tagged:
+ ensureIsPresent("tag", tag);
+ ensureIsPresent("item", item);
+ return TaggedLogicalType(std::move(*tag), std::move(item));
+ }
+ YT_ABORT();
+ }
+ }, *typeName);
+}
+
+void Deserialize(TTypeV3LogicalTypeWrapper& wrapper, TYsonPullParserCursor* cursor)
+{
+ DeserializeV3(wrapper.LogicalType, cursor);
+}
+
+void DeserializeV3(TLogicalTypePtr& type, TYsonPullParserCursor* cursor)
+{
+ DeserializeV3Impl(type, cursor, /*depth*/ 0);
+}
+
+void Deserialize(TLogicalTypePtr& type, NYson::TYsonPullParserCursor* cursor)
+{
+ YT_VERIFY(UseTypeV3ForSerialization);
+ DeserializeV3(type, cursor);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSimpleTypeStore
+{
+public:
+ TSimpleTypeStore()
+ {
+ for (auto simpleLogicalType : TEnumTraits<ESimpleLogicalValueType>::GetDomainValues()) {
+ auto logicalType = New<TSimpleLogicalType>(simpleLogicalType);
+ SimpleTypeMap_[simpleLogicalType] = logicalType;
+ OptionalTypeMap_[simpleLogicalType] = New<TOptionalLogicalType>(logicalType);
+ }
+ }
+
+ const TLogicalTypePtr& GetSimpleType(ESimpleLogicalValueType type)
+ {
+ return GetOrCrash(SimpleTypeMap_, type);
+ }
+
+ const TLogicalTypePtr& GetOptionalType(ESimpleLogicalValueType type)
+ {
+ return GetOrCrash(OptionalTypeMap_, type);
+ }
+
+private:
+ THashMap<ESimpleLogicalValueType, TLogicalTypePtr> SimpleTypeMap_;
+ THashMap<ESimpleLogicalValueType, TLogicalTypePtr> OptionalTypeMap_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLogicalTypePtr OptionalLogicalType(TLogicalTypePtr element)
+{
+ if (element->GetMetatype() == ELogicalMetatype::Simple) {
+ auto simpleLogicalType = element->AsSimpleTypeRef().GetElement();
+ if (element.Get() == Singleton<TSimpleTypeStore>()->GetSimpleType(simpleLogicalType).Get()) {
+ return Singleton<TSimpleTypeStore>()->GetOptionalType(simpleLogicalType);
+ }
+ }
+ return New<TOptionalLogicalType>(std::move(element));
+}
+
+TLogicalTypePtr MakeOptionalIfNot(TLogicalTypePtr element)
+{
+ if (element->GetMetatype() == ELogicalMetatype::Optional) {
+ return element;
+ }
+ return OptionalLogicalType(std::move(element));
+}
+
+TLogicalTypePtr SimpleLogicalType(ESimpleLogicalValueType element)
+{
+ return Singleton<TSimpleTypeStore>()->GetSimpleType(element);
+}
+
+TLogicalTypePtr DecimalLogicalType(int precision, int scale)
+{
+ return New<TDecimalLogicalType>(precision, scale);
+}
+
+TLogicalTypePtr MakeLogicalType(ESimpleLogicalValueType element, bool required)
+{
+ if (element == ESimpleLogicalValueType::Null || element == ESimpleLogicalValueType::Void) {
+ if (required) {
+ THROW_ERROR_EXCEPTION("Null type cannot be required");
+ }
+ return Singleton<TSimpleTypeStore>()->GetSimpleType(element);
+ } else if (required) {
+ return Singleton<TSimpleTypeStore>()->GetSimpleType(element);
+ } else {
+ return Singleton<TSimpleTypeStore>()->GetOptionalType(element);
+ }
+}
+
+TLogicalTypePtr ListLogicalType(TLogicalTypePtr element)
+{
+ return New<TListLogicalType>(std::move(element));
+}
+
+TLogicalTypePtr StructLogicalType(std::vector<TStructField> fields)
+{
+ return New<TStructLogicalType>(std::move(fields));
+}
+
+TLogicalTypePtr TupleLogicalType(std::vector<TLogicalTypePtr> elements)
+{
+ return New<TTupleLogicalType>(std::move(elements));
+}
+
+TLogicalTypePtr VariantTupleLogicalType(std::vector<TLogicalTypePtr> elements)
+{
+ return New<TVariantTupleLogicalType>(std::move(elements));
+}
+
+TLogicalTypePtr VariantStructLogicalType(std::vector<TStructField> fields)
+{
+ return New<TVariantStructLogicalType>(std::move(fields));
+}
+
+TLogicalTypePtr DictLogicalType(TLogicalTypePtr key, TLogicalTypePtr value)
+{
+ return New<TDictLogicalType>(std::move(key), std::move(value));
+}
+
+TLogicalTypePtr TaggedLogicalType(TString tag, TLogicalTypePtr element)
+{
+ return New<TTaggedLogicalType>(std::move(tag), std::move(element));
+}
+
+TLogicalTypePtr NullLogicalType()
+{
+ return Singleton<TSimpleTypeStore>()->GetSimpleType(ESimpleLogicalValueType::Null);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+static inline size_t GetHash(
+ const THash<NYT::NTableClient::TLogicalType>& hasher,
+ const std::vector<NYT::NTableClient::TLogicalTypePtr>& elements)
+{
+ size_t result = 0;
+ for (const auto& element : elements) {
+ result = CombineHashes(result, hasher(*element));
+ }
+ return result;
+}
+
+static inline size_t GetHash(
+ const THash<NYT::NTableClient::TLogicalType>& hasher,
+ const std::vector<NYT::NTableClient::TStructField>& fields)
+{
+ size_t result = 0;
+ for (const auto& field : fields) {
+ result = CombineHashes(result, THash<TString>{}(field.Name));
+ result = CombineHashes(result, hasher(*field.Type));
+ }
+ return result;
+}
+
+size_t THash<NYT::NTableClient::TLogicalType>::operator()(const NYT::NTableClient::TLogicalType& logicalType) const
+{
+ using namespace NYT::NTableClient;
+ const auto typeHash = static_cast<size_t>(logicalType.GetMetatype());
+ switch (logicalType.GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ return CombineHashes(static_cast<size_t>(logicalType.AsSimpleTypeRef().GetElement()), typeHash);
+ case ELogicalMetatype::Decimal:
+ return CombineHashes(
+ CombineHashes(
+ static_cast<size_t>(logicalType.AsDecimalTypeRef().GetPrecision()),
+ static_cast<size_t>(logicalType.AsDecimalTypeRef().GetScale())
+ ),
+ typeHash);
+ case ELogicalMetatype::Optional:
+ return CombineHashes((*this)(*logicalType.AsOptionalTypeRef().GetElement()), typeHash);
+ case ELogicalMetatype::List:
+ return CombineHashes((*this)(*logicalType.AsListTypeRef().GetElement()), typeHash);
+ case ELogicalMetatype::Struct:
+ return CombineHashes(GetHash(*this, logicalType.AsStructTypeRef().GetFields()), typeHash);
+ case ELogicalMetatype::Tuple:
+ return CombineHashes(GetHash(*this, logicalType.AsTupleTypeRef().GetElements()), typeHash);
+ case ELogicalMetatype::VariantStruct:
+ return CombineHashes(GetHash(*this, logicalType.AsVariantStructTypeRef().GetFields()), typeHash);
+ case ELogicalMetatype::VariantTuple:
+ return CombineHashes(GetHash(*this, logicalType.AsVariantTupleTypeRef().GetElements()), typeHash);
+ case ELogicalMetatype::Dict:
+ return CombineHashes(
+ CombineHashes(
+ (*this)(*logicalType.AsDictTypeRef().GetKey()),
+ (*this)(*logicalType.AsDictTypeRef().GetValue())
+ ),
+ typeHash);
+ case ELogicalMetatype::Tagged:
+ return CombineHashes(
+ CombineHashes(
+ THash<TString>()(logicalType.AsTaggedTypeRef().GetTag()),
+ (*this)(*logicalType.AsTaggedTypeRef().GetElement())
+ ),
+ typeHash);
+ }
+ YT_ABORT();
+}
diff --git a/yt/yt/client/table_client/logical_type.h b/yt/yt/client/table_client/logical_type.h
new file mode 100644
index 0000000000..807b851e0c
--- /dev/null
+++ b/yt/yt/client/table_client/logical_type.h
@@ -0,0 +1,447 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/table_client/row_base.h>
+
+#include <yt/yt/core/yson/public.h>
+#include <yt/yt/core/ytree/public.h>
+
+#include <yt/yt/core/misc/ref_counted.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+#include <util/generic/hash.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWalkContext;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ELogicalMetatype,
+ (Simple)
+ (Optional)
+ (List)
+ (Struct)
+ (Tuple)
+
+ // Variant with named elements.
+ (VariantStruct)
+
+ // Variant with unnamed elements.
+ (VariantTuple)
+
+ // Dict is effectively list of pairs. We have restrictions on the key type.
+ // YT doesn't check uniqueness of the keys.
+ (Dict)
+
+ (Tagged)
+
+ (Decimal)
+);
+
+class TLogicalType
+ : public virtual TRefCounted
+{
+public:
+ explicit TLogicalType(ELogicalMetatype type);
+ Y_FORCE_INLINE ELogicalMetatype GetMetatype() const;
+
+ const TSimpleLogicalType& AsSimpleTypeRef() const;
+ Y_FORCE_INLINE const TSimpleLogicalType& UncheckedAsSimpleTypeRef() const;
+ const TDecimalLogicalType& AsDecimalTypeRef() const;
+ Y_FORCE_INLINE const TDecimalLogicalType& UncheckedAsDecimalTypeRef() const;
+ const TOptionalLogicalType& AsOptionalTypeRef() const;
+ Y_FORCE_INLINE const TOptionalLogicalType& UncheckedAsOptionalTypeRef() const;
+ const TListLogicalType& AsListTypeRef() const;
+ Y_FORCE_INLINE const TListLogicalType& UncheckedAsListTypeRef() const;
+ const TStructLogicalType& AsStructTypeRef() const;
+ Y_FORCE_INLINE const TStructLogicalType& UncheckedAsStructTypeRef() const;
+ const TTupleLogicalType& AsTupleTypeRef() const;
+ Y_FORCE_INLINE const TTupleLogicalType& UncheckedAsTupleTypeRef() const;
+ const TVariantTupleLogicalType& AsVariantTupleTypeRef() const;
+ Y_FORCE_INLINE const TVariantTupleLogicalType& UncheckedAsVariantTupleTypeRef() const;
+ const TVariantStructLogicalType& AsVariantStructTypeRef() const;
+ Y_FORCE_INLINE const TVariantStructLogicalType& UncheckedAsVariantStructTypeRef() const;
+ const TDictLogicalType& AsDictTypeRef() const;
+ Y_FORCE_INLINE const TDictLogicalType& UncheckedAsDictTypeRef() const;
+ const TTaggedLogicalType& AsTaggedTypeRef() const;
+ Y_FORCE_INLINE const TTaggedLogicalType& UncheckedAsTaggedTypeRef() const;
+
+ virtual size_t GetMemoryUsage() const = 0;
+ virtual int GetTypeComplexity() const = 0;
+
+ // This function doesn't validate children of current node.
+ // Users should use ValidateLogicalType function.
+ virtual void ValidateNode(const TWalkContext& context) const = 0;
+
+ // Whether or not this type can have null value.
+ virtual bool IsNullable() const = 0;
+
+ //
+ // Additional helpers to decompose complex type.
+ // Logical type MUST have appropriate metatype otherwise abort() will be called.
+
+ //
+ // Return underlying element for Optional,List,Tagged.
+ const TLogicalTypePtr& GetElement() const;
+
+ //
+ // Return elements for Tuple,VariantTuple
+ const std::vector<TLogicalTypePtr>& GetElements() const;
+
+ //
+ // Return fields for Struct,VariantStruct
+ const std::vector<TStructField>& GetFields() const;
+
+private:
+ const ELogicalMetatype Metatype_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TLogicalType)
+
+TString ToString(const TLogicalType& logicalType);
+
+//! Debug printers for Gtest unittests.
+void PrintTo(ELogicalMetatype type, std::ostream* os);
+void PrintTo(const TLogicalType& type, std::ostream* os);
+void PrintTo(const TLogicalTypePtr& type, std::ostream* os);
+
+bool operator == (const TLogicalType& lhs, const TLogicalType& rhs);
+bool operator != (const TLogicalType& lhs, const TLogicalType& rhs);
+bool operator == (const TLogicalTypePtr& lhs, const TLogicalTypePtr& rhs) = delete;
+bool operator != (const TLogicalTypePtr& lhs, const TLogicalTypePtr& rhs) = delete;
+
+void ValidateLogicalType(const TComplexTypeFieldDescriptor& descriptor, std::optional<int> depthLimit = std::nullopt);
+
+// Function converts new type to old typesystem.
+// The first element of result is ESimpleLogicalValue type corresponding to logicalType
+// (as seen in `type` field of column schema).
+// The second element of result is false if logicalType is Null or it is optional<A> where A is any type otherwise it's true.
+std::pair<ESimpleLogicalValueType, bool> CastToV1Type(const TLogicalTypePtr& logicalType);
+
+EValueType GetWireType(const TLogicalTypePtr& logicalType);
+
+// Return true if given type is pure v1 type (i.e. expressible with `type` and `required` fields in schema).
+bool IsV1Type(const TLogicalTypePtr& logicalType);
+
+// Return true if this is new type expressible with EValueType::Composite type.
+bool IsV3Composite(const TLogicalTypePtr& logicalType);
+
+EValueType GetWireType(const TLogicalTypePtr& logicalType);
+
+// Try to remove top level optional type if that doesn't change UnversionedValue representation
+// of non null types.
+TLogicalTypePtr DenullifyLogicalType(const TLogicalTypePtr& logicalType);
+
+// Returns copy of the logical type with all tagged types replaces with its elements.
+TLogicalTypePtr DetagLogicalType(const TLogicalTypePtr& logicalType);
+
+void ToProto(NProto::TLogicalType* protoLogicalType, const TLogicalTypePtr& logicalType);
+void FromProto(TLogicalTypePtr* logicalType, const NProto::TLogicalType& protoLogicalType);
+
+bool IsComparable(const TLogicalTypePtr& type);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Special wrapper class that allows to serialize LogicalType in type_v3 format
+// https://a.yandex-team.ru/arc/trunk/arcadia/logfeller/mvp/docs/types_serialization.md
+struct TTypeV3LogicalTypeWrapper
+{
+ TLogicalTypePtr LogicalType;
+};
+
+void Serialize(const TTypeV3LogicalTypeWrapper& wrapper, NYson::IYsonConsumer* consumer);
+void Deserialize(TTypeV3LogicalTypeWrapper& wrapper, NYTree::INodePtr node);
+void Deserialize(TTypeV3LogicalTypeWrapper& wrapper, NYson::TYsonPullParserCursor* cursor);
+
+void Serialize(const TLogicalTypePtr& type, NYson::IYsonConsumer* consumer);
+void Deserialize(TLogicalTypePtr& type, NYTree::INodePtr node);
+
+void DeserializeV3(TLogicalTypePtr& type, NYson::TYsonPullParserCursor* cursor);
+void Deserialize(TLogicalTypePtr& type, NYson::TYsonPullParserCursor* cursor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecimalLogicalType
+ : public TLogicalType
+{
+public:
+ static constexpr int MinPrecision = 1;
+ static constexpr int MaxPrecision = 35;
+
+public:
+ TDecimalLogicalType(int precision, int scale);
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+ Y_FORCE_INLINE int GetPrecision() const;
+ Y_FORCE_INLINE int GetScale() const;
+
+private:
+ const int Precision_;
+ const int Scale_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TOptionalLogicalType
+ : public TLogicalType
+{
+public:
+ explicit TOptionalLogicalType(TLogicalTypePtr element);
+
+ Y_FORCE_INLINE const TLogicalTypePtr& GetElement() const;
+
+ std::optional<ESimpleLogicalValueType> Simplify() const;
+
+ // Cached value of GetElement()->IsNullable(), useful for performance reasons.
+ Y_FORCE_INLINE bool IsElementNullable() const;
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+private:
+ const TLogicalTypePtr Element_;
+ const bool ElementIsNullable_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSimpleLogicalType
+ : public TLogicalType
+{
+public:
+ explicit TSimpleLogicalType(ESimpleLogicalValueType element);
+
+ Y_FORCE_INLINE ESimpleLogicalValueType GetElement() const;
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+private:
+ ESimpleLogicalValueType Element_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TListLogicalType
+ : public TLogicalType
+{
+public:
+ explicit TListLogicalType(TLogicalTypePtr element);
+
+ Y_FORCE_INLINE const TLogicalTypePtr& GetElement() const;
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+private:
+ TLogicalTypePtr Element_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Class builds descriptors of a complex value field.
+// Such descriptors are useful for generating error messages when working with complex types.
+class TComplexTypeFieldDescriptor
+{
+public:
+ explicit TComplexTypeFieldDescriptor(TLogicalTypePtr type);
+ explicit TComplexTypeFieldDescriptor(const TColumnSchema& column);
+ TComplexTypeFieldDescriptor(TString columnName, TLogicalTypePtr type);
+
+ TComplexTypeFieldDescriptor OptionalElement() const;
+ TComplexTypeFieldDescriptor ListElement() const;
+ TComplexTypeFieldDescriptor Element(size_t i) const;
+ TComplexTypeFieldDescriptor TupleElement(size_t i) const;
+ TComplexTypeFieldDescriptor VariantTupleElement(size_t i) const;
+ TComplexTypeFieldDescriptor Field(size_t i) const;
+ TComplexTypeFieldDescriptor StructField(size_t i) const;
+ TComplexTypeFieldDescriptor VariantStructField(size_t i) const;
+ TComplexTypeFieldDescriptor DictKey() const;
+ TComplexTypeFieldDescriptor DictValue() const;
+ TComplexTypeFieldDescriptor TaggedElement() const;
+
+ TComplexTypeFieldDescriptor Detag() const;
+
+ const TString& GetDescription() const;
+ const TLogicalTypePtr& GetType() const;
+
+private:
+ TString Descriptor_;
+ TLogicalTypePtr Type_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TStructField
+{
+ TString Name;
+ TLogicalTypePtr Type;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Base class for struct and named variant.
+class TStructLogicalTypeBase
+ : public TLogicalType
+{
+public:
+
+public:
+ TStructLogicalTypeBase(ELogicalMetatype metatype, std::vector<TStructField> fields);
+ Y_FORCE_INLINE const std::vector<TStructField>& GetFields() const;
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+private:
+ std::vector<TStructField> Fields_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTupleLogicalTypeBase
+ : public TLogicalType
+{
+public:
+ explicit TTupleLogicalTypeBase(ELogicalMetatype metatype, std::vector<TLogicalTypePtr> elements);
+
+ Y_FORCE_INLINE const std::vector<TLogicalTypePtr>& GetElements() const;
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+private:
+ std::vector<TLogicalTypePtr> Elements_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TStructLogicalType
+ : public TStructLogicalTypeBase
+{
+public:
+ TStructLogicalType(std::vector<TStructField> fields);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTupleLogicalType
+ : public TTupleLogicalTypeBase
+{
+public:
+ TTupleLogicalType(std::vector<TLogicalTypePtr> elements);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TVariantStructLogicalType
+ : public TStructLogicalTypeBase
+{
+public:
+ explicit TVariantStructLogicalType(std::vector<TStructField> fields);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TVariantTupleLogicalType
+ : public TTupleLogicalTypeBase
+{
+public:
+ explicit TVariantTupleLogicalType(std::vector<TLogicalTypePtr> elements);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDictLogicalType
+ : public TLogicalType
+{
+public:
+ TDictLogicalType(TLogicalTypePtr key, TLogicalTypePtr value);
+
+ Y_FORCE_INLINE const TLogicalTypePtr& GetKey() const;
+ Y_FORCE_INLINE const TLogicalTypePtr& GetValue() const;
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+private:
+ TLogicalTypePtr Key_;
+ TLogicalTypePtr Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTaggedLogicalType
+ : public TLogicalType
+{
+public:
+ TTaggedLogicalType(TString tag, TLogicalTypePtr element);
+
+ Y_FORCE_INLINE const TString& GetTag() const;
+ Y_FORCE_INLINE const TLogicalTypePtr& GetElement() const;
+
+ size_t GetMemoryUsage() const override;
+ int GetTypeComplexity() const override;
+ void ValidateNode(const TWalkContext& context) const override;
+ bool IsNullable() const override;
+
+private:
+ const TString Tag_;
+ const TLogicalTypePtr Element_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLogicalTypePtr SimpleLogicalType(ESimpleLogicalValueType element);
+TLogicalTypePtr DecimalLogicalType(int precision, int scale);
+TLogicalTypePtr OptionalLogicalType(TLogicalTypePtr element);
+TLogicalTypePtr ListLogicalType(TLogicalTypePtr element);
+TLogicalTypePtr StructLogicalType(std::vector<TStructField> fields);
+TLogicalTypePtr TupleLogicalType(std::vector<TLogicalTypePtr> elements);
+TLogicalTypePtr VariantStructLogicalType(std::vector<TStructField> fields);
+TLogicalTypePtr VariantTupleLogicalType(std::vector<TLogicalTypePtr> elements);
+TLogicalTypePtr DictLogicalType(TLogicalTypePtr key, TLogicalTypePtr value);
+TLogicalTypePtr TaggedLogicalType(TString tag, TLogicalTypePtr element);
+TLogicalTypePtr NullLogicalType();
+
+TLogicalTypePtr MakeOptionalIfNot(TLogicalTypePtr element);
+
+// Creates logical type from legacy schema fields.
+// IMPORTANT: Only used for compatibility reasons.
+// In modern code, one should use OptionalLogicalType + SimpleLogicalType instead.
+TLogicalTypePtr MakeLogicalType(ESimpleLogicalValueType type, bool required);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+template <>
+struct THash<NYT::NTableClient::TLogicalType>
+{
+ size_t operator() (const NYT::NTableClient::TLogicalType& logicalType) const;
+};
+
+#define LOGICAL_TYPE_INL_H_
+#include "logical_type-inl.h"
+#undef LOGICAL_TYPE_INL_H_
diff --git a/yt/yt/client/table_client/name_table.cpp b/yt/yt/client/table_client/name_table.cpp
new file mode 100644
index 0000000000..d8918deb85
--- /dev/null
+++ b/yt/yt/client/table_client/name_table.cpp
@@ -0,0 +1,304 @@
+#include "name_table.h"
+
+#include "column_sort_schema.h"
+#include "schema.h"
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNameTablePtr TNameTable::FromSchema(const TTableSchema& schema)
+{
+ auto nameTable = New<TNameTable>();
+ nameTable->NameToId_.reserve(schema.Columns().size());
+ for (const auto& column : schema.Columns()) {
+ nameTable->DoRegisterNameOrThrow(column.Name());
+ }
+ return nameTable;
+}
+
+TNameTablePtr TNameTable::FromSchemaStable(const TTableSchema& schema)
+{
+ auto nameTable = New<TNameTable>();
+ nameTable->NameToId_.reserve(schema.Columns().size());
+ for (const auto& column : schema.Columns()) {
+ nameTable->DoRegisterNameOrThrow(column.StableName().Get());
+ }
+ return nameTable;
+}
+
+TNameTablePtr TNameTable::FromKeyColumns(const TKeyColumns& keyColumns)
+{
+ auto nameTable = New<TNameTable>();
+ nameTable->NameToId_.reserve(keyColumns.size());
+ for (const auto& name : keyColumns) {
+ nameTable->DoRegisterNameOrThrow(name);
+ }
+ return nameTable;
+}
+
+TNameTablePtr TNameTable::FromSortColumns(const TSortColumns& sortColumns)
+{
+ return TNameTable::FromKeyColumns(GetColumnNames(sortColumns));
+}
+
+int TNameTable::GetSize() const
+{
+ auto guard = Guard(SpinLock_);
+ return IdToName_.size();
+}
+
+i64 TNameTable::GetByteSize() const
+{
+ auto guard = Guard(SpinLock_);
+ return ByteSize_;
+}
+
+void TNameTable::SetEnableColumnNameValidation()
+{
+ auto guard = Guard(SpinLock_);
+ EnableColumnNameValidation_ = true;
+}
+
+std::optional<int> TNameTable::FindId(TStringBuf name) const
+{
+ auto guard = Guard(SpinLock_);
+ auto it = NameToId_.find(name);
+ if (it == NameToId_.end()) {
+ return std::nullopt;
+ } else {
+ return std::make_optional(it->second);
+ }
+}
+
+int TNameTable::GetIdOrThrow(TStringBuf name) const
+{
+ auto optionalId = FindId(name);
+ if (!optionalId) {
+ THROW_ERROR_EXCEPTION("No such column %Qv", name);
+ }
+ return *optionalId;
+}
+
+int TNameTable::GetId(TStringBuf name) const
+{
+ auto index = FindId(name);
+ YT_VERIFY(index);
+ return *index;
+}
+
+TStringBuf TNameTable::GetName(int id) const
+{
+ auto guard = Guard(SpinLock_);
+ YT_VERIFY(id >= 0 && id < std::ssize(IdToName_));
+ return IdToName_[id];
+}
+
+TStringBuf TNameTable::GetNameOrThrow(int id) const
+{
+ auto guard = Guard(SpinLock_);
+ if (id < 0 || id >= std::ssize(IdToName_)) {
+ THROW_ERROR_EXCEPTION("Invalid column requested from name table: expected in range [0, %v), got %v",
+ IdToName_.size(),
+ id);
+ }
+ return IdToName_[id];
+}
+
+int TNameTable::RegisterName(TStringBuf name)
+{
+ auto guard = Guard(SpinLock_);
+ return DoRegisterName(name);
+}
+
+int TNameTable::RegisterNameOrThrow(TStringBuf name)
+{
+ auto guard = Guard(SpinLock_);
+ return DoRegisterNameOrThrow(name);
+}
+
+int TNameTable::GetIdOrRegisterName(TStringBuf name)
+{
+ auto guard = Guard(SpinLock_);
+ auto it = NameToId_.find(name);
+ if (it == NameToId_.end()) {
+ return DoRegisterName(name);
+ } else {
+ return it->second;
+ }
+}
+
+int TNameTable::DoRegisterName(TStringBuf name)
+{
+ int id = IdToName_.size();
+
+ if (id >= MaxColumnId) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::CorruptedNameTable,
+ "Cannot register column %Qv: column limit exceeded",
+ name)
+ << TErrorAttribute("max_column_id", MaxColumnId);
+ }
+
+ if (EnableColumnNameValidation_ && name.length() > MaxColumnNameLength) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::CorruptedNameTable,
+ "Cannot register column %Qv: column name is too long",
+ name)
+ << TErrorAttribute("max_column_name_length", MaxColumnNameLength);
+ }
+
+ const auto& savedName = IdToName_.emplace_back(name);
+ YT_VERIFY(NameToId_.emplace(savedName, id).second);
+ ByteSize_ += savedName.length();
+ return id;
+}
+
+int TNameTable::DoRegisterNameOrThrow(TStringBuf name)
+{
+ auto optionalId = NameToId_.find(name);
+ if (optionalId != NameToId_.end()) {
+ THROW_ERROR_EXCEPTION("Cannot register column %Qv: column already exists", name);
+ }
+ return DoRegisterName(name);
+}
+
+std::vector<TString> TNameTable::GetNames() const
+{
+ auto guard = Guard(SpinLock_);
+ std::vector<TString> result(IdToName_.begin(), IdToName_.end());
+ return result;
+}
+
+void FormatValue(TStringBuilderBase* builder, const TNameTable& nameTable, TStringBuf /*spec*/)
+{
+ builder->AppendChar('{');
+ bool first = true;
+ for (const auto& name : nameTable.GetNames()) {
+ if (first) {
+ first = false;
+ } else {
+ builder->AppendString("; ");
+ }
+ builder->AppendFormat("%Qv=%v", name, nameTable.GetId(name));
+ }
+ builder->AppendChar('}');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNameTableReader::TNameTableReader(TNameTablePtr nameTable)
+ : NameTable_(std::move(nameTable))
+{
+ Fill();
+}
+
+TStringBuf TNameTableReader::FindName(int id) const
+{
+ if (id < 0) {
+ return {};
+ }
+
+ if (id >= std::ssize(IdToNameCache_)) {
+ Fill();
+
+ if (id >= std::ssize(IdToNameCache_)) {
+ return {};
+ }
+ }
+
+ return IdToNameCache_[id];
+}
+
+TStringBuf TNameTableReader::GetName(int id) const
+{
+ YT_ASSERT(id >= 0);
+ if (id >= std::ssize(IdToNameCache_)) {
+ Fill();
+ }
+
+ YT_ASSERT(id < std::ssize(IdToNameCache_));
+ return IdToNameCache_[id];
+}
+
+int TNameTableReader::GetSize() const
+{
+ Fill();
+ return static_cast<int>(IdToNameCache_.size());
+}
+
+void TNameTableReader::Fill() const
+{
+ int thisSize = static_cast<int>(IdToNameCache_.size());
+ int underlyingSize = NameTable_->GetSize();
+ for (int id = thisSize; id < underlyingSize; ++id) {
+ IdToNameCache_.push_back(std::string(NameTable_->GetName(id)));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNameTableWriter::TNameTableWriter(TNameTablePtr nameTable)
+ : NameTable_(std::move(nameTable))
+{ }
+
+std::optional<int> TNameTableWriter::FindId(TStringBuf name) const
+{
+ auto it = NameToId_.find(name);
+ if (it != NameToId_.end()) {
+ return it->second;
+ }
+
+ auto optionalId = NameTable_->FindId(name);
+ if (optionalId) {
+ Names_.push_back(TString(name));
+ YT_VERIFY(NameToId_.emplace(Names_.back(), *optionalId).second);
+ }
+ return optionalId;
+}
+
+int TNameTableWriter::GetIdOrThrow(TStringBuf name) const
+{
+ auto optionalId = FindId(name);
+ if (!optionalId) {
+ THROW_ERROR_EXCEPTION("No such column %Qv", name);
+ }
+ return *optionalId;
+}
+
+int TNameTableWriter::GetIdOrRegisterName(TStringBuf name)
+{
+ auto it = NameToId_.find(name);
+ if (it != NameToId_.end()) {
+ return it->second;
+ }
+
+ auto id = NameTable_->GetIdOrRegisterName(name);
+ Names_.push_back(TString(name));
+ YT_VERIFY(NameToId_.emplace(Names_.back(), id).second);
+ return id;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(NProto::TNameTableExt* protoNameTable, const TNameTablePtr& nameTable)
+{
+ using NYT::ToProto;
+
+ ToProto(protoNameTable->mutable_names(), nameTable->GetNames());
+}
+
+void FromProto(TNameTablePtr* nameTable, const NProto::TNameTableExt& protoNameTable)
+{
+ using NYT::FromProto;
+
+ *nameTable = TNameTable::FromKeyColumns(FromProto<std::vector<TString>>(protoNameTable.names()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/name_table.h b/yt/yt/client/table_client/name_table.h
new file mode 100644
index 0000000000..feeabede18
--- /dev/null
+++ b/yt/yt/client/table_client/name_table.h
@@ -0,0 +1,113 @@
+#pragma once
+
+#include "public.h"
+
+#include <library/cpp/yt/threading/spin_lock.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A thread-safe id-to-name mapping.
+class TNameTable
+ : public virtual TRefCounted
+{
+public:
+ static TNameTablePtr FromSchema(const TTableSchema& schema);
+ static TNameTablePtr FromKeyColumns(const TKeyColumns& keyColumns);
+ static TNameTablePtr FromSortColumns(const TSortColumns& sortColumns);
+
+ // TODO(levysotsky): This one is currently used only for
+ // chunk name table. A separate type to chunk name table
+ // may be handy.
+ static TNameTablePtr FromSchemaStable(const TTableSchema& schema);
+
+ int GetSize() const;
+ i64 GetByteSize() const;
+
+ void SetEnableColumnNameValidation();
+
+ std::optional<int> FindId(TStringBuf name) const;
+ int GetIdOrThrow(TStringBuf name) const;
+ int GetId(TStringBuf name) const;
+ int RegisterName(TStringBuf name);
+ int RegisterNameOrThrow(TStringBuf name);
+ int GetIdOrRegisterName(TStringBuf name);
+
+ TStringBuf GetName(int id) const;
+ TStringBuf GetNameOrThrow(int id) const;
+
+ std::vector<TString> GetNames() const;
+
+private:
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock_);
+
+ bool EnableColumnNameValidation_ = false;
+
+ // String values are owned by IdToName_.
+ // NB: Names may be SSO-strings, using a deque to avoid string view invalidation.
+ std::deque<TString> IdToName_;
+ THashMap<TStringBuf, int> NameToId_;
+ i64 ByteSize_ = 0;
+
+ int DoRegisterName(TStringBuf name);
+ int DoRegisterNameOrThrow(TStringBuf name);
+};
+
+DEFINE_REFCOUNTED_TYPE(TNameTable)
+
+void FormatValue(TStringBuilderBase* builder, const TNameTable& nameTable, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A non thread-safe read-only wrapper for TNameTable.
+class TNameTableReader
+ : private TNonCopyable
+{
+public:
+ explicit TNameTableReader(TNameTablePtr nameTable);
+
+ TStringBuf FindName(int id) const;
+ TStringBuf GetName(int id) const;
+ int GetSize() const;
+
+private:
+ const TNameTablePtr NameTable_;
+
+ mutable std::deque<std::string> IdToNameCache_; // Addresses of string data are immutable.
+
+ void Fill() const;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A non thread-safe read-write wrapper for TNameTable.
+class TNameTableWriter
+{
+public:
+ explicit TNameTableWriter(TNameTablePtr nameTable);
+
+ std::optional<int> FindId(TStringBuf name) const;
+ int GetIdOrThrow(TStringBuf name) const;
+ int GetIdOrRegisterName(TStringBuf name);
+
+private:
+ const TNameTablePtr NameTable_;
+
+ // String values are owned by Names_
+ // NB: Names may be SSO-strings, using a deque to avoid string view invalidation
+ mutable std::deque<TString> Names_;
+ mutable THashMap<TStringBuf, int> NameToId_;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ToProto(NProto::TNameTableExt* protoNameTable, const TNameTablePtr& nameTable);
+void ToProto(NProto::TNameTableExt* protoNameTable, const TNameTablePtr& nameTable);
+void FromProto(TNameTablePtr* nameTable, const NProto::TNameTableExt& protoNameTable);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/pipe.cpp b/yt/yt/client/table_client/pipe.cpp
new file mode 100644
index 0000000000..232be888ce
--- /dev/null
+++ b/yt/yt/client/table_client/pipe.cpp
@@ -0,0 +1,292 @@
+#include "pipe.h"
+
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/unversioned_reader.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/misc/ring_queue.h>
+
+namespace NYT::NTableClient {
+
+using NChunkClient::NProto::TDataStatistics;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSchemafulPipeBufferTag
+{ };
+
+struct TSchemafulPipe::TData
+ : public TRefCounted
+{
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock);
+
+ const TRowBufferPtr RowBuffer;
+ TRingQueue<TUnversionedRow> RowQueue;
+
+ TPromise<void> ReaderReadyEvent;
+ TPromise<void> WriterReadyEvent = NewPromise<void>();
+
+ int RowsWritten = 0;
+ int RowsRead = 0;
+ bool WriterClosed = false;
+ bool Failed = false;
+
+ explicit TData(IMemoryChunkProviderPtr chunkProvider)
+ : RowBuffer(New<TRowBuffer>(TSchemafulPipeBufferTag(), std::move(chunkProvider)))
+ {
+ ResetReaderReadyEvent();
+ }
+
+ void ResetReaderReadyEvent()
+ {
+ ReaderReadyEvent = NewPromise<void>();
+ ReaderReadyEvent.OnCanceled(BIND([=, this, this_ = MakeStrong(this)] (const TError& error) {
+ Fail(TError(NYT::EErrorCode::Canceled, "Pipe reader canceled")
+ << error);
+ }));
+ }
+
+ void Fail(const TError& error)
+ {
+ YT_VERIFY(!error.IsOK());
+
+ TPromise<void> readerReadyEvent;
+ TPromise<void> writerReadyEvent;
+
+ {
+ auto guard = Guard(SpinLock);
+ if (WriterClosed || Failed)
+ return;
+
+ Failed = true;
+ readerReadyEvent = ReaderReadyEvent;
+ writerReadyEvent = WriterReadyEvent;
+ }
+
+ readerReadyEvent.TrySet(error);
+ writerReadyEvent.TrySet(error);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemafulPipe::TReader
+ : public ISchemafulUnversionedReader
+{
+public:
+ explicit TReader(TDataPtr data)
+ : Data_(std::move(data))
+ { }
+
+ IUnversionedRowBatchPtr Read(const TRowBatchReadOptions& options) override
+ {
+ std::vector<TUnversionedRow> rows;
+ rows.reserve(options.MaxRowsPerRead);
+ i64 dataWeight = 0;
+
+ {
+ auto guard = Guard(Data_->SpinLock);
+
+ if (Data_->WriterClosed && Data_->RowsWritten == Data_->RowsRead) {
+ return nullptr;
+ }
+
+ if (!Data_->Failed) {
+ auto& rowQueue = Data_->RowQueue;
+ while (!rowQueue.empty() &&
+ std::ssize(rows) < options.MaxRowsPerRead &&
+ dataWeight < options.MaxDataWeightPerRead)
+ {
+ auto row = rowQueue.front();
+ rowQueue.pop();
+ dataWeight += GetDataWeight(row);
+ rows.push_back(row);
+ ++Data_->RowsRead;
+ }
+ }
+
+ if (rows.empty()) {
+ ReadyEvent_ = Data_->ReaderReadyEvent.ToFuture();
+ }
+ }
+
+ return CreateBatchFromUnversionedRows(MakeSharedRange(std::move(rows), MakeStrong(this)));
+ }
+
+ TFuture<void> GetReadyEvent() const override
+ {
+ return ReadyEvent_;
+ }
+
+ TDataStatistics GetDataStatistics() const override
+ {
+ return TDataStatistics();
+ }
+
+ NChunkClient::TCodecStatistics GetDecompressionStatistics() const override
+ {
+ return NChunkClient::TCodecStatistics();
+ }
+
+ bool IsFetchingCompleted() const override
+ {
+ return false;
+ }
+
+ std::vector<NChunkClient::TChunkId> GetFailedChunkIds() const override
+ {
+ return {};
+ }
+
+private:
+ const TDataPtr Data_;
+
+ TFuture<void> ReadyEvent_ = VoidFuture;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemafulPipe::TWriter
+ : public IUnversionedRowsetWriter
+{
+public:
+ explicit TWriter(TDataPtr data)
+ : Data_(std::move(data))
+ { }
+
+ TFuture<void> Close() override
+ {
+ TPromise<void> readerReadyEvent;
+ TPromise<void> writerReadyEvent;
+
+ bool doClose = false;
+
+ {
+ auto guard = Guard(Data_->SpinLock);
+
+ YT_VERIFY(!Data_->WriterClosed);
+ Data_->WriterClosed = true;
+
+ if (!Data_->Failed) {
+ doClose = true;
+ }
+
+ readerReadyEvent = Data_->ReaderReadyEvent;
+ writerReadyEvent = Data_->WriterReadyEvent;
+ }
+
+ readerReadyEvent.TrySet(TError());
+ if (doClose) {
+ writerReadyEvent.TrySet(TError());
+ }
+
+ return writerReadyEvent;
+ }
+
+ bool Write(TRange<TUnversionedRow> rows) override
+ {
+ // Copy data (no lock).
+ auto capturedRows = Data_->RowBuffer->CaptureRows(rows);
+
+ // Enqueue rows (with lock).
+ TPromise<void> readerReadyEvent;
+
+ {
+ auto guard = Guard(Data_->SpinLock);
+
+ YT_VERIFY(!Data_->WriterClosed);
+
+ if (Data_->Failed) {
+ return false;
+ }
+
+ for (auto row : capturedRows) {
+ Data_->RowQueue.push(row);
+ ++Data_->RowsWritten;
+ }
+
+ readerReadyEvent = std::move(Data_->ReaderReadyEvent);
+ Data_->ResetReaderReadyEvent();
+ }
+
+ // Signal readers.
+ readerReadyEvent.TrySet(TError());
+
+ return true;
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ // TODO(babenko): implement backpressure from reader
+ auto guard = Guard(Data_->SpinLock);
+ YT_VERIFY(Data_->Failed);
+ return Data_->WriterReadyEvent;
+ }
+
+private:
+ const TDataPtr Data_;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSchemafulPipe::TImpl
+ : public TRefCounted
+{
+public:
+ explicit TImpl(IMemoryChunkProviderPtr chunkProvider)
+ : Data_(New<TData>(std::move(chunkProvider)))
+ , Reader_(New<TReader>(Data_))
+ , Writer_(New<TWriter>(Data_))
+ { }
+
+ ISchemafulUnversionedReaderPtr GetReader() const
+ {
+ return Reader_;
+ }
+
+ IUnversionedRowsetWriterPtr GetWriter() const
+ {
+ return Writer_;
+ }
+
+ void Fail(const TError& error)
+ {
+ Data_->Fail(error);
+ }
+
+private:
+ TDataPtr Data_;
+ TReaderPtr Reader_;
+ TWriterPtr Writer_;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSchemafulPipe::TSchemafulPipe(IMemoryChunkProviderPtr chunkProvider)
+ : Impl_(New<TImpl>(std::move(chunkProvider)))
+{ }
+
+TSchemafulPipe::~TSchemafulPipe() = default;
+
+ISchemafulUnversionedReaderPtr TSchemafulPipe::GetReader() const
+{
+ return Impl_->GetReader();
+}
+
+IUnversionedRowsetWriterPtr TSchemafulPipe::GetWriter() const
+{
+ return Impl_->GetWriter();
+}
+
+void TSchemafulPipe::Fail(const TError& error)
+{
+ Impl_->Fail(error);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/pipe.h b/yt/yt/client/table_client/pipe.h
new file mode 100644
index 0000000000..2c9ac00bf6
--- /dev/null
+++ b/yt/yt/client/table_client/pipe.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <library/cpp/yt/memory/public.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A pipe connecting a schemaful writer to a schemaful reader.
+class TSchemafulPipe
+ : public TRefCounted
+{
+public:
+ explicit TSchemafulPipe(IMemoryChunkProviderPtr chunkProvider);
+ ~TSchemafulPipe();
+
+ //! Returns the reader side of the pipe.
+ ISchemafulUnversionedReaderPtr GetReader() const;
+
+ //! Returns the writer side of the pipe.
+ IUnversionedRowsetWriterPtr GetWriter() const;
+
+ //! When called, propagates the error to the reader.
+ void Fail(const TError& error);
+
+private:
+ class TImpl;
+ typedef TIntrusivePtr<TImpl> TImplPtr;
+
+ struct TData;
+ typedef TIntrusivePtr<TData> TDataPtr;
+
+ class TReader;
+ typedef TIntrusivePtr<TReader> TReaderPtr;
+
+ class TWriter;
+ typedef TIntrusivePtr<TWriter> TWriterPtr;
+
+
+ TIntrusivePtr<TImpl> Impl_;
+
+};
+
+DEFINE_REFCOUNTED_TYPE(TSchemafulPipe)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/public.cpp b/yt/yt/client/table_client/public.cpp
new file mode 100644
index 0000000000..648b4daa4f
--- /dev/null
+++ b/yt/yt/client/table_client/public.cpp
@@ -0,0 +1,19 @@
+#include "public.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString PrimaryLockName("<primary>");
+
+const TString SystemColumnNamePrefix("$");
+const TString TableIndexColumnName = SystemColumnNamePrefix + "table_index";
+const TString RowIndexColumnName = SystemColumnNamePrefix + "row_index";
+const TString RangeIndexColumnName = SystemColumnNamePrefix + "range_index";
+const TString TabletIndexColumnName = SystemColumnNamePrefix + "tablet_index";
+const TString TimestampColumnName = SystemColumnNamePrefix + "timestamp";
+const TString CumulativeDataWeightColumnName = SystemColumnNamePrefix + "cumulative_data_weight";
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/public.h b/yt/yt/client/table_client/public.h
new file mode 100644
index 0000000000..6074593977
--- /dev/null
+++ b/yt/yt/client/table_client/public.h
@@ -0,0 +1,402 @@
+#pragma once
+
+#include <yt/yt/client/chunk_client/public.h>
+
+#include <yt/yt/client/cypress_client/public.h>
+
+#include <yt/yt/client/transaction_client/public.h>
+
+#include <yt/yt/core/misc/range.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+#include <util/generic/size_literals.h>
+
+#include <initializer_list>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+class TNameTableExt;
+class TLogicalType;
+class TColumnSchema;
+class TDeletedColumn;
+class TTableSchemaExt;
+class TKeyColumnsExt;
+class TSortColumnsExt;
+class TBoundaryKeysExt;
+class TBlockIndexesExt;
+class TDataBlockMetaExt;
+class TSystemBlockMetaExt;
+class TColumnarStatisticsExt;
+class TDataBlockMeta;
+class TSimpleVersionedBlockMeta;
+class TSlimVersionedBlockMeta;
+class TSchemaDictionary;
+class TColumnFilter;
+class TReqLookupRows;
+class TColumnRenameDescriptor;
+class THunkChunkRef;
+class TColumnMetaExt;
+class TVersionedRowDigestExt;
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TRefCountedDataBlockMeta = TRefCountedProto<NProto::TDataBlockMetaExt>;
+using TRefCountedDataBlockMetaPtr = TIntrusivePtr<TRefCountedDataBlockMeta>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TRefCountedColumnMeta = TRefCountedProto<NProto::TColumnMetaExt>;
+using TRefCountedColumnMetaPtr = TIntrusivePtr<TRefCountedColumnMeta>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+using NTransactionClient::TTimestamp;
+using NTransactionClient::NullTimestamp;
+using NTransactionClient::MinTimestamp;
+using NTransactionClient::MaxTimestamp;
+using NTransactionClient::SyncLastCommittedTimestamp;
+using NTransactionClient::AsyncLastCommittedTimestamp;
+using NTransactionClient::AllCommittedTimestamp;
+using NTransactionClient::NotPreparedTimestamp;
+
+using TKeyColumns = std::vector<TString>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Keep values below consistent with https://wiki.yandex-team.ru/yt/userdoc/tables.
+constexpr int MaxKeyColumnCount = 256;
+constexpr int TypicalColumnCount = 64;
+constexpr int MaxColumnLockCount = 32;
+constexpr int MaxColumnNameLength = 256;
+constexpr int MaxColumnLockLength = 256;
+constexpr int MaxColumnGroupLength = 256;
+
+// Only for dynamic tables.
+constexpr int MaxValuesPerRow = 1024;
+constexpr int MaxRowsPerRowset = 5 * 1024 * 1024;
+constexpr i64 MaxStringValueLength = 16_MB;
+constexpr i64 MaxAnyValueLength = 16_MB;
+constexpr i64 MaxCompositeValueLength = 16_MB;
+constexpr i64 MaxServerVersionedRowDataWeight = 512_MB;
+constexpr i64 MaxClientVersionedRowDataWeight = 128_MB;
+constexpr int MaxKeyColumnCountInDynamicTable = 32;
+constexpr int MaxTimestampCountPerRow = std::numeric_limits<ui16>::max();
+
+static_assert(
+ MaxTimestampCountPerRow <= std::numeric_limits<ui16>::max(),
+ "Max timestamp count cannot be larger than UINT16_MAX");
+
+// Only for static tables.
+constexpr i64 MaxRowWeightLimit = 128_MB;
+constexpr i64 MaxKeyWeightLimit = 256_KB;
+
+// NB(psushin): increasing this parameter requires rewriting all chunks,
+// so one probably should never want to do it.
+constexpr int MaxSampleSize = 64_KB;
+
+// This is a hard limit for static tables,
+// imposed by Id field size (16-bit) in TUnversionedValue.
+constexpr int MaxColumnId = 32 * 1024;
+
+constexpr int MaxSchemaTotalTypeComplexity = MaxColumnId;
+constexpr int MaxSchemaDepth = 32;
+
+extern const TString SystemColumnNamePrefix;
+extern const TString TableIndexColumnName;
+extern const TString RowIndexColumnName;
+extern const TString RangeIndexColumnName;
+extern const TString TabletIndexColumnName;
+extern const TString TimestampColumnName;
+extern const TString CumulativeDataWeightColumnName;
+extern const TString PrimaryLockName;
+
+constexpr int TypicalHunkColumnCount = 8;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM_WITH_UNDERLYING_TYPE(EHunkValueTag, ui8,
+ ((Inline) (0))
+ ((LocalRef) (1))
+ ((GlobalRef)(2))
+);
+
+// Do not change these values since they are stored in the master snapshot.
+DEFINE_ENUM(ETableSchemaMode,
+ ((Weak) (0))
+ ((Strong) (1))
+);
+
+DEFINE_ENUM(EOptimizeFor,
+ ((Lookup) (0))
+ ((Scan) (1))
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((SortOrderViolation) (301))
+ ((InvalidDoubleValue) (302))
+ ((IncomparableTypes) (303))
+ ((UnhashableType) (304))
+ // E.g. name table with more than #MaxColumnId columns (may come from legacy chunks).
+ ((CorruptedNameTable) (305))
+ ((UniqueKeyViolation) (306))
+ ((SchemaViolation) (307))
+ ((RowWeightLimitExceeded) (308))
+ ((InvalidColumnFilter) (309))
+ ((InvalidColumnRenaming) (310))
+ ((IncompatibleKeyColumns) (311))
+ ((ReaderDeadlineExpired) (312))
+ ((TimestampOutOfRange) (313))
+ ((InvalidSchemaValue) (314))
+ ((FormatCannotRepresentRow) (315))
+ ((IncompatibleSchemas) (316))
+ ((InvalidPartitionedBy) (317))
+ ((MisconfiguredPartitions) (318))
+ ((TooManyRowsInRowset) (319))
+ ((TooManyColumnsInKey) (320))
+ ((TooManyValuesInRow) (321))
+ ((DuplicateColumnInSchema) (322))
+ ((MissingRequiredColumnInSchema) (323))
+ ((IncomparableComplexValues) (324))
+ ((KeyCannotBeNan) (325))
+ ((StringLikeValueLengthLimitExceeded)(326))
+ ((NameTableUpdateFailed) (327))
+ ((InvalidTableChunkFormat) (328))
+);
+
+DEFINE_ENUM(EControlAttribute,
+ (TableIndex)
+ (KeySwitch)
+ (RangeIndex)
+ (RowIndex)
+ (TabletIndex)
+ (EndOfStream)
+);
+
+DEFINE_ENUM(EUnavailableChunkStrategy,
+ ((ThrowError) (0))
+ ((Restore) (1))
+ ((Skip) (2))
+);
+
+DEFINE_ENUM(ETableSchemaModification,
+ ((None) (0))
+ ((UnversionedUpdate) (1))
+ ((UnversionedUpdateUnsorted) (2))
+);
+
+DEFINE_ENUM(EColumnarStatisticsFetcherMode,
+ ((FromNodes) (0))
+ ((FromMaster) (1))
+ ((Fallback) (2))
+);
+
+DEFINE_ENUM(ETablePartitionMode,
+ ((Sorted) (0))
+ ((Ordered) (1))
+ ((Unordered) (2))
+);
+
+DEFINE_ENUM(EMisconfiguredPartitionTactics,
+ ((Fail) (0))
+ ((Skip) (1))
+);
+
+using TTableId = NCypressClient::TNodeId;
+using TTableCollocationId = NObjectClient::TObjectId;
+using TMasterTableSchemaId = NObjectClient::TObjectId;
+
+//! NB: |int| is important since we use negative values to indicate that
+//! certain values need to be dropped. Cf. #TRowBuffer::CaptureAndPermuteRow.
+using TNameTableToSchemaIdMapping = TCompactVector<int, TypicalColumnCount>;
+
+using TIdMapping = TCompactVector<int, TypicalColumnCount>;
+
+using THunkColumnIds = TCompactVector<int, TypicalHunkColumnCount>;
+
+union TUnversionedValueData;
+
+enum class ESortOrder;
+enum class EValueType : ui8;
+enum class ESimpleLogicalValueType : ui32;
+enum class ELogicalMetatype;
+
+using TKeyColumnTypes = TCompactVector<EValueType, 16>;
+
+class TColumnFilter;
+
+struct TUnversionedValue;
+using TUnversionedValueRange = TRange<TUnversionedValue>;
+using TMutableUnversionedValueRange = TMutableRange<TUnversionedValue>;
+
+struct TVersionedValue;
+using TVersionedValueRange = TRange<TVersionedValue>;
+using TMutableVersionedValueRange = TMutableRange<TVersionedValue>;
+
+using TTimestampRange = TRange<TTimestamp>;
+using TMutableTimestampRange = TMutableRange<TTimestamp>;
+
+class TUnversionedOwningValue;
+
+struct TUnversionedRowHeader;
+struct TVersionedRowHeader;
+
+class TUnversionedRow;
+class TMutableUnversionedRow;
+class TUnversionedOwningRow;
+
+class TVersionedRow;
+class TMutableVersionedRow;
+class TVersionedOwningRow;
+
+class TKey;
+using TKeyRef = TUnversionedValueRange;
+
+using TLegacyKey = TUnversionedRow;
+using TLegacyMutableKey = TMutableUnversionedRow;
+using TLegacyOwningKey = TUnversionedOwningRow;
+
+// TODO(babenko): replace with TRange<TUnversionedRow>.
+using TRowRange = std::pair<TUnversionedRow, TUnversionedRow>;
+
+class TUnversionedRowBuilder;
+class TUnversionedOwningRowBuilder;
+
+struct TTypeErasedRow;
+
+class TKeyBound;
+class TOwningKeyBound;
+
+class TKeyComparer;
+
+struct TColumnRenameDescriptor;
+using TColumnRenameDescriptors = std::vector<TColumnRenameDescriptor>;
+
+class TStableName;
+
+class TColumnSchema;
+
+struct TColumnSortSchema;
+using TSortColumns = std::vector<TColumnSortSchema>;
+
+struct TColumnarStatistics;
+
+class TTableSchema;
+using TTableSchemaPtr = TIntrusivePtr<TTableSchema>;
+
+class TLegacyLockMask;
+using TLegacyLockBitmap = ui64;
+
+class TLockMask;
+using TLockBitmap = TCompactVector<ui64, 1>;
+
+class TComparator;
+
+DECLARE_REFCOUNTED_CLASS(TNameTable)
+class TNameTableReader;
+class TNameTableWriter;
+
+DECLARE_REFCOUNTED_CLASS(TRowBuffer)
+
+DECLARE_REFCOUNTED_STRUCT(ISchemalessUnversionedReader)
+DECLARE_REFCOUNTED_STRUCT(ISchemafulUnversionedReader)
+DECLARE_REFCOUNTED_STRUCT(IUnversionedWriter)
+DECLARE_REFCOUNTED_STRUCT(IUnversionedRowsetWriter)
+
+using TSchemalessWriterFactory = std::function<IUnversionedRowsetWriterPtr(
+ TNameTablePtr,
+ TTableSchemaPtr)>;
+
+DECLARE_REFCOUNTED_STRUCT(IVersionedReader)
+DECLARE_REFCOUNTED_STRUCT(IVersionedWriter)
+
+DECLARE_REFCOUNTED_CLASS(THashTableChunkIndexWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TChunkIndexesWriterConfig)
+DECLARE_REFCOUNTED_CLASS(TSlimVersionedWriterConfig)
+
+DECLARE_REFCOUNTED_CLASS(TChunkWriterTestingOptions)
+
+DECLARE_REFCOUNTED_CLASS(TChunkReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TChunkWriterConfig)
+
+DECLARE_REFCOUNTED_CLASS(TKeyFilterWriterConfig)
+
+DECLARE_REFCOUNTED_CLASS(TBatchHunkReaderConfig)
+
+DECLARE_REFCOUNTED_CLASS(TTableReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TTableWriterConfig)
+
+DECLARE_REFCOUNTED_CLASS(TRetentionConfig)
+
+DECLARE_REFCOUNTED_CLASS(TTypeConversionConfig)
+DECLARE_REFCOUNTED_CLASS(TInsertRowsFormatConfig)
+
+DECLARE_REFCOUNTED_CLASS(TChunkReaderOptions)
+DECLARE_REFCOUNTED_CLASS(TChunkWriterOptions)
+
+DECLARE_REFCOUNTED_CLASS(TVersionedRowDigestConfig)
+
+class TSaveContext;
+class TLoadContext;
+using TPersistenceContext = TCustomPersistenceContext<TSaveContext, TLoadContext>;
+
+struct IWireProtocolReader;
+struct IWireProtocolWriter;
+
+using TSchemaData = std::vector<ui32>;
+
+DECLARE_REFCOUNTED_STRUCT(IWireProtocolRowsetReader)
+DECLARE_REFCOUNTED_STRUCT(IWireProtocolRowsetWriter)
+
+DECLARE_REFCOUNTED_STRUCT(IUnversionedRowBatch)
+DECLARE_REFCOUNTED_STRUCT(IUnversionedColumnarRowBatch)
+DECLARE_REFCOUNTED_STRUCT(IVersionedRowBatch)
+
+struct IValueConsumer;
+struct IFlushableValueConsumer;
+
+class TComplexTypeFieldDescriptor;
+
+DECLARE_REFCOUNTED_CLASS(TLogicalType)
+class TSimpleLogicalType;
+class TDecimalLogicalType;
+class TOptionalLogicalType;
+class TListLogicalType;
+class TStructLogicalType;
+class TTupleLogicalType;
+class TVariantTupleLogicalType;
+class TVariantStructLogicalType;
+class TDictLogicalType;
+class TTaggedLogicalType;
+
+struct TStructField;
+
+struct IRecordDescriptor;
+
+//! Enumeration is used to describe compatibility of two schemas (or logical types).
+//! Such compatibility tests are performed before altering table schema or before merge operation.
+DEFINE_ENUM(ESchemaCompatibility,
+ // Values are incompatible.
+ // E.g. Int8 and String.
+ (Incompatible)
+
+ // Values that satisfy old schema MIGHT satisfy new schema, dynamic check is required.
+ // E.g. Optional<Int8> and Int8, in this case we must check that value of old type is not NULL.
+ (RequireValidation)
+
+ // Values that satisfy old schema ALWAYS satisfy new schema.
+ // E.g. Int32 and Int64
+ (FullyCompatible)
+);
+
+static constexpr TMasterTableSchemaId NullTableSchemaId = TMasterTableSchemaId();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/record_codegen_cpp.cpp b/yt/yt/client/table_client/record_codegen_cpp.cpp
new file mode 100644
index 0000000000..e2a3a3816d
--- /dev/null
+++ b/yt/yt/client/table_client/record_codegen_cpp.cpp
@@ -0,0 +1,36 @@
+#include "record_codegen_cpp.h"
+
+namespace NYT::NTableClient::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateKeyValueCount(TLegacyKey key, int count)
+{
+ if (static_cast<int>(key.GetCount()) != count) {
+ THROW_ERROR_EXCEPTION("Invalid number of key values: expected %v, got %v",
+ count,
+ key.GetCount());
+ }
+}
+
+int GetColumnIdOrThrow(std::optional<int> optionalId, TStringBuf name)
+{
+ if (!optionalId) {
+ THROW_ERROR_EXCEPTION("Column %Qv is not registered",
+ name);
+ }
+ return *optionalId;
+}
+
+void ValidateRowValueCount(TUnversionedRow row, int id)
+{
+ if (static_cast<int>(row.GetCount()) < id) {
+ THROW_ERROR_EXCEPTION("Too few values in row: expected > %v, actual %v",
+ id,
+ row.GetCount());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient::NDetail
diff --git a/yt/yt/client/table_client/record_codegen_cpp.h b/yt/yt/client/table_client/record_codegen_cpp.h
new file mode 100644
index 0000000000..7ff6afa1bb
--- /dev/null
+++ b/yt/yt/client/table_client/record_codegen_cpp.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <yt/yt/client/table_client/comparator.h>
+#include <yt/yt/client/table_client/helpers.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_base.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <array>
+
+namespace NYT::NTableClient::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateKeyValueCount(TLegacyKey key, int count);
+int GetColumnIdOrThrow(std::optional<int> optionalId, TStringBuf name);
+void ValidateRowValueCount(TUnversionedRow row, int id);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient::NDetail
diff --git a/yt/yt/client/table_client/record_helpers-inl.h b/yt/yt/client/table_client/record_helpers-inl.h
new file mode 100644
index 0000000000..ce35d20e35
--- /dev/null
+++ b/yt/yt/client/table_client/record_helpers-inl.h
@@ -0,0 +1,166 @@
+#ifndef RECORD_HELPERS_INL_H_
+#error "Direct inclusion of this file is not allowed, include record_helpers.h"
+// For the sake of sane code completion.
+#include "record_helpers.h"
+#endif
+#undef RECORD_HELPERS_INL_H_
+
+#include "row_buffer.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+void ValidateRowNotNull(TUnversionedRow row);
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRecord>
+TRecord ToRecord(
+ TUnversionedRow row,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ NTableClient::NDetail::ValidateRowNotNull(row);
+ return TRecord::FromUnversionedRow(row, idMapping);
+}
+
+template <class TRecord>
+std::vector<TRecord> ToRecords(
+ TRange<TUnversionedRow> rows,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ std::vector<TRecord> records;
+ records.reserve(rows.size());
+ for (auto row : rows) {
+ records.push_back(ToRecord<TRecord>(row, idMapping));
+ }
+ return records;
+}
+
+template <class TRecord>
+std::vector<TRecord> ToRecords(const NApi::IUnversionedRowsetPtr& rowset)
+{
+ typename TRecord::TRecordDescriptor::TIdMapping idMapping(rowset->GetNameTable());
+ return ToRecords<TRecord>(rowset->GetRows(), idMapping);
+}
+
+template <class TRecord>
+std::optional<TRecord> ToOptionalRecord(
+ TUnversionedRow row,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ if (!row) {
+ return std::nullopt;
+ }
+ return ToRecord<TRecord>(row, idMapping);
+}
+
+template <class TRecord>
+std::vector<std::optional<TRecord>> ToOptionalRecords(
+ TRange<TUnversionedRow> rows,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ std::vector<std::optional<TRecord>> records;
+ records.reserve(rows.size());
+ for (auto row : rows) {
+ records.push_back(ToOptionalRecord<TRecord>(row, idMapping));
+ }
+ return records;
+}
+
+template <class TRecord>
+std::vector<std::optional<TRecord>> ToOptionalRecords(const NApi::IUnversionedRowsetPtr& rowset)
+{
+ typename TRecord::TRecordDescriptor::TIdMapping idMapping(rowset->GetNameTable());
+ return ToOptionalRecords<TRecord>(rowset->GetRows(), idMapping);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRecord>
+TUnversionedRow FromRecord(
+ const TRecord& record,
+ const TRowBufferPtr& rowBuffer,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ return record.ToUnversionedRow(rowBuffer, idMapping);
+}
+
+template <class TRecord>
+TUnversionedOwningRow FromRecord(
+ const TRecord& record,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ // TODO(babenko): optimize
+ auto rowBuffer = New<TRowBuffer>(TDefaultRowBufferPoolTag(), 256);
+ return TUnversionedOwningRow(FromRecord(record, rowBuffer, idMapping));
+}
+
+template <class TRecord>
+TSharedRange<TUnversionedRow> FromRecords(
+ TRange<TRecord> records,
+ const TRowBufferPtr& rowBuffer,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ std::vector<TUnversionedRow> rows;
+ rows.reserve(records.size());
+ for (const auto& record : records) {
+ rows.push_back(FromRecord(record, rowBuffer, idMapping));
+ }
+ return MakeSharedRange(std::move(rows), rowBuffer);
+}
+
+template <class TRecord>
+TSharedRange<TUnversionedRow> FromRecords(
+ TRange<TRecord> records,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping)
+{
+ return FromRecords(records, New<TRowBuffer>(), idMapping);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRecordKey>
+TLegacyKey FromRecordKey(
+ const TRecordKey& recordKey,
+ const TRowBufferPtr& rowBuffer)
+{
+ return recordKey.ToKey(rowBuffer);
+}
+
+template <class TRecordKey>
+TLegacyOwningKey FromRecordKey(
+ const TRecordKey& recordKey)
+{
+ // TODO(babenko): optimize
+ auto rowBuffer = New<TRowBuffer>(TDefaultRowBufferPoolTag(), 256);
+ return TUnversionedOwningRow(FromRecordKey(recordKey, rowBuffer));
+}
+
+template <class TRecordKey>
+TSharedRange<TLegacyKey> FromRecordKeys(
+ TRange<TRecordKey> recordKeys,
+ const TRowBufferPtr& rowBuffer)
+{
+ std::vector<TLegacyKey> keys;
+ keys.reserve(recordKeys.size());
+ for (const auto& recordKey : recordKeys) {
+ keys.push_back(FromRecordKey(recordKey, rowBuffer));
+ }
+ return MakeSharedRange(std::move(keys), rowBuffer);
+}
+
+template <class TRecordKey>
+TSharedRange<TLegacyKey> FromRecordKeys(
+ TRange<TRecordKey> recordKeys)
+{
+ return FromRecordKeys(recordKeys, New<TRowBuffer>());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/record_helpers.cpp b/yt/yt/client/table_client/record_helpers.cpp
new file mode 100644
index 0000000000..67842db2ee
--- /dev/null
+++ b/yt/yt/client/table_client/record_helpers.cpp
@@ -0,0 +1,20 @@
+#include "record_helpers.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+void ValidateRowNotNull(TUnversionedRow row)
+{
+ if (!row) {
+ THROW_ERROR_EXCEPTION("Row must not be null");
+ }
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/record_helpers.h b/yt/yt/client/table_client/record_helpers.h
new file mode 100644
index 0000000000..1ef7982a33
--- /dev/null
+++ b/yt/yt/client/table_client/record_helpers.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include "unversioned_row.h"
+
+#include <yt/yt/client/api/rowset.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRecord>
+TRecord ToRecord(
+ TUnversionedRow row,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping);
+
+template <class TRecord>
+std::vector<TRecord> ToRecords(
+ TRange<TUnversionedRow> rows,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping);
+
+template <class TRecord>
+std::vector<TRecord> ToRecords(const NApi::IUnversionedRowsetPtr& rowset);
+
+template <class TRecord>
+std::optional<TRecord> ToOptionalRecord(
+ TUnversionedRow row,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping);
+
+template <class TRecord>
+std::vector<std::optional<TRecord>> ToOptionalRecords(
+ TRange<TUnversionedRow> rows,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping);
+
+template <class TRecord>
+std::vector<std::optional<TRecord>> ToOptionalRecords(const NApi::IUnversionedRowsetPtr& rowset);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRecord>
+TUnversionedRow FromRecord(
+ const TRecord& record,
+ const TRowBufferPtr& rowBuffer,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping =
+ TRecord::TRecordDescriptor::Get()->GetIdMapping());
+
+template <class TRecord>
+TUnversionedOwningRow FromRecord(
+ const TRecord& record,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping =
+ TRecord::TRecordDescriptor::Get()->GetIdMapping());
+
+template <class TRecord>
+TSharedRange<TUnversionedRow> FromRecords(
+ TRange<TRecord> records,
+ const TRowBufferPtr& rowBuffer,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping =
+ TRecord::TRecordDescriptor::Get()->GetIdMapping());
+
+template <class TRecord>
+TSharedRange<TUnversionedRow> FromRecords(
+ TRange<TRecord> records,
+ const typename TRecord::TRecordDescriptor::TIdMapping& idMapping =
+ TRecord::TRecordDescriptor::Get()->GetIdMapping());
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRecordKey>
+TLegacyKey FromRecordKey(
+ const TRecordKey& recordKey,
+ const TRowBufferPtr& rowBuffer);
+
+template <class TRecordKey>
+TLegacyOwningKey FromRecordKey(
+ const TRecordKey& recordKey);
+
+template <class TRecordKey>
+TSharedRange<TLegacyKey> FromRecordKeys(
+ TRange<TRecordKey> recordKeys,
+ const TRowBufferPtr& rowBuffer);
+
+template <class TRecordKey>
+TSharedRange<TLegacyKey> FromRecordKeys(
+ TRange<TRecordKey> recordKeys);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+#define RECORD_HELPERS_INL_H_
+#include "record_helpers-inl.h"
+#undef RECORD_HELPERS_INL_H_
diff --git a/yt/yt/client/table_client/row_base.cpp b/yt/yt/client/table_client/row_base.cpp
new file mode 100644
index 0000000000..2eb15d37fd
--- /dev/null
+++ b/yt/yt/client/table_client/row_base.cpp
@@ -0,0 +1,189 @@
+#include "row_base.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void PrintTo(EValueType type, std::ostream* os)
+{
+ *os << ToString(type);
+}
+
+void PrintTo(ESimpleLogicalValueType type, std::ostream* os)
+{
+ *os << ToString(type);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TColumnFilter::TColumnFilter()
+ : Universal_(true)
+{ }
+
+TColumnFilter::TColumnFilter(const std::initializer_list<int>& indexes)
+ : Universal_(false)
+ , Indexes_(indexes.begin(), indexes.end())
+{ }
+
+TColumnFilter::TColumnFilter(TIndexes&& indexes)
+ : Universal_(false)
+ , Indexes_(std::move(indexes))
+{ }
+
+TColumnFilter::TColumnFilter(const std::vector<int>& indexes)
+ : Universal_(false)
+ , Indexes_(indexes.begin(), indexes.end())
+{ }
+
+TColumnFilter::TColumnFilter(int schemaColumnCount)
+ : Universal_(false)
+{
+ for (int i = 0; i < schemaColumnCount; ++i) {
+ Indexes_.push_back(i);
+ }
+}
+
+std::optional<int> TColumnFilter::FindPosition(int columnIndex) const
+{
+ if (Universal_) {
+ THROW_ERROR_EXCEPTION("Unable to search index in column filter with IsUniversal flag");
+ }
+ auto it = std::find(Indexes_.begin(), Indexes_.end(), columnIndex);
+ if (it == Indexes_.end()) {
+ return std::nullopt;
+ }
+ return std::distance(Indexes_.begin(), it);
+}
+
+int TColumnFilter::GetPosition(int columnIndex) const
+{
+ if (auto indexOrNull = FindPosition(columnIndex)) {
+ return *indexOrNull;
+ }
+
+ THROW_ERROR_EXCEPTION("Column filter does not contain column index %Qv", columnIndex);
+}
+
+bool TColumnFilter::ContainsIndex(int columnIndex) const
+{
+ if (Universal_) {
+ return true;
+ }
+
+ return std::find(Indexes_.begin(), Indexes_.end(), columnIndex) != Indexes_.end();
+}
+
+const TColumnFilter::TIndexes& TColumnFilter::GetIndexes() const
+{
+ YT_VERIFY(!Universal_);
+ return Indexes_;
+}
+
+bool TColumnFilter::IsUniversal() const
+{
+ return Universal_;
+}
+
+const TColumnFilter& TColumnFilter::MakeUniversal()
+{
+ static const TColumnFilter Result;
+ return Result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateDataValueType(EValueType type)
+{
+ if (type != EValueType::Int64 &&
+ type != EValueType::Uint64 &&
+ type != EValueType::Double &&
+ type != EValueType::Boolean &&
+ type != EValueType::String &&
+ type != EValueType::Any &&
+ type != EValueType::Composite &&
+ type != EValueType::Null)
+ {
+ THROW_ERROR_EXCEPTION("Invalid data value type %Qlv", type);
+ }
+}
+
+void ValidateKeyValueType(EValueType type)
+{
+ if (type != EValueType::Int64 &&
+ type != EValueType::Uint64 &&
+ type != EValueType::Double &&
+ type != EValueType::Boolean &&
+ type != EValueType::String &&
+ type != EValueType::Composite &&
+ type != EValueType::Null &&
+ type != EValueType::Min &&
+ type != EValueType::Max)
+ {
+ THROW_ERROR_EXCEPTION("Invalid key value type %Qlv", type);
+ }
+}
+
+void ValidateSchemaValueType(EValueType type)
+{
+ if (type != EValueType::Null &&
+ type != EValueType::Int64 &&
+ type != EValueType::Uint64 &&
+ type != EValueType::Double &&
+ type != EValueType::Boolean &&
+ type != EValueType::String &&
+ type != EValueType::Any &&
+ type != EValueType::Composite)
+ {
+ THROW_ERROR_EXCEPTION("Invalid value type %Qlv", type);
+ }
+}
+
+void ValidateColumnFilter(const TColumnFilter& columnFilter, int schemaColumnCount)
+{
+ if (columnFilter.IsUniversal()) {
+ return;
+ }
+
+ TCompactVector<bool, TypicalColumnCount> flags;
+ flags.resize(schemaColumnCount);
+
+ for (int index : columnFilter.GetIndexes()) {
+ if (index < 0 || index >= schemaColumnCount) {
+ THROW_ERROR_EXCEPTION("Column filter contains invalid index: actual %v, expected in range [0, %v]",
+ index,
+ schemaColumnCount - 1);
+ }
+ if (flags[index]) {
+ THROW_ERROR_EXCEPTION("Column filter contains duplicate index %v",
+ index);
+ }
+ flags[index] = true;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TColumnFilter& columnFilter)
+{
+ if (columnFilter.IsUniversal()) {
+ return TString("{All}");
+ } else {
+ return Format("{%v}", columnFilter.GetIndexes());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+[[noreturn]] void ThrowUnexpectedValueType(EValueType valueType)
+{
+ THROW_ERROR_EXCEPTION("Unexpected value type %Qlv",
+ valueType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/row_base.h b/yt/yt/client/table_client/row_base.h
new file mode 100644
index 0000000000..b353edd1cf
--- /dev/null
+++ b/yt/yt/client/table_client/row_base.h
@@ -0,0 +1,458 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM_WITH_UNDERLYING_TYPE(EValueType, ui8,
+ ((Min) (0x00))
+
+ ((TheBottom) (0x01))
+ ((Null) (0x02))
+
+ ((Int64) (0x03))
+ ((Uint64) (0x04))
+ ((Double) (0x05))
+ ((Boolean) (0x06))
+
+ ((String) (0x10))
+ ((Any) (0x11))
+
+ ((Composite) (0x12))
+
+ ((Max) (0xef))
+);
+
+static_assert(
+ EValueType::Int64 < EValueType::Uint64 &&
+ EValueType::Uint64 < EValueType::Double,
+ "Incorrect type order.");
+
+DEFINE_BIT_ENUM_WITH_UNDERLYING_TYPE(EValueFlags, ui8,
+ ((None) (0x0000))
+ ((Aggregate) (0x0001))
+ ((Hunk) (0x0002))
+);
+
+DEFINE_ENUM_WITH_UNDERLYING_TYPE(ESimpleLogicalValueType, ui32,
+ ((Null) (0x02))
+
+ ((Int64) (0x03))
+ ((Uint64) (0x04))
+ ((Double) (0x05))
+ ((Boolean) (0x06))
+
+ ((String) (0x10))
+ ((Any) (0x11))
+
+ ((Int8) (0x1000))
+ ((Uint8) (0x1001))
+
+ ((Int16) (0x1003))
+ ((Uint16) (0x1004))
+
+ ((Int32) (0x1005))
+ ((Uint32) (0x1006))
+
+ ((Utf8) (0x1007))
+
+ ((Date) (0x1008))
+ ((Datetime) (0x1009))
+ ((Timestamp) (0x100a))
+ ((Interval) (0x100b))
+
+ ((Void) (0x100c))
+
+ ((Float) (0x100d))
+ ((Json) (0x100e))
+
+ ((Uuid) (0x100f))
+);
+
+//! Debug printers for Gtest unittests.
+void PrintTo(EValueType type, std::ostream* os);
+void PrintTo(ESimpleLogicalValueType type, std::ostream* os);
+
+inline bool IsIntegralTypeSigned(ESimpleLogicalValueType type)
+{
+ switch (type) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+ return true;
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+ return false;
+ default:
+ YT_ABORT();
+ }
+}
+
+inline bool IsIntegralType(ESimpleLogicalValueType type)
+{
+ switch (type) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Int64:
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ case ESimpleLogicalValueType::Uint64:
+ return true;
+ default:
+ return false;
+ }
+}
+
+inline bool IsStringLikeType(ESimpleLogicalValueType type)
+{
+ switch (type) {
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Any:
+ case ESimpleLogicalValueType::Utf8:
+ case ESimpleLogicalValueType::Json:
+ case ESimpleLogicalValueType::Uuid:
+ return true;
+ default:
+ return false;
+ }
+}
+
+inline int GetIntegralTypeBitWidth(ESimpleLogicalValueType type)
+{
+ switch (type) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Uint8:
+ return 8;
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Uint16:
+ return 16;
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Uint32:
+ return 32;
+ case ESimpleLogicalValueType::Int64:
+ case ESimpleLogicalValueType::Uint64:
+ return 64;
+ default:
+ YT_ABORT();
+ }
+}
+
+inline int GetIntegralTypeByteSize(ESimpleLogicalValueType type)
+{
+ switch (type) {
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Uint8:
+ return 1;
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Uint16:
+ return 2;
+ case ESimpleLogicalValueType::Int32:
+ case ESimpleLogicalValueType::Uint32:
+ return 4;
+ case ESimpleLogicalValueType::Int64:
+ case ESimpleLogicalValueType::Uint64:
+ return 8;
+ default:
+ YT_ABORT();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline constexpr EValueType GetPhysicalType(ESimpleLogicalValueType type)
+{
+ switch (type) {
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Int64:
+ case ESimpleLogicalValueType::Uint64:
+ case ESimpleLogicalValueType::Double:
+ case ESimpleLogicalValueType::Boolean:
+ case ESimpleLogicalValueType::String:
+ case ESimpleLogicalValueType::Any:
+ return static_cast<EValueType>(type);
+
+ case ESimpleLogicalValueType::Int8:
+ case ESimpleLogicalValueType::Int16:
+ case ESimpleLogicalValueType::Int32:
+ return EValueType::Int64;
+
+ case ESimpleLogicalValueType::Uint8:
+ case ESimpleLogicalValueType::Uint16:
+ case ESimpleLogicalValueType::Uint32:
+ return EValueType::Uint64;
+
+ case ESimpleLogicalValueType::Utf8:
+ case ESimpleLogicalValueType::Json:
+ case ESimpleLogicalValueType::Uuid:
+ return EValueType::String;
+ case ESimpleLogicalValueType::Date:
+ case ESimpleLogicalValueType::Datetime:
+ case ESimpleLogicalValueType::Timestamp:
+ return EValueType::Uint64;
+ case ESimpleLogicalValueType::Interval:
+ return EValueType::Int64;
+
+ case ESimpleLogicalValueType::Void:
+ return EValueType::Null;
+
+ case ESimpleLogicalValueType::Float:
+ return EValueType::Double;
+
+ default:
+ YT_ABORT();
+ }
+}
+
+inline ESimpleLogicalValueType GetLogicalType(EValueType type)
+{
+ switch (type) {
+ case EValueType::Null:
+ case EValueType::Int64:
+ case EValueType::Uint64:
+ case EValueType::Double:
+ case EValueType::Boolean:
+ case EValueType::String:
+ case EValueType::Any:
+ return static_cast<ESimpleLogicalValueType>(type);
+
+ case EValueType::Composite:
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ THROW_ERROR_EXCEPTION("Value type %Qlv has no corresponding logical type",
+ type);
+
+ default:
+ YT_ABORT();
+ }
+}
+
+inline constexpr bool IsIntegralType(EValueType type)
+{
+ return type == EValueType::Int64 || type == EValueType::Uint64;
+}
+
+inline constexpr bool IsArithmeticType(EValueType type)
+{
+ return IsIntegralType(type) || type == EValueType::Double;
+}
+
+inline constexpr bool IsAnyOrComposite(EValueType type)
+{
+ return type == EValueType::Any || type == EValueType::Composite;
+}
+
+inline constexpr bool IsStringLikeType(EValueType type)
+{
+ return type == EValueType::String || IsAnyOrComposite(type);
+}
+
+inline constexpr bool IsValueType(EValueType type)
+{
+ return
+ type == EValueType::Int64 ||
+ type == EValueType::Uint64 ||
+ type == EValueType::Double ||
+ type == EValueType::Boolean ||
+ type == EValueType::String ||
+ type == EValueType::Any;
+}
+
+inline constexpr bool IsAnyColumnCompatibleType(EValueType type)
+{
+ return
+ type == EValueType::Null ||
+ type == EValueType::Int64 ||
+ type == EValueType::Uint64 ||
+ type == EValueType::Double ||
+ type == EValueType::Boolean ||
+ type == EValueType::String ||
+ type == EValueType::Any ||
+ type == EValueType::Composite;
+}
+
+inline constexpr bool IsSentinelType(EValueType type)
+{
+ return type == EValueType::Min || type == EValueType::Max;
+}
+
+inline constexpr bool IsPrimitiveType(EValueType type)
+{
+ return
+ type == EValueType::Int64 ||
+ type == EValueType::Uint64 ||
+ type == EValueType::Double ||
+ type == EValueType::Boolean ||
+ type == EValueType::String;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This class contains ordered collection of indexes of columns of some table.
+// Position in context of the class means position of some column index in ordered collection.
+class TColumnFilter
+{
+public:
+ using TIndexes = TCompactVector<int, TypicalColumnCount>;
+
+ TColumnFilter();
+ TColumnFilter(const std::initializer_list<int>& indexes);
+ explicit TColumnFilter(TIndexes&& indexes);
+ explicit TColumnFilter(const std::vector<int>& indexes);
+ explicit TColumnFilter(int schemaColumnCount);
+
+ bool ContainsIndex(int columnIndex) const;
+ int GetPosition(int columnIndex) const;
+ std::optional<int> FindPosition(int columnIndex) const;
+ const TIndexes& GetIndexes() const;
+
+ static const TColumnFilter& MakeUniversal();
+ bool IsUniversal() const;
+
+private:
+ bool Universal_;
+ TIndexes Indexes_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TColumnFilter& columnFilter);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTypeErasedRow
+{
+ const void* OpaqueHeader;
+
+ explicit operator bool() const
+ {
+ return OpaqueHeader != nullptr;
+ }
+};
+
+static_assert(std::is_pod<TTypeErasedRow>::value, "TTypeErasedRow must be POD.");
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Checks that #type is allowed to appear in data. Throws on failure.
+void ValidateDataValueType(EValueType type);
+
+//! Checks that #type is allowed to appear in keys. Throws on failure.
+void ValidateKeyValueType(EValueType type);
+
+//! Checks that #type is allowed to appear in schema. Throws on failure.
+void ValidateSchemaValueType(EValueType type);
+
+//! Checks that column filter contains indexes in range |[0, schemaColumnCount - 1]|.
+void ValidateColumnFilter(const TColumnFilter& columnFilter, int schemaColumnCount);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+constexpr TValue MakeSentinelValue(EValueType type, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ TValue result{};
+ result.Id = id;
+ result.Type = type;
+ result.Flags = flags;
+ return result;
+}
+
+template <class TValue>
+constexpr TValue MakeNullValue(int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ TValue result{};
+ result.Id = id;
+ result.Type = EValueType::Null;
+ result.Flags = flags;
+ return result;
+}
+
+template <class TValue>
+constexpr TValue MakeInt64Value(i64 value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ TValue result{};
+ result.Id = id;
+ result.Type = EValueType::Int64;
+ result.Flags = flags;
+ result.Data.Int64 = value;
+ return result;
+}
+
+template <class TValue>
+constexpr TValue MakeUint64Value(ui64 value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ TValue result{};
+ result.Id = id;
+ result.Type = EValueType::Uint64;
+ result.Flags = flags;
+ result.Data.Uint64 = value;
+ return result;
+}
+
+template <class TValue>
+constexpr TValue MakeDoubleValue(double value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ TValue result{};
+ result.Id = id;
+ result.Type = EValueType::Double;
+ result.Flags = flags;
+ result.Data.Double = value;
+ return result;
+}
+
+template <class TValue>
+constexpr TValue MakeBooleanValue(bool value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ TValue result{};
+ result.Id = id;
+ result.Type = EValueType::Boolean;
+ result.Flags = flags;
+ result.Data.Boolean = value;
+ return result;
+}
+
+template <class TValue>
+constexpr TValue MakeStringLikeValue(EValueType valueType, TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ TValue result{};
+ result.Id = id;
+ result.Type = valueType;
+ result.Flags = flags;
+ result.Length = value.length();
+ result.Data.String = value.begin();
+ return result;
+}
+
+template <class TValue>
+constexpr TValue MakeStringValue(TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeStringLikeValue<TValue>(EValueType::String, value, id, flags);
+}
+
+template <class TValue>
+constexpr TValue MakeAnyValue(TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeStringLikeValue<TValue>(EValueType::Any, value, id, flags);
+}
+
+template <class TValue>
+constexpr TValue MakeCompositeValue(TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeStringLikeValue<TValue>(EValueType::Composite, value, id, flags);
+}
+
+[[noreturn]] void ThrowUnexpectedValueType(EValueType valueType);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/row_batch-inl.h b/yt/yt/client/table_client/row_batch-inl.h
new file mode 100644
index 0000000000..c491a6bece
--- /dev/null
+++ b/yt/yt/client/table_client/row_batch-inl.h
@@ -0,0 +1,70 @@
+#ifndef ROW_BATCH_INL_H_
+#error "Direct inclusion of this file is not allowed, include row_batch.h"
+// For the sake of sane code completion.
+#include "row_batch.h"
+#endif
+#undef ROW_BATCH_INL_H_
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+TRange<T> IUnversionedColumnarRowBatch::TColumn::GetTypedValues() const
+{
+ YT_VERIFY(Values);
+ YT_VERIFY(Values->BitWidth == sizeof(T) * 8);
+ return TRange<T>(
+ reinterpret_cast<const T*>(Values->Data.Begin()),
+ reinterpret_cast<const T*>(Values->Data.End()));
+}
+
+
+template <class T>
+TRange<T> IUnversionedColumnarRowBatch::TColumn::GetRelevantTypedValues() const
+{
+ YT_VERIFY(Values);
+ YT_VERIFY(Values->BitWidth == sizeof(T) * 8);
+ YT_VERIFY(!Rle);
+ return TRange<T>(
+ reinterpret_cast<const T*>(Values->Data.Begin()) + StartIndex,
+ reinterpret_cast<const T*>(Values->Data.Begin()) + StartIndex + ValueCount);
+}
+
+inline TRef IUnversionedColumnarRowBatch::TColumn::GetBitmapValues() const
+{
+ YT_VERIFY(Values);
+ YT_VERIFY(Values->BitWidth == 1);
+ YT_VERIFY(Values->BaseValue == 0);
+ YT_VERIFY(!Values->ZigZagEncoded);
+ YT_VERIFY(!Rle);
+ YT_VERIFY(!Dictionary);
+ return Values->Data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+typename TRowBatchTrait<TRow>::IRowBatchPtr CreateBatchFromRows(
+ TSharedRange<TRow> rows)
+{
+ if constexpr (std::is_same_v<TRow, TVersionedRow>) {
+ return CreateBatchFromVersionedRows(rows);
+ } else {
+ return CreateBatchFromUnversionedRows(rows);
+ }
+}
+
+template <class TRow>
+typename TRowBatchTrait<TRow>::IRowBatchPtr CreateEmptyRowBatch()
+{
+ if constexpr (std::is_same_v<TRow, TVersionedRow>) {
+ return CreateEmptyVersionedRowBatch();
+ } else {
+ return CreateEmptyUnversionedRowBatch();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/row_batch.cpp b/yt/yt/client/table_client/row_batch.cpp
new file mode 100644
index 0000000000..64926a26fd
--- /dev/null
+++ b/yt/yt/client/table_client/row_batch.cpp
@@ -0,0 +1,144 @@
+#include "row_batch.h"
+#include "unversioned_row.h"
+#include "versioned_row.h"
+
+#include <atomic>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IUnversionedRowBatch::IsEmpty() const
+{
+ return GetRowCount() == 0;
+}
+
+IUnversionedColumnarRowBatchPtr IUnversionedRowBatch::TryAsColumnar()
+{
+ return dynamic_cast<IUnversionedColumnarRowBatch*>(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IUnversionedColumnarRowBatch::TDictionaryId IUnversionedColumnarRowBatch::GenerateDictionaryId()
+{
+ static std::atomic<TDictionaryId> CurrentId;
+ return ++CurrentId;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IUnversionedRowBatchPtr CreateBatchFromUnversionedRows(
+ TSharedRange<TUnversionedRow> rows)
+{
+ class TUnversionedRowBatch
+ : public IUnversionedRowBatch
+ {
+ public:
+ explicit TUnversionedRowBatch(TSharedRange<TUnversionedRow> rows)
+ : Rows_(std::move(rows))
+ { }
+
+ int GetRowCount() const override
+ {
+ return static_cast<int>(Rows_.size());
+ }
+
+ TSharedRange<TUnversionedRow> MaterializeRows() override
+ {
+ return Rows_;
+ }
+
+ private:
+ const TSharedRange<TUnversionedRow> Rows_;
+ };
+
+ return New<TUnversionedRowBatch>(rows);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IUnversionedRowBatchPtr CreateEmptyUnversionedRowBatch()
+{
+ class TUnversionedRowBatch
+ : public IUnversionedRowBatch
+ {
+ public:
+ int GetRowCount() const override
+ {
+ return 0;
+ }
+
+ TSharedRange<TUnversionedRow> MaterializeRows() override
+ {
+ return {};
+ }
+
+ };
+
+ return New<TUnversionedRowBatch>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IVersionedRowBatch::IsEmpty() const
+{
+ return GetRowCount() == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IVersionedRowBatchPtr CreateBatchFromVersionedRows(
+ TSharedRange<TVersionedRow> rows)
+{
+ class TVersionedRowBatch
+ : public IVersionedRowBatch
+ {
+ public:
+ explicit TVersionedRowBatch(TSharedRange<TVersionedRow> rows)
+ : Rows_(std::move(rows))
+ { }
+
+ int GetRowCount() const override
+ {
+ return static_cast<int>(Rows_.size());
+ }
+
+ TSharedRange<TVersionedRow> MaterializeRows() override
+ {
+ return Rows_;
+ }
+
+ private:
+ const TSharedRange<TVersionedRow> Rows_;
+ };
+
+ return New<TVersionedRowBatch>(rows);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IVersionedRowBatchPtr CreateEmptyVersionedRowBatch()
+{
+ class TVersionedRowBatch
+ : public IVersionedRowBatch
+ {
+ public:
+ int GetRowCount() const override
+ {
+ return 0;
+ }
+
+ TSharedRange<TVersionedRow> MaterializeRows() override
+ {
+ return {};
+ }
+
+ };
+
+ return New<TVersionedRowBatch>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/row_batch.h b/yt/yt/client/table_client/row_batch.h
new file mode 100644
index 0000000000..29ec2b53d5
--- /dev/null
+++ b/yt/yt/client/table_client/row_batch.h
@@ -0,0 +1,269 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/range.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+#include <vector>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IUnversionedRowBatch
+ : public virtual TRefCounted
+{
+ //! Returns the number of rows in the batch.
+ //! This call is cheap (in contrast to #IUnversionedRowBatch::MaterializeRows).
+ virtual int GetRowCount() const = 0;
+
+ //! A helper method that returns |true| iff #GetRowCount is zero.
+ bool IsEmpty() const;
+
+ //! Tries to dynamic-cast the instance to IUnversionedColumnarRowBatch;
+ //! returns null on failure.
+ IUnversionedColumnarRowBatchPtr TryAsColumnar();
+
+ //! Returns the rows representing the batch.
+ //! If the batch is columnar then the rows are materialized on first
+ //! call to #IUnversionedRowBatch::MaterializeRows. This call could be slow.
+ //! Invoking #IUnversionedColumnarRowBatch::MaterializeColumns after this call is forbidden.
+ virtual TSharedRange<TUnversionedRow> MaterializeRows() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IUnversionedRowBatch)
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IUnversionedColumnarRowBatch
+ : public IUnversionedRowBatch
+{
+ struct TValueBuffer;
+ struct TStringBuffer;
+ struct TBitmap;
+ struct TDictionaryEncoding;
+ struct TRleEncoding;
+ struct TColumn;
+
+ struct TValueBuffer
+ {
+ //! Bits per value: 8, 16, 32 or 64.
+ int BitWidth = -1;
+
+ //! For integer values, values in #Data must be adjusted by adding #BaseValue.
+ ui64 BaseValue = 0;
+
+ //! Indicates if zig-zag encoding is used for values.
+ bool ZigZagEncoded = false;
+
+ //! Memory region containing the values.
+ TRef Data;
+ };
+
+ struct TStringBuffer
+ {
+ //! Controls offset decoding.
+ /*
+ * If non-null then k-th value starts at offset
+ * 0 for k = 0;
+ * AvgLength * k + ZigZagDecode32(values[k - 1]) for k = 1, ..., n-1,
+ * where |values[i]| is the i-th raw unsigned 32-bit value stored in #TColumn::Values.
+ *
+ * Otherwise #TColumn::Values contains raw unsigned 32-bit offsets.
+ */
+ std::optional<ui32> AvgLength;
+
+ //! Memory region with string data. Offsets point here.
+ TRef Data;
+ };
+
+ struct TBitmap
+ {
+ //! Bits.
+ TRef Data;
+ };
+
+ //! An opaque dictionary id.
+ using TDictionaryId = ui64;
+
+ //! This dictionary id is invalid and cannot appear in #TDictionaryEncoding::Id.
+ static constexpr TDictionaryId NullDictionaryId = 0;
+
+ //! A helper for generating unique dictionary ids.
+ static TDictionaryId GenerateDictionaryId();
+
+ struct TDictionaryEncoding
+ {
+ //! The unique id of the dictionary.
+ //! Dictionary ids are never reused (within a process incarnation).
+ //! If you see a previously appearing ids then you could safely assume that this is exactly same dictionary.
+ TDictionaryId DictionaryId = NullDictionaryId;
+
+ //! If |true| then dictionary indexes are in fact 1-based; 0 in #TColumn::Values means null
+ //! and #TColumn::NullBitmap is not used; one should subtract 1 from value in #TColumn::Values
+ //! before dereferencing the dictionary.
+ bool ZeroMeansNull = false;
+
+ //! Contains dictionary values.
+ /*!
+ * Example (assuming ZeroMeansNull is true):
+ * Raw values: hello, world, <null>, world
+ * Index values: 1, 2, 0, 2
+ * Dictionary values: hello, world
+ */
+ const TColumn* ValueColumn;
+ };
+
+ struct TRleEncoding
+ {
+ //! Contains RLE-encoded values.
+ /*!
+ * Example:
+ * Raw values: 1, 1, 2, 3, 3, 3
+ * RLE values: 1, 2, 3
+ * RLE indexes: 0, 2, 3
+ */
+ const TColumn* ValueColumn;
+ };
+
+ struct TColumn
+ {
+ //! Id in name table.
+ //! -1 for non-root columns.
+ int Id = -1;
+
+ //! Index of the first relevant value in the column.
+ //! For non-RLE encoded columns, #Values typically provides a vector whose elements starting from #StartIndex
+ //! give the desired values. For dictionary-encoded columns, these elements are not actual values but rather
+ //! dictionary indexes (see #Dictionary).
+ //! For RLE encoded columns, #Values contains starting indexes of RLE segments and #StartIndex
+ //! must be compared against these indexes to obtain the relevant range of values (stored in #Rle).
+ i64 StartIndex = -1;
+
+ //! The number of relevant values in this column (starting from #StartIndex).
+ i64 ValueCount = -1;
+
+ //! The type of values in this column.
+ NTableClient::TLogicalTypePtr Type;
+
+ //! Bitmap with ones indicating nulls.
+ //! If both #NullBitmap and #Values are null then all values are null.
+ //! If just #NullBitmap is null then all values are non-null.
+ std::optional<TBitmap> NullBitmap;
+
+ //! If non-null then values are actually indexes in #Dictionary.
+ std::optional<TDictionaryEncoding> Dictionary;
+
+ //! If non-null then contiguous segments of coinciding #Values are collapsed.
+ //! #Rle describes the resulting values and #Values store RLE indexes.
+ std::optional<TRleEncoding> Rle;
+
+ //! Somewhat encoded values.
+ /*!
+ * Encoding proceeds as follows:
+ * 1) For signed integers, zig-zag encoding could be applied (see #TValueBuffer::ZigZagEncoded)
+ * 2) For integers, some base value could be subtracted (see #TValueBuffer::MinValue)
+ * 3) Dictionary encoding could be applied; in this case values are replaced with integer dictionary indexes
+ * 4) RLE encoding could be applied: in this case value repetitions are eliminated
+ */
+ std::optional<TValueBuffer> Values;
+
+ //! Contains string data and metadata for string-like values.
+ //! Null for other types.
+ std::optional<TStringBuffer> Strings;
+
+ //! A helper for accessing all #Values.
+ template <class T>
+ TRange<T> GetTypedValues() const;
+
+ //! Similar to #GetTypedValues but the returned range only covers the part
+ //! [#StartRowIndex, #StartRowIndex + #ValueCount). This is not applicable in RLE-encoded columns.
+ template <class T>
+ TRange<T> GetRelevantTypedValues() const;
+
+ //! Provides access to values as a bitmap.
+ TRef GetBitmapValues() const;
+ };
+
+ //! Returns the (root) columns representing the batch.
+ //! The batch must be columnar.
+ //! This call is fast.
+ //! Invoking #IUnversionedRowBatch::MaterializeRows after this call is forbidden.
+ virtual TRange<const TColumn*> MaterializeColumns() = 0;
+
+ //! Contains the ids of dictionaries that are guaranteed to never be used
+ //! past this batch. These ids, however, can appear in this very batch.
+ virtual TRange<TDictionaryId> GetRetiringDictionaryIds() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IUnversionedColumnarRowBatch)
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IVersionedRowBatch
+ : public virtual TRefCounted
+{
+ //! Returns the number of rows in the batch.
+ //! This call is cheap (in contrast to #IVersionedRowBatch::MaterializeRows).
+ virtual int GetRowCount() const = 0;
+
+ //! Returns the rows representing the batch.
+ virtual TSharedRange<TVersionedRow> MaterializeRows() = 0;
+
+ // Extension methods
+
+ //! Returns |true| iff #GetRowCount is zero.
+ bool IsEmpty() const;
+};
+
+DEFINE_REFCOUNTED_TYPE(IVersionedRowBatch)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IUnversionedRowBatchPtr CreateBatchFromUnversionedRows(
+ TSharedRange<TUnversionedRow> rows);
+
+IUnversionedRowBatchPtr CreateEmptyUnversionedRowBatch();
+
+////////////////////////////////////////////////////////////////////////////////
+
+IVersionedRowBatchPtr CreateBatchFromVersionedRows(
+ TSharedRange<TVersionedRow> rows);
+
+IVersionedRowBatchPtr CreateEmptyVersionedRowBatch();
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+struct TRowBatchTrait;
+
+template <>
+struct TRowBatchTrait<TUnversionedRow>
+{
+ using IRowBatchPtr = IUnversionedRowBatchPtr;
+};
+
+template <>
+struct TRowBatchTrait<TVersionedRow>
+{
+ using IRowBatchPtr = IVersionedRowBatchPtr;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TRow>
+typename TRowBatchTrait<TRow>::IRowBatchPtr CreateBatchFromRows(
+ TSharedRange<TRow> rows);
+
+template <class TRow>
+typename TRowBatchTrait<TRow>::IRowBatchPtr CreateEmptyRowBatch();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+#define ROW_BATCH_INL_H_
+#include "row_batch-inl.h"
+#undef ROW_BATCH_INL_H_
diff --git a/yt/yt/client/table_client/row_buffer.cpp b/yt/yt/client/table_client/row_buffer.cpp
new file mode 100644
index 0000000000..40b85f7f1f
--- /dev/null
+++ b/yt/yt/client/table_client/row_buffer.cpp
@@ -0,0 +1,310 @@
+#include "row_buffer.h"
+
+#include "schema.h"
+#include "unversioned_row.h"
+#include "versioned_row.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TChunkedMemoryPool* TRowBuffer::GetPool()
+{
+ return &Pool_;
+}
+
+TMutableUnversionedRow TRowBuffer::AllocateUnversioned(int valueCount)
+{
+ return TMutableUnversionedRow::Allocate(&Pool_, valueCount);
+}
+
+TMutableVersionedRow TRowBuffer::AllocateVersioned(
+ int keyCount,
+ int valueCount,
+ int writeTimestampCount,
+ int deleteTimestampCount)
+{
+ return TMutableVersionedRow::Allocate(
+ &Pool_,
+ keyCount,
+ valueCount,
+ writeTimestampCount,
+ deleteTimestampCount);
+}
+
+void TRowBuffer::CaptureValue(TUnversionedValue* value)
+{
+ if (IsStringLikeType(value->Type) && value->Data.String != nullptr) {
+ char* dst = Pool_.AllocateUnaligned(value->Length);
+ memcpy(dst, value->Data.String, value->Length);
+ value->Data.String = dst;
+ }
+}
+
+TVersionedValue TRowBuffer::CaptureValue(const TVersionedValue& value)
+{
+ auto capturedValue = value;
+ CaptureValue(&capturedValue);
+ return capturedValue;
+}
+
+TUnversionedValue TRowBuffer::CaptureValue(const TUnversionedValue& value)
+{
+ auto capturedValue = value;
+ CaptureValue(&capturedValue);
+ return capturedValue;
+}
+
+TMutableUnversionedRow TRowBuffer::CaptureRow(TUnversionedRow row, bool captureValues)
+{
+ if (!row) {
+ return TMutableUnversionedRow();
+ }
+
+ return CaptureRow(row.Elements(), captureValues);
+}
+
+void TRowBuffer::CaptureValues(TMutableUnversionedRow row)
+{
+ if (!row) {
+ return;
+ }
+
+ for (ui32 index = 0; index < row.GetCount(); ++index) {
+ CaptureValue(&row[index]);
+ }
+}
+
+TMutableUnversionedRow TRowBuffer::CaptureRow(TUnversionedValueRange values, bool captureValues)
+{
+ int count = std::ssize(values);
+ auto capturedRow = TMutableUnversionedRow::Allocate(&Pool_, count);
+ auto* capturedBegin = capturedRow.Begin();
+
+ ::memcpy(capturedBegin, values.Begin(), count * sizeof (TUnversionedValue));
+
+ if (captureValues) {
+ for (int index = 0; index < count; ++index) {
+ CaptureValue(&capturedBegin[index]);
+ }
+ }
+
+ return capturedRow;
+}
+
+std::vector<TMutableUnversionedRow> TRowBuffer::CaptureRows(TRange<TUnversionedRow> rows, bool captureValues)
+{
+ int rowCount = static_cast<int>(rows.Size());
+ std::vector<TMutableUnversionedRow> capturedRows(rowCount);
+ for (int index = 0; index < rowCount; ++index) {
+ capturedRows[index] = CaptureRow(rows[index], captureValues);
+ }
+ return capturedRows;
+}
+
+TMutableUnversionedRow TRowBuffer::CaptureAndPermuteRow(
+ TUnversionedRow row,
+ const TTableSchema& tableSchema,
+ int schemafulColumnCount,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer)
+{
+ int valueCount = schemafulColumnCount;
+
+ if (columnPresenceBuffer) {
+ ValidateDuplicateAndRequiredValueColumns(row, tableSchema, idMapping, columnPresenceBuffer);
+ }
+
+ for (const auto& value : row) {
+ ui16 originalId = value.Id;
+ YT_VERIFY(originalId < idMapping.size());
+ int mappedId = idMapping[originalId];
+ if (mappedId < 0) {
+ continue;
+ }
+ if (mappedId >= schemafulColumnCount) {
+ ++valueCount;
+ }
+ }
+
+ auto capturedRow = TMutableUnversionedRow::Allocate(&Pool_, valueCount);
+ for (int pos = 0; pos < schemafulColumnCount; ++pos) {
+ capturedRow[pos] = MakeUnversionedNullValue(pos);
+ }
+
+ valueCount = schemafulColumnCount;
+
+ for (const auto& value : row) {
+ ui16 originalId = value.Id;
+ int mappedId = idMapping[originalId];
+ if (mappedId < 0) {
+ continue;
+ }
+ int pos = mappedId < schemafulColumnCount ? mappedId : valueCount++;
+ capturedRow[pos] = value;
+ capturedRow[pos].Id = mappedId;
+ }
+
+ return capturedRow;
+}
+
+TMutableVersionedRow TRowBuffer::CaptureRow(TVersionedRow row, bool captureValues)
+{
+ if (!row) {
+ return TMutableVersionedRow();
+ }
+
+ auto capturedRow = TMutableVersionedRow::Allocate(
+ &Pool_,
+ row.GetKeyCount(),
+ row.GetValueCount(),
+ row.GetWriteTimestampCount(),
+ row.GetDeleteTimestampCount());
+ ::memcpy(capturedRow.BeginKeys(), row.BeginKeys(), sizeof(TUnversionedValue) * row.GetKeyCount());
+ ::memcpy(capturedRow.BeginValues(), row.BeginValues(), sizeof(TVersionedValue) * row.GetValueCount());
+ ::memcpy(capturedRow.BeginWriteTimestamps(), row.BeginWriteTimestamps(), sizeof(TTimestamp) * row.GetWriteTimestampCount());
+ ::memcpy(capturedRow.BeginDeleteTimestamps(), row.BeginDeleteTimestamps(), sizeof(TTimestamp) * row.GetDeleteTimestampCount());
+
+ if (captureValues) {
+ CaptureValues(capturedRow);
+ }
+
+ return capturedRow;
+}
+
+void TRowBuffer::CaptureValues(TMutableVersionedRow row)
+{
+ if (!row) {
+ return;
+ }
+
+ for (auto& value : row.Keys()) {
+ CaptureValue(&value);
+ }
+ for (auto& value : row.Values()) {
+ CaptureValue(&value);
+ }
+}
+
+TMutableVersionedRow TRowBuffer::CaptureAndPermuteRow(
+ TVersionedRow row,
+ const TTableSchema& tableSchema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer,
+ bool allowMissingKeyColumns)
+{
+ int keyColumnCount = tableSchema.GetKeyColumnCount();
+
+ if (!allowMissingKeyColumns) {
+ YT_VERIFY(keyColumnCount == row.GetKeyCount());
+ YT_VERIFY(keyColumnCount <= std::ssize(idMapping));
+ }
+
+ int valueCount = 0;
+ int deleteTimestampCount = row.GetDeleteTimestampCount();
+
+ TCompactVector<TTimestamp, 64> writeTimestamps;
+ for (const auto& value : row.Values()) {
+ ui16 originalId = value.Id;
+ YT_VERIFY(originalId < idMapping.size());
+ int mappedId = idMapping[originalId];
+ if (mappedId < 0) {
+ continue;
+ }
+ YT_VERIFY(mappedId < std::ssize(tableSchema.Columns()));
+ ++valueCount;
+ writeTimestamps.push_back(value.Timestamp);
+ }
+
+ std::sort(writeTimestamps.begin(), writeTimestamps.end(), std::greater<TTimestamp>());
+ writeTimestamps.erase(std::unique(writeTimestamps.begin(), writeTimestamps.end()), writeTimestamps.end());
+ int writeTimestampCount = static_cast<int>(writeTimestamps.size());
+
+ if (columnPresenceBuffer) {
+ ValidateDuplicateAndRequiredValueColumns(
+ row,
+ tableSchema,
+ idMapping,
+ columnPresenceBuffer,
+ writeTimestamps.data(),
+ writeTimestampCount);
+ }
+
+ auto capturedRow = TMutableVersionedRow::Allocate(
+ &Pool_,
+ keyColumnCount,
+ valueCount,
+ writeTimestampCount,
+ deleteTimestampCount);
+
+ ::memcpy(capturedRow.BeginWriteTimestamps(), writeTimestamps.data(), sizeof (TTimestamp) * writeTimestampCount);
+ ::memcpy(capturedRow.BeginDeleteTimestamps(), row.BeginDeleteTimestamps(), sizeof (TTimestamp) * deleteTimestampCount);
+
+ if (!allowMissingKeyColumns) {
+ int index = 0;
+ auto* dstValue = capturedRow.BeginKeys();
+ for (const auto* srcValue = row.BeginKeys(); srcValue != row.EndKeys(); ++srcValue, ++index) {
+ YT_VERIFY(idMapping[index] == index);
+ *dstValue++ = *srcValue;
+ }
+ } else {
+ for (int index = 0; index < keyColumnCount; ++index) {
+ capturedRow.Keys()[index] = MakeUnversionedNullValue(index);
+ }
+ for (const auto& srcValue : row.Keys()) {
+ ui16 originalId = srcValue.Id;
+ int mappedId = idMapping[originalId];
+ if (mappedId < 0) {
+ continue;
+ }
+ auto* dstValue = &capturedRow.Keys()[mappedId];
+ *dstValue = srcValue;
+ dstValue->Id = mappedId;
+ }
+ }
+
+ {
+ auto* dstValue = capturedRow.BeginValues();
+ for (const auto& srcValue : row.Values()) {
+ ui16 originalId = srcValue.Id;
+ int mappedId = idMapping[originalId];
+ if (mappedId < 0) {
+ continue;
+ }
+ *dstValue = srcValue;
+ dstValue->Id = mappedId;
+ ++dstValue;
+ }
+ }
+
+ return capturedRow;
+}
+
+void TRowBuffer::Absorb(TRowBuffer&& other)
+{
+ Pool_.Absorb(std::move(other.Pool_));
+}
+
+i64 TRowBuffer::GetSize() const
+{
+ return Pool_.GetSize();
+}
+
+i64 TRowBuffer::GetCapacity() const
+{
+ return Pool_.GetCapacity();
+}
+
+void TRowBuffer::Clear()
+{
+ Pool_.Clear();
+}
+
+void TRowBuffer::Purge()
+{
+ Pool_.Purge();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/row_buffer.h b/yt/yt/client/table_client/row_buffer.h
new file mode 100644
index 0000000000..48f6494016
--- /dev/null
+++ b/yt/yt/client/table_client/row_buffer.h
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "public.h"
+#include "unversioned_row.h"
+#include "versioned_row.h"
+
+#include <library/cpp/yt/memory/chunked_memory_pool.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TDefaultRowBufferPoolTag { };
+
+//! Holds data for a bunch of rows.
+/*!
+ * Acts as a ref-counted wrapped around TChunkedMemoryPool plus a bunch
+ * of helpers.
+ */
+class TRowBuffer
+ : public TRefCounted
+{
+public:
+ TRowBuffer(
+ TRefCountedTypeCookie tagCookie,
+ IMemoryChunkProviderPtr chunkProvider,
+ size_t startChunkSize = TChunkedMemoryPool::DefaultStartChunkSize)
+ : Pool_(
+ tagCookie,
+ std::move(chunkProvider),
+ startChunkSize)
+ { }
+
+ template <class TTag = TDefaultRowBufferPoolTag>
+ explicit TRowBuffer(
+ TTag = TDefaultRowBufferPoolTag(),
+ size_t startChunkSize = TChunkedMemoryPool::DefaultStartChunkSize)
+ : Pool_(
+ TTag(),
+ startChunkSize)
+ { }
+
+ template <class TTag>
+ TRowBuffer(
+ TTag,
+ IMemoryChunkProviderPtr chunkProvider)
+ : Pool_(
+ GetRefCountedTypeCookie<TTag>(),
+ std::move(chunkProvider))
+ { }
+
+ TChunkedMemoryPool* GetPool();
+
+ TMutableUnversionedRow AllocateUnversioned(int valueCount);
+ TMutableVersionedRow AllocateVersioned(
+ int keyCount,
+ int valueCount,
+ int writeTimestampCount,
+ int deleteTimestampCount);
+
+ void CaptureValue(TUnversionedValue* value);
+ TVersionedValue CaptureValue(const TVersionedValue& value);
+ TUnversionedValue CaptureValue(const TUnversionedValue& value);
+
+ TMutableUnversionedRow CaptureRow(TUnversionedRow row, bool captureValues = true);
+ void CaptureValues(TMutableUnversionedRow row);
+ TMutableUnversionedRow CaptureRow(TUnversionedValueRange values, bool captureValues = true);
+ std::vector<TMutableUnversionedRow> CaptureRows(TRange<TUnversionedRow> rows, bool captureValues = true);
+
+ TMutableVersionedRow CaptureRow(TVersionedRow row, bool captureValues = true);
+ void CaptureValues(TMutableVersionedRow row);
+
+ //! Captures the row applying #idMapping to value ids and placing values to the proper positions.
+ //! Skips values that map to negative ids via #idMapping.
+ //! The resulting row consists of a schemaful prefix consisting of values whose ids
+ //! (after remapping) are less than #schemafulColumnCount
+ //! and an arbitrarily-ordered suffix of values whose ids
+ //! (after remapping) are greater than or equal to #schemafulColumnCount.
+ TMutableUnversionedRow CaptureAndPermuteRow(
+ TUnversionedRow row,
+ const TTableSchema& tableSchema,
+ int schemafulColumnCount,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer);
+
+ //! Captures the row applying #idMapping to value ids.
+ //! #idMapping must be identity for key columns.
+ //! Skips values that map to negative ids via #idMapping.
+ TMutableVersionedRow CaptureAndPermuteRow(
+ TVersionedRow row,
+ const TTableSchema& tableSchema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer,
+ bool allowMissingKeyColumns = false);
+
+ i64 GetSize() const;
+ i64 GetCapacity() const;
+
+ //! Moves all the rows from other row buffer to the current one.
+ //! The other buffer becomes empty, like after Purge() call.
+ void Absorb(TRowBuffer&& other);
+
+ void Clear();
+ void Purge();
+
+private:
+ TChunkedMemoryPool Pool_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TRowBuffer)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/schema-inl.h b/yt/yt/client/table_client/schema-inl.h
new file mode 100644
index 0000000000..e2949f6aa9
--- /dev/null
+++ b/yt/yt/client/table_client/schema-inl.h
@@ -0,0 +1,227 @@
+#ifndef SCHEMA_INL_H_
+#error "Direct inclusion of this file is not allowed, include schema.h"
+// For the sake of sane code completion.
+#include "schema.h"
+#endif
+
+#include <yt/yt/core/misc/numeric_helpers.h>
+
+#include <library/cpp/yt/misc/cast.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline TLegacyLockMask::TLegacyLockMask(TLegacyLockBitmap value)
+ : Data_(value)
+{ }
+
+inline ELockType TLegacyLockMask::Get(int index) const
+{
+ return ELockType((Data_ >> (BitsPerType * index)) & TypeMask);
+}
+
+inline void TLegacyLockMask::Set(int index, ELockType lock)
+{
+ YT_VERIFY(lock <= MaxOldLockType);
+ Data_ &= ~(TypeMask << (BitsPerType * index));
+ Data_ |= static_cast<TLegacyLockBitmap>(lock) << (BitsPerType * index);
+}
+
+inline void TLegacyLockMask::Enrich(int columnCount)
+{
+ auto primaryLockType = Get(PrimaryLockIndex);
+ auto maxLockType = primaryLockType;
+ for (int index = 1; index < columnCount; ++index) {
+ auto lockType = Get(index);
+ Set(index, GetStrongestLock(primaryLockType, lockType));
+ maxLockType = GetStrongestLock(maxLockType, lockType);
+ }
+ Set(PrimaryLockIndex, maxLockType);
+}
+
+inline TLegacyLockBitmap TLegacyLockMask::GetBitmap() const
+{
+ return Data_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline TLockMask::TLockMask(TLockBitmap bitmap, int size)
+ : Bitmap_(std::move(bitmap))
+ , Size_(size)
+{ }
+
+inline ELockType TLockMask::Get(int index) const
+{
+ auto wordIndex = index / LocksPerWord;
+ if (wordIndex < std::ssize(Bitmap_)) {
+ auto wordPosition = index & (LocksPerWord - 1);
+ auto lock = (Bitmap_[wordIndex] >> (BitsPerType * wordPosition)) & LockMask;
+ return CheckedEnumCast<ELockType>(lock);
+ } else {
+ return ELockType::None;
+ }
+}
+
+inline void TLockMask::Set(int index, ELockType lock)
+{
+ if (index >= Size_) {
+ Reserve(index + 1);
+ }
+
+ auto& word = Bitmap_[index / LocksPerWord];
+ auto wordPosition = index & (LocksPerWord - 1);
+
+ word &= ~(LockMask << (BitsPerType * wordPosition));
+ word |= static_cast<ui64>(lock) << (BitsPerType * wordPosition);
+}
+
+inline void TLockMask::Enrich(int size)
+{
+ if (size > Size_) {
+ Reserve(size);
+ }
+ Size_ = size;
+
+ auto primaryLockType = Get(PrimaryLockIndex);
+ auto maxLockType = primaryLockType;
+ for (int index = 1; index < size; ++index) {
+ auto lockType = Get(index);
+ Set(index, GetStrongestLock(primaryLockType, lockType));
+ maxLockType = GetStrongestLock(maxLockType, lockType);
+ }
+ // TODO(gritukan): Do we really need both to promote all locks up to primary lock
+ // and promote primary lock to strongest of all locks in mask?
+ Set(PrimaryLockIndex, maxLockType);
+}
+
+inline int TLockMask::GetSize() const
+{
+ return Size_;
+}
+
+inline TLockBitmap TLockMask::GetBitmap() const
+{
+ return Bitmap_;
+}
+
+inline TLegacyLockMask TLockMask::ToLegacyMask() const
+{
+ TLegacyLockMask legacyMask;
+ for (int index = 0; index < TLegacyLockMask::MaxCount; ++index) {
+ legacyMask.Set(index, Get(index));
+ }
+
+ return legacyMask;
+}
+
+inline bool TLockMask::HasNewLocks() const
+{
+ for (int index = 0; index < Size_; ++index) {
+ if (Get(index) > MaxOldLockType) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline void TLockMask::Reserve(int size)
+{
+ YT_VERIFY(size < MaxSize);
+
+ int wordCount = DivCeil(size, LocksPerWord);
+ if (wordCount > std::ssize(Bitmap_)) {
+ Bitmap_.resize(wordCount);
+ }
+
+ Size_ = size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr bool operator < (ESchemaCompatibility lhs, ESchemaCompatibility rhs)
+{
+ return static_cast<int>(lhs) < static_cast<int>(rhs);
+}
+
+constexpr bool operator > (ESchemaCompatibility lhs, ESchemaCompatibility rhs)
+{
+ return rhs < lhs;
+}
+
+constexpr bool operator <= (ESchemaCompatibility lhs, ESchemaCompatibility rhs)
+{
+ return !(rhs < lhs);
+}
+
+constexpr bool operator >= (ESchemaCompatibility lhs, ESchemaCompatibility rhs)
+{
+ return !(lhs < rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline size_t TTableSchemaHash::operator() (const TTableSchema& schema) const
+{
+ return THash<TTableSchema>()(schema);
+}
+
+inline size_t TTableSchemaHash::operator() (const TTableSchemaPtr& schema) const
+{
+ return THash<TTableSchema>()(*schema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline bool TTableSchemaEquals::operator() (const TTableSchema& lhs, const TTableSchema& rhs) const
+{
+ return lhs == rhs;
+}
+
+inline bool TTableSchemaEquals::operator() (const TTableSchemaPtr& lhs, const TTableSchemaPtr& rhs) const
+{
+ return *lhs == *rhs;
+}
+
+inline bool TTableSchemaEquals::operator() (const TTableSchemaPtr& lhs, const TTableSchema& rhs) const
+{
+ return *lhs == rhs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline size_t TCellTaggedTableSchemaHash::operator() (const TCellTaggedTableSchema& cellTaggedSchema) const
+{
+ return MultiHash(cellTaggedSchema.TableSchema, cellTaggedSchema.CellTag);
+}
+
+inline size_t TCellTaggedTableSchemaHash::operator() (const TCellTaggedTableSchemaPtr& cellTaggedSchemaPtr) const
+{
+ YT_ASSERT(cellTaggedSchemaPtr.TableSchema);
+ return MultiHash(*cellTaggedSchemaPtr.TableSchema, cellTaggedSchemaPtr.CellTag);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline bool TCellTaggedTableSchemaEquals::operator() (const TCellTaggedTableSchema& lhs, const TCellTaggedTableSchema& rhs) const
+{
+ return lhs.TableSchema == rhs.TableSchema && lhs.CellTag == rhs.CellTag;
+}
+
+inline bool TCellTaggedTableSchemaEquals::operator() (const TCellTaggedTableSchemaPtr& lhs, const TCellTaggedTableSchemaPtr& rhs) const
+{
+ YT_ASSERT(lhs.TableSchema && rhs.TableSchema);
+ return *lhs.TableSchema == *rhs.TableSchema && lhs.CellTag == rhs.CellTag;
+}
+
+inline bool TCellTaggedTableSchemaEquals::operator() (const TCellTaggedTableSchemaPtr& lhs, const TCellTaggedTableSchema& rhs) const
+{
+ YT_ASSERT(lhs.TableSchema);
+ return *lhs.TableSchema == rhs.TableSchema && lhs.CellTag == rhs.CellTag;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/schema.cpp b/yt/yt/client/table_client/schema.cpp
new file mode 100644
index 0000000000..d0edb7e13b
--- /dev/null
+++ b/yt/yt/client/table_client/schema.cpp
@@ -0,0 +1,2002 @@
+#include "schema.h"
+
+#include "column_sort_schema.h"
+#include "comparator.h"
+#include "logical_type.h"
+#include "unversioned_row.h"
+
+#include <optional>
+
+#include <yt/yt/client/tablet_client/public.h>
+
+#include <yt/yt/client/table_client/schema_serialization_helpers.h>
+
+#include <yt/yt/core/yson/public.h>
+#include <yt/yt/core/yson/pull_parser_deserialize.h>
+
+#include <yt/yt/core/ytree/serialize.h>
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.pb.h>
+
+namespace NYT::NTableClient {
+
+using namespace NChunkClient;
+using namespace NObjectClient;
+using namespace NTabletClient;
+using namespace NYson;
+using namespace NYTree;
+
+using NYT::ToProto;
+using NYT::FromProto;
+
+/////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+int GetLockPriority(ELockType lockType)
+{
+ switch (lockType) {
+ case ELockType::None:
+ return 0;
+ case ELockType::SharedWeak:
+ return 1;
+ case ELockType::SharedStrong:
+ return 2;
+ case ELockType::Exclusive:
+ return 3;
+ default:
+ YT_ABORT();
+ }
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+ELockType GetStrongestLock(ELockType lhs, ELockType rhs)
+{
+ return GetLockPriority(lhs) > GetLockPriority(rhs) ? lhs : rhs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLockMask MaxMask(TLockMask lhs, TLockMask rhs)
+{
+ int size = std::max<int>(lhs.GetSize(), rhs.GetSize());
+ for (int index = 0; index < size; ++index) {
+ lhs.Set(index, GetStrongestLock(lhs.Get(index), rhs.Get(index)));
+ }
+
+ return lhs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStableName::TStableName(TString stableName)
+ : Name_(std::move(stableName))
+{ }
+
+const TString& TStableName::Get() const
+{
+ return Name_;
+}
+
+void FormatValue(TStringBuilderBase* builder, const TStableName& stableName, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("%v", stableName.Get());
+}
+
+bool operator == (const TStableName& lhs, const TStableName& rhs)
+{
+ return lhs.Get() == rhs.Get();
+}
+
+bool operator < (const TStableName& lhs, const TStableName& rhs)
+{
+ return lhs.Get() < rhs.Get();
+}
+
+void ToProto(TString* protoStableName, const TStableName& stableName)
+{
+ *protoStableName = stableName.Get();
+}
+
+void FromProto(TStableName* stableName, const TString& protoStableName)
+{
+ *stableName = TStableName(protoStableName);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TColumnSchema::TColumnSchema()
+ : TColumnSchema(
+ TString(),
+ NullLogicalType(),
+ std::nullopt)
+{ }
+
+TColumnSchema::TColumnSchema(
+ TString name,
+ EValueType type,
+ std::optional<ESortOrder> sortOrder)
+ : TColumnSchema(
+ std::move(name),
+ MakeLogicalType(GetLogicalType(type), /*required*/ false),
+ sortOrder)
+{ }
+
+TColumnSchema::TColumnSchema(
+ TString name,
+ ESimpleLogicalValueType type,
+ std::optional<ESortOrder> sortOrder)
+ : TColumnSchema(
+ std::move(name),
+ MakeLogicalType(type, /*required*/ false),
+ sortOrder)
+{ }
+
+TColumnSchema::TColumnSchema(
+ TString name,
+ TLogicalTypePtr type,
+ std::optional<ESortOrder> sortOrder)
+ : StableName_(name)
+ , Name_(name)
+ , SortOrder_(sortOrder)
+{
+ SetLogicalType(std::move(type));
+}
+
+TColumnSchema& TColumnSchema::SetStableName(TStableName value)
+{
+ StableName_ = std::move(value);
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetName(TString value)
+{
+ Name_ = value;
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetSortOrder(std::optional<ESortOrder> value)
+{
+ SortOrder_ = value;
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetLock(std::optional<TString> value)
+{
+ Lock_ = std::move(value);
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetGroup(std::optional<TString> value)
+{
+ Group_ = std::move(value);
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetExpression(std::optional<TString> value)
+{
+ Expression_ = std::move(value);
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetAggregate(std::optional<TString> value)
+{
+ Aggregate_ = std::move(value);
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetRequired(bool value)
+{
+ if (!IsOfV1Type_) {
+ THROW_ERROR_EXCEPTION("Cannot set required flag for non-v1 typed column");
+ }
+ SetLogicalType(MakeLogicalType(V1Type_, value));
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetMaxInlineHunkSize(std::optional<i64> value)
+{
+ MaxInlineHunkSize_ = value;
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetLogicalType(TLogicalTypePtr type)
+{
+ LogicalType_ = std::move(type);
+ IsOfV1Type_ = IsV1Type(LogicalType_);
+ std::tie(V1Type_, Required_) = NTableClient::CastToV1Type(LogicalType_);
+ return *this;
+}
+
+TColumnSchema& TColumnSchema::SetSimpleLogicalType(ESimpleLogicalValueType type)
+{
+ SetLogicalType(MakeLogicalType(type, /*required*/ false));
+ return *this;
+}
+
+EValueType TColumnSchema::GetWireType() const
+{
+ return NTableClient::GetWireType(LogicalType_);
+}
+
+i64 TColumnSchema::GetMemoryUsage() const
+{
+ return
+ sizeof(TColumnSchema) +
+ StableName_.Get().size() +
+ Name_.size() +
+ (LogicalType_ ? LogicalType_->GetMemoryUsage() : 0) +
+ (Lock_ ? Lock_->size() : 0) +
+ (Expression_ ? Expression_->size() : 0) +
+ (Aggregate_ ? Aggregate_->size() : 0) +
+ (Group_ ? Group_->size() : 0);
+}
+
+bool TColumnSchema::IsOfV1Type() const
+{
+ return IsOfV1Type_;
+}
+
+bool TColumnSchema::IsOfV1Type(ESimpleLogicalValueType type) const
+{
+ return IsOfV1Type_ && V1Type_ == type;
+}
+
+ESimpleLogicalValueType TColumnSchema::CastToV1Type() const
+{
+ return V1Type_;
+}
+
+bool TColumnSchema::IsRenamed() const
+{
+ return Name() != StableName().Get();
+}
+
+TString TColumnSchema::GetDiagnosticNameString() const
+{
+ if (IsRenamed()) {
+ return Format("%Qv (stable name %Qv)", Name(), StableName().Get());
+ } else {
+ return Format("%Qv", Name());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TDeletedColumn::TDeletedColumn()
+{
+}
+
+TDeletedColumn::TDeletedColumn(TStableName stableName)
+ : StableName_(stableName)
+{
+}
+
+TDeletedColumn& TDeletedColumn::SetStableName(TStableName value)
+{
+ StableName_ = std::move(value);
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TColumnSchema& schema, TStringBuf /*spec*/)
+{
+ builder->AppendChar('{');
+
+ builder->AppendFormat("name=%Qv", schema.Name());
+
+ if (schema.IsRenamed()) {
+ builder->AppendFormat("; stable_name=%Qv", schema.StableName());
+ }
+
+ if (const auto& logicalType = schema.LogicalType()) {
+ builder->AppendFormat("; type=%v", *logicalType);
+ }
+
+ if (const auto& sortOrder = schema.SortOrder()) {
+ builder->AppendFormat("; sort_order=%v", *sortOrder);
+ }
+
+ if (const auto& lock = schema.Lock()) {
+ builder->AppendFormat("; lock=%v", *lock);
+ }
+
+ if (const auto& expression = schema.Expression()) {
+ builder->AppendFormat("; expression=%Qv", *expression);
+ }
+
+ if (const auto& aggregate = schema.Aggregate()) {
+ builder->AppendFormat("; aggregate=%v", *aggregate);
+ }
+
+ if (const auto& group = schema.Group()) {
+ builder->AppendFormat("; group=%v", *group);
+ }
+
+ builder->AppendFormat("; physical_type=%v", CamelCaseToUnderscoreCase(ToString(schema.CastToV1Type())));
+
+ builder->AppendFormat("; required=%v", schema.Required());
+
+ if (auto maxInlineHunkSize = schema.MaxInlineHunkSize()) {
+ builder->AppendFormat("; max_inline_hunk_size=%v", *maxInlineHunkSize);
+ }
+
+ builder->AppendChar('}');
+}
+
+void FormatValue(TStringBuilderBase* builder, const TDeletedColumn& schema, TStringBuf spec)
+{
+ builder->AppendChar('{');
+
+ FormatValue(builder, schema.StableName(), spec);
+ builder->AppendFormat("; deleted=true");
+
+ builder->AppendChar('}');
+}
+
+void ToProto(NProto::TColumnSchema* protoSchema, const TColumnSchema& schema)
+{
+ protoSchema->set_name(schema.Name());
+ if (schema.IsRenamed()) {
+ protoSchema->set_stable_name(schema.StableName().Get());
+ }
+
+ protoSchema->set_type(static_cast<int>(GetPhysicalType(schema.CastToV1Type())));
+
+ if (schema.IsOfV1Type()) {
+ protoSchema->set_simple_logical_type(static_cast<int>(schema.CastToV1Type()));
+ } else {
+ protoSchema->clear_simple_logical_type();
+ }
+ if (schema.Required()) {
+ protoSchema->set_required(true);
+ } else {
+ protoSchema->clear_required();
+ }
+ ToProto(protoSchema->mutable_logical_type(), schema.LogicalType());
+ if (schema.Lock()) {
+ protoSchema->set_lock(*schema.Lock());
+ } else {
+ protoSchema->clear_lock();
+ }
+ if (schema.Expression()) {
+ protoSchema->set_expression(*schema.Expression());
+ } else {
+ protoSchema->clear_expression();
+ }
+ if (schema.Aggregate()) {
+ protoSchema->set_aggregate(*schema.Aggregate());
+ } else {
+ protoSchema->clear_aggregate();
+ }
+ if (schema.SortOrder()) {
+ protoSchema->set_sort_order(static_cast<int>(*schema.SortOrder()));
+ } else {
+ protoSchema->clear_sort_order();
+ }
+ if (schema.Group()) {
+ protoSchema->set_group(*schema.Group());
+ } else {
+ protoSchema->clear_group();
+ }
+ if (schema.MaxInlineHunkSize()) {
+ protoSchema->set_max_inline_hunk_size(*schema.MaxInlineHunkSize());
+ } else {
+ protoSchema->clear_max_inline_hunk_size();
+ }
+}
+
+void ToProto(NProto::TDeletedColumn* protoSchema, const TDeletedColumn& schema)
+{
+ protoSchema->set_stable_name(schema.StableName().Get());
+}
+
+void FromProto(TColumnSchema* schema, const NProto::TColumnSchema& protoSchema)
+{
+ schema->SetName(protoSchema.name());
+ schema->SetStableName(protoSchema.has_stable_name()
+ ? TStableName(protoSchema.stable_name())
+ : TStableName(protoSchema.name()));
+
+ if (protoSchema.has_logical_type()) {
+ TLogicalTypePtr logicalType;
+ FromProto(&logicalType, protoSchema.logical_type());
+ schema->SetLogicalType(logicalType);
+ } else if (protoSchema.has_simple_logical_type()) {
+ schema->SetLogicalType(
+ MakeLogicalType(
+ CheckedEnumCast<ESimpleLogicalValueType>(protoSchema.simple_logical_type()),
+ protoSchema.required()));
+ } else {
+ auto physicalType = CheckedEnumCast<EValueType>(protoSchema.type());
+ schema->SetLogicalType(MakeLogicalType(GetLogicalType(physicalType), protoSchema.required()));
+ }
+
+ schema->SetLock(protoSchema.has_lock() ? std::make_optional(protoSchema.lock()) : std::nullopt);
+ schema->SetExpression(protoSchema.has_expression() ? std::make_optional(protoSchema.expression()) : std::nullopt);
+ schema->SetAggregate(protoSchema.has_aggregate() ? std::make_optional(protoSchema.aggregate()) : std::nullopt);
+ schema->SetSortOrder(protoSchema.has_sort_order() ? std::make_optional(CheckedEnumCast<ESortOrder>(protoSchema.sort_order())) : std::nullopt);
+ schema->SetGroup(protoSchema.has_group() ? std::make_optional(protoSchema.group()) : std::nullopt);
+ schema->SetMaxInlineHunkSize(protoSchema.has_max_inline_hunk_size() ? std::make_optional(protoSchema.max_inline_hunk_size()) : std::nullopt);
+}
+
+void FromProto(TDeletedColumn* schema, const NProto::TDeletedColumn& protoSchema)
+{
+ schema->SetStableName(TStableName{protoSchema.stable_name()});
+}
+
+void PrintTo(const TColumnSchema& columnSchema, std::ostream* os)
+{
+ *os << Format("%v", columnSchema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTableSchema::TNameMapping::TNameMapping(const TTableSchema& schema)
+ : Schema_(schema)
+{ }
+
+bool TTableSchema::TNameMapping::IsDeleted(const TStableName& stableName) const
+{
+ return Schema_.FindDeletedColumn(stableName) != nullptr;
+}
+
+TString TTableSchema::TNameMapping::StableNameToName(const TStableName& stableName) const
+{
+ auto* column = Schema_.FindColumnByStableName(stableName);
+ if (!column) {
+ if (Schema_.GetStrict()) {
+ THROW_ERROR_EXCEPTION("No column with stable name %Qv in strict schema", stableName);
+ }
+ return stableName.Get();
+ }
+ return column->Name();
+}
+
+TStableName TTableSchema::TNameMapping::NameToStableName(TStringBuf name) const
+{
+ auto* column = Schema_.FindColumn(name);
+ if (!column) {
+ if (Schema_.GetStrict()) {
+ THROW_ERROR_EXCEPTION("No column with name %Qv in strict schema", name);
+ }
+ return TStableName(TString(name));
+ }
+ return column->StableName();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const std::vector<TColumnSchema>& TTableSchema::Columns() const
+{
+ if (Columns_) [[likely]] {
+ return *Columns_;
+ } else {
+ static const std::vector<TColumnSchema> empty;
+ return empty;
+ }
+}
+
+const std::vector<TDeletedColumn>& TTableSchema::DeletedColumns() const
+{
+ return DeletedColumns_;
+}
+
+TTableSchema::TTableSchema(
+ std::vector<TColumnSchema> columns,
+ bool strict,
+ bool uniqueKeys,
+ ETableSchemaModification schemaModification,
+ std::vector<TDeletedColumn> deletedColumns)
+ : Strict_(strict)
+ , UniqueKeys_(uniqueKeys)
+ , SchemaModification_(schemaModification)
+ , Columns_(std::make_shared<const std::vector<TColumnSchema>>(std::move(columns)))
+ , DeletedColumns_(std::move(deletedColumns))
+{
+ for (int index = 0; index < std::ssize(Columns()); ++index) {
+ const auto& column = Columns()[index];
+ if (column.SortOrder()) {
+ ++KeyColumnCount_;
+ }
+ if (column.Expression()) {
+ HasComputedColumns_ = true;
+ }
+ if (column.Aggregate()) {
+ HasAggregateColumns_ = true;
+ }
+ if (column.MaxInlineHunkSize()) {
+ HunkColumnsIds_.push_back(index);
+ }
+ // NB(levysotsky): We ignore duplicates in both maps, they will be
+ // accounted for in consequent validation.
+ NameToColumnIndex_.emplace(column.Name(), index);
+ StableNameToColumnIndex_.emplace(column.StableName().Get(), index);
+ }
+ for (int index = 0; index < std::ssize(DeletedColumns()); ++index) {
+ const auto& deletedColumn = DeletedColumns()[index];
+ StableNameToDeletedColumnIndex_.emplace(deletedColumn.StableName().Get(), index);
+ }
+}
+
+const TColumnSchema* TTableSchema::FindColumnByStableName(const TStableName& stableName) const
+{
+ auto it = StableNameToColumnIndex_.find(stableName.Get());
+ if (it == StableNameToColumnIndex_.end()) {
+ return nullptr;
+ }
+ return &Columns()[it->second];
+}
+
+const TDeletedColumn* TTableSchema::FindDeletedColumn(const TStableName& stableName) const
+{
+ auto it = StableNameToDeletedColumnIndex_.find(stableName.Get());
+ if (it == StableNameToDeletedColumnIndex_.end()) {
+ return nullptr;
+ }
+ return &DeletedColumns()[it->second];
+}
+
+const TColumnSchema* TTableSchema::FindColumn(TStringBuf name) const
+{
+ auto it = NameToColumnIndex_.find(name);
+ if (it == NameToColumnIndex_.end()) {
+ return nullptr;
+ }
+ return &Columns()[it->second];
+}
+
+const TColumnSchema& TTableSchema::GetColumn(TStringBuf name) const
+{
+ auto* column = FindColumn(name);
+ YT_VERIFY(column);
+ return *column;
+}
+
+const TColumnSchema& TTableSchema::GetColumnOrThrow(TStringBuf name) const
+{
+ auto* column = FindColumn(name);
+ if (!column) {
+ THROW_ERROR_EXCEPTION("Missing schema column with name %Qv", name);
+ }
+ return *column;
+}
+
+int TTableSchema::GetColumnIndex(const TColumnSchema& column) const
+{
+ auto begin = Columns().data();
+ auto end = begin + Columns().size();
+ YT_VERIFY(begin <= &column && &column < end);
+
+ return &column - begin;
+}
+
+int TTableSchema::GetColumnIndex(TStringBuf name) const
+{
+ return GetColumnIndex(GetColumn(name));
+}
+
+int TTableSchema::GetColumnIndexOrThrow(TStringBuf name) const
+{
+ return GetColumnIndex(GetColumnOrThrow(name));
+}
+
+TTableSchema::TNameMapping TTableSchema::GetNameMapping() const
+{
+ return TNameMapping(*this);
+}
+
+TTableSchemaPtr TTableSchema::Filter(const TColumnFilter& columnFilter, bool discardSortOrder) const
+{
+ int newKeyColumnCount = 0;
+ std::vector<TColumnSchema> columns;
+
+ if (columnFilter.IsUniversal()) {
+ if (!discardSortOrder) {
+ return New<TTableSchema>(*this);
+ } else {
+ columns = Columns();
+ for (auto& column : columns) {
+ column.SetSortOrder(std::nullopt);
+ }
+ }
+ } else {
+ bool inKeyColumns = !discardSortOrder;
+ for (int id : columnFilter.GetIndexes()) {
+ if (id < 0 || id >= std::ssize(Columns())) {
+ THROW_ERROR_EXCEPTION("Invalid column during schema filtering: expected in range [0, %v), got %v",
+ Columns().size(),
+ id);
+ }
+
+ if (id != std::ssize(columns) || !Columns()[id].SortOrder()) {
+ inKeyColumns = false;
+ }
+
+ columns.push_back(Columns()[id]);
+
+ if (!inKeyColumns) {
+ columns.back().SetSortOrder(std::nullopt);
+ }
+
+ if (columns.back().SortOrder()) {
+ ++newKeyColumnCount;
+ }
+ }
+ }
+
+ return New<TTableSchema>(
+ std::move(columns),
+ Strict_,
+ UniqueKeys_ && (newKeyColumnCount == GetKeyColumnCount()),
+ ETableSchemaModification::None,
+ DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::Filter(const THashSet<TString>& columnNames, bool discardSortOrder) const
+{
+ TColumnFilter::TIndexes indexes;
+ for (const auto& column : Columns()) {
+ if (columnNames.find(column.Name()) != columnNames.end()) {
+ indexes.push_back(GetColumnIndex(column));
+ }
+ }
+ return Filter(TColumnFilter(std::move(indexes)), discardSortOrder);
+}
+
+TTableSchemaPtr TTableSchema::Filter(const std::optional<std::vector<TString>>& columnNames, bool discardSortOrder) const
+{
+ if (!columnNames) {
+ return Filter(TColumnFilter(), discardSortOrder);
+ }
+
+ return Filter(THashSet<TString>(columnNames->begin(), columnNames->end()), discardSortOrder);
+}
+
+bool TTableSchema::HasComputedColumns() const
+{
+ return HasComputedColumns_;
+}
+
+bool TTableSchema::HasAggregateColumns() const
+{
+ return HasAggregateColumns_;
+}
+
+bool TTableSchema::HasHunkColumns() const
+{
+ return !HunkColumnsIds_.empty();
+}
+
+bool TTableSchema::HasTimestampColumn() const
+{
+ return FindColumn(TimestampColumnName);
+}
+
+bool TTableSchema::IsSorted() const
+{
+ return KeyColumnCount_ > 0;
+}
+
+bool TTableSchema::IsUniqueKeys() const
+{
+ return UniqueKeys_;
+}
+
+bool TTableSchema::HasRenamedColumns() const
+{
+ return std::any_of(Columns().begin(), Columns().end(), [] (const TColumnSchema& column) {
+ return column.IsRenamed();
+ });
+}
+
+bool TTableSchema::IsEmpty() const
+{
+ return Columns().empty();
+}
+
+TKeyColumns TTableSchema::GetKeyColumnNames() const
+{
+ TKeyColumns keyColumns;
+ for (const auto& column : Columns()) {
+ if (column.SortOrder()) {
+ keyColumns.push_back(column.Name());
+ }
+ }
+ return keyColumns;
+}
+
+std::vector<TStableName> TTableSchema::GetKeyColumnStableNames() const
+{
+ std::vector<TStableName> keyColumns;
+ for (const auto& column : Columns()) {
+ if (column.SortOrder()) {
+ keyColumns.push_back(column.StableName());
+ }
+ }
+ return keyColumns;
+}
+
+TKeyColumns TTableSchema::GetKeyColumns() const
+{
+ return GetKeyColumnNames();
+}
+
+int TTableSchema::GetColumnCount() const
+{
+ return static_cast<int>(Columns().size());
+}
+
+std::vector<TString> TTableSchema::GetColumnNames() const
+{
+ std::vector<TString> result;
+ result.reserve(Columns().size());
+ for (const auto& column : Columns()) {
+ result.push_back(column.Name());
+ }
+ return result;
+}
+
+std::vector<TStableName> TTableSchema::GetColumnStableNames() const
+{
+ std::vector<TStableName> result;
+ result.reserve(Columns().size());
+ for (const auto& column : Columns()) {
+ result.push_back(column.StableName());
+ }
+ return result;
+}
+
+const THunkColumnIds& TTableSchema::GetHunkColumnIds() const
+{
+ return HunkColumnsIds_;
+}
+
+std::vector<TStableName> MapNamesToStableNames(
+ const TTableSchema& schema,
+ std::vector<TString> names,
+ const std::optional<TStringBuf>& missingColumnReplacement)
+{
+ std::vector<TStableName> stableNames;
+ stableNames.reserve(names.size());
+ for (const auto& name : names) {
+ const auto* column = schema.FindColumn(name);
+ if (column) {
+ stableNames.push_back(column->StableName());
+ } else if (!schema.GetStrict()) {
+ stableNames.push_back(TStableName(name));
+ } else if (missingColumnReplacement) {
+ stableNames.push_back(TStableName(TString(*missingColumnReplacement)));
+ } else {
+ THROW_ERROR_EXCEPTION("Column %Qv is missing in strict schema",
+ name);
+ }
+ }
+ return stableNames;
+}
+
+int TTableSchema::GetKeyColumnCount() const
+{
+ return KeyColumnCount_;
+}
+
+int TTableSchema::GetValueColumnCount() const
+{
+ return GetColumnCount() - GetKeyColumnCount();
+}
+
+TSortColumns TTableSchema::GetSortColumns(const std::optional<TNameMapping>& nameMapping) const
+{
+ auto actualNameMapping = nameMapping
+ ? *nameMapping
+ : GetNameMapping();
+
+ TSortColumns sortColumns;
+ sortColumns.reserve(GetKeyColumnCount());
+
+ for (const auto& column : Columns()) {
+ if (column.SortOrder()) {
+ const auto& name = actualNameMapping.StableNameToName(column.StableName());
+ sortColumns.push_back(TColumnSortSchema{
+ .Name = TString(name),
+ .SortOrder = *column.SortOrder(),
+ });
+ }
+ }
+
+ return sortColumns;
+}
+
+TTableSchemaPtr TTableSchema::SetUniqueKeys(bool uniqueKeys) const
+{
+ auto schema = *this;
+ schema.UniqueKeys_ = uniqueKeys;
+ return New<TTableSchema>(std::move(schema));
+}
+
+TTableSchemaPtr TTableSchema::SetSchemaModification(ETableSchemaModification schemaModification) const
+{
+ auto schema = *this;
+ schema.SchemaModification_ = schemaModification;
+ return New<TTableSchema>(std::move(schema));
+}
+
+bool TTableSchema::HasNontrivialSchemaModification() const
+{
+ return GetSchemaModification() != ETableSchemaModification::None;
+}
+
+TTableSchemaPtr TTableSchema::FromKeyColumns(const TKeyColumns& keyColumns)
+{
+ TTableSchema schema;
+ std::vector<TColumnSchema> columns;
+ for (const auto& columnName : keyColumns) {
+ columns.push_back(
+ TColumnSchema(columnName, ESimpleLogicalValueType::Any)
+ .SetSortOrder(ESortOrder::Ascending));
+ }
+ schema.Columns_ = std::make_shared<const std::vector<TColumnSchema>>(std::move(columns));
+ schema.KeyColumnCount_ = keyColumns.size();
+ ValidateTableSchema(schema);
+ return New<TTableSchema>(std::move(schema));
+}
+
+TTableSchemaPtr TTableSchema::FromSortColumns(const TSortColumns& sortColumns)
+{
+ TTableSchema schema;
+ std::vector<TColumnSchema> columns;
+ for (const auto& sortColumn : sortColumns) {
+ columns.push_back(
+ TColumnSchema(sortColumn.Name, ESimpleLogicalValueType::Any)
+ .SetSortOrder(sortColumn.SortOrder));
+ }
+ schema.Columns_ = std::make_shared<const std::vector<TColumnSchema>>(std::move(columns));
+ schema.KeyColumnCount_ = sortColumns.size();
+ ValidateTableSchema(schema);
+ return New<TTableSchema>(std::move(schema));
+}
+
+TTableSchemaPtr TTableSchema::ToQuery() const
+{
+ if (IsSorted()) {
+ return New<TTableSchema>(*this);
+ } else {
+ std::vector<TColumnSchema> columns {
+ TColumnSchema(TabletIndexColumnName, ESimpleLogicalValueType::Int64)
+ .SetSortOrder(ESortOrder::Ascending),
+ TColumnSchema(RowIndexColumnName, ESimpleLogicalValueType::Int64)
+ .SetSortOrder(ESortOrder::Ascending)
+ };
+ columns.insert(columns.end(), Columns().begin(), Columns().end());
+ return New<TTableSchema>(std::move(columns), true, false,
+ ETableSchemaModification::None, DeletedColumns_);
+ }
+}
+
+TTableSchemaPtr TTableSchema::ToWrite() const
+{
+ std::vector<TColumnSchema> columns;
+ if (IsSorted()) {
+ for (const auto& column : Columns()) {
+ if (!column.Expression()) {
+ columns.push_back(column);
+ }
+ }
+ } else {
+ columns.push_back(TColumnSchema(TabletIndexColumnName, ESimpleLogicalValueType::Int64)
+ .SetSortOrder(ESortOrder::Ascending));
+ for (const auto& column : Columns()) {
+ if (column.StableName().Get() != TimestampColumnName &&
+ column.StableName().Get() != CumulativeDataWeightColumnName)
+ {
+ columns.push_back(column);
+ }
+ }
+ }
+ return New<TTableSchema>(std::move(columns), Strict_, UniqueKeys_,
+ ETableSchemaModification::None, DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::WithTabletIndex() const
+{
+ if (IsSorted()) {
+ return New<TTableSchema>(*this);
+ } else {
+ auto columns = Columns();
+ // XXX: Is it ok? $tablet_index is usually a key column.
+ columns.push_back(TColumnSchema(TabletIndexColumnName, ESimpleLogicalValueType::Int64));
+ return New<TTableSchema>(std::move(columns), Strict_, UniqueKeys_,
+ ETableSchemaModification::None, DeletedColumns_);
+ }
+}
+
+TTableSchemaPtr TTableSchema::ToVersionedWrite() const
+{
+ if (IsSorted()) {
+ return New<TTableSchema>(*this);
+ } else {
+ auto columns = Columns();
+ columns.insert(columns.begin(), TColumnSchema(TabletIndexColumnName, ESimpleLogicalValueType::Int64)
+ .SetSortOrder(ESortOrder::Ascending));
+ return New<TTableSchema>(std::move(columns), Strict_, UniqueKeys_,
+ ETableSchemaModification::None, DeletedColumns_);
+ }
+}
+
+TTableSchemaPtr TTableSchema::ToLookup() const
+{
+ std::vector<TColumnSchema> columns;
+ for (const auto& column : Columns()) {
+ if (column.SortOrder() && !column.Expression()) {
+ columns.push_back(column);
+ }
+ }
+ return New<TTableSchema>(std::move(columns), Strict_, UniqueKeys_, ETableSchemaModification::None, DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToDelete() const
+{
+ return ToLookup();
+}
+
+TTableSchemaPtr TTableSchema::ToKeys() const
+{
+ std::vector<TColumnSchema> columns(Columns().begin(), Columns().begin() + KeyColumnCount_);
+ return New<TTableSchema>(std::move(columns), Strict_, UniqueKeys_,
+ ETableSchemaModification::None, DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToUniqueKeys() const
+{
+ return New<TTableSchema>(Columns(), Strict_, /*uniqueKeys*/ true,
+ ETableSchemaModification::None, DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToStrippedColumnAttributes() const
+{
+ std::vector<TColumnSchema> strippedColumns;
+ for (const auto& column : Columns()) {
+ auto& strippedColumn = strippedColumns.emplace_back(column.Name(), column.LogicalType());
+ strippedColumn.SetStableName(column.StableName());
+ }
+ return New<TTableSchema>(std::move(strippedColumns), Strict_, /*uniqueKeys*/ false,
+ ETableSchemaModification::None, DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToSortedStrippedColumnAttributes() const
+{
+ std::vector<TColumnSchema> strippedColumns;
+ for (const auto& column : Columns()) {
+ auto& strippedColumn = strippedColumns.emplace_back(column.Name(), column.LogicalType(), column.SortOrder());
+ strippedColumn.SetStableName(column.StableName());
+ }
+ return New<TTableSchema>(std::move(strippedColumns), Strict_, UniqueKeys_,
+ ETableSchemaModification::None, DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToCanonical() const
+{
+ auto columns = Columns();
+ std::sort(
+ columns.begin() + KeyColumnCount_,
+ columns.end(),
+ [] (const TColumnSchema& lhs, const TColumnSchema& rhs) {
+ return lhs.Name() < rhs.Name();
+ });
+ return New<TTableSchema>(columns, Strict_, UniqueKeys_,
+ ETableSchemaModification::None, DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToSorted(const TKeyColumns& keyColumns) const
+{
+ TSortColumns sortColumns;
+ sortColumns.reserve(keyColumns.size());
+ for (const auto& keyColumn : keyColumns) {
+ sortColumns.push_back(TColumnSortSchema{
+ .Name = keyColumn,
+ .SortOrder = ESortOrder::Ascending
+ });
+ }
+
+ return TTableSchema::ToSorted(sortColumns);
+}
+
+TTableSchemaPtr TTableSchema::ToSorted(const TSortColumns& sortColumns) const
+{
+ int oldKeyColumnCount = 0;
+ auto columns = Columns();
+ for (int index = 0; index < std::ssize(sortColumns); ++index) {
+ auto it = std::find_if(
+ columns.begin() + index,
+ columns.end(),
+ [&] (const TColumnSchema& column) {
+ return column.Name() == sortColumns[index].Name;
+ });
+
+ if (it == columns.end()) {
+ if (Strict_) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::IncompatibleKeyColumns,
+ "Column %Qv is not found in strict schema",
+ sortColumns[index].Name)
+ << TErrorAttribute("schema", *this)
+ << TErrorAttribute("sort_columns", sortColumns);
+ } else {
+ columns.push_back(TColumnSchema(sortColumns[index].Name, EValueType::Any));
+ it = columns.end();
+ --it;
+ }
+ }
+
+ if (it->SortOrder()) {
+ ++oldKeyColumnCount;
+ }
+
+ std::swap(columns[index], *it);
+ columns[index].SetSortOrder(sortColumns[index].SortOrder);
+ }
+
+ auto uniqueKeys = UniqueKeys_ && oldKeyColumnCount == GetKeyColumnCount();
+
+ for (auto it = columns.begin() + sortColumns.size(); it != columns.end(); ++it) {
+ it->SetSortOrder(std::nullopt);
+ }
+
+ return New<TTableSchema>(std::move(columns), Strict_, uniqueKeys, GetSchemaModification(),
+ DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToReplicationLog() const
+{
+ std::vector<TColumnSchema> columns;
+ columns.push_back(TColumnSchema(TimestampColumnName, ESimpleLogicalValueType::Uint64));
+ if (IsSorted()) {
+ columns.push_back(TColumnSchema(TReplicationLogTable::ChangeTypeColumnName, ESimpleLogicalValueType::Int64));
+ for (const auto& column : Columns()) {
+ if (column.SortOrder()) {
+ columns.push_back(
+ TColumnSchema(
+ TReplicationLogTable::KeyColumnNamePrefix + column.Name(),
+ column.LogicalType()));
+ } else {
+ columns.push_back(
+ TColumnSchema(
+ TReplicationLogTable::ValueColumnNamePrefix + column.Name(),
+ MakeOptionalIfNot(column.LogicalType())));
+ columns.push_back(
+ TColumnSchema(TReplicationLogTable::FlagsColumnNamePrefix + column.Name(), ESimpleLogicalValueType::Uint64));
+ }
+ }
+ } else {
+ for (const auto& column : Columns()) {
+ columns.push_back(
+ TColumnSchema(
+ TReplicationLogTable::ValueColumnNamePrefix + column.Name(),
+ MakeOptionalIfNot(column.LogicalType()))
+ .SetMaxInlineHunkSize(column.MaxInlineHunkSize()));
+ }
+ columns.push_back(TColumnSchema(TReplicationLogTable::ValueColumnNamePrefix + TabletIndexColumnName, ESimpleLogicalValueType::Int64));
+ }
+ return New<TTableSchema>(
+ std::move(columns),
+ /* strict */ true,
+ /* uniqueKeys */ false,
+ ETableSchemaModification::None,
+ DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToUnversionedUpdate(bool sorted) const
+{
+ YT_VERIFY(IsSorted());
+
+ std::vector<TColumnSchema> columns;
+ columns.reserve(GetKeyColumnCount() + 1 + GetValueColumnCount() * 2);
+
+ // Keys.
+ for (int columnIndex = 0; columnIndex < GetKeyColumnCount(); ++columnIndex) {
+ auto column = Columns()[columnIndex];
+ if (!sorted) {
+ column.SetSortOrder(std::nullopt);
+ }
+ columns.push_back(column);
+ }
+
+ // Modification type.
+ columns.emplace_back(
+ TUnversionedUpdateSchema::ChangeTypeColumnName,
+ MakeLogicalType(ESimpleLogicalValueType::Uint64, /*required*/ true));
+
+ // Values.
+ for (int columnIndex = GetKeyColumnCount(); columnIndex < GetColumnCount(); ++columnIndex) {
+ const auto& column = Columns()[columnIndex];
+ YT_VERIFY(!column.SortOrder());
+ columns.emplace_back(
+ TUnversionedUpdateSchema::ValueColumnNamePrefix + column.Name(),
+ MakeOptionalIfNot(column.LogicalType()));
+ columns.emplace_back(
+ TUnversionedUpdateSchema::FlagsColumnNamePrefix + column.Name(),
+ MakeLogicalType(ESimpleLogicalValueType::Uint64, /*required*/ false));
+ }
+
+ return New<TTableSchema>(
+ std::move(columns),
+ /*strict*/ true,
+ /*uniqueKeys*/ sorted,
+ ETableSchemaModification::None,
+ DeletedColumns_);
+}
+
+TTableSchemaPtr TTableSchema::ToModifiedSchema(ETableSchemaModification schemaModification) const
+{
+ if (HasNontrivialSchemaModification()) {
+ THROW_ERROR_EXCEPTION("Cannot apply schema modification because schema is already modified")
+ << TErrorAttribute("existing_modification", GetSchemaModification())
+ << TErrorAttribute("requested_modification", schemaModification);
+ }
+ YT_VERIFY(GetSchemaModification() == ETableSchemaModification::None);
+
+ switch (schemaModification) {
+ case ETableSchemaModification::None:
+ return New<TTableSchema>(*this);
+
+ case ETableSchemaModification::UnversionedUpdate:
+ return ToUnversionedUpdate(/*sorted*/ true)
+ ->SetSchemaModification(schemaModification);
+
+ case ETableSchemaModification::UnversionedUpdateUnsorted:
+ return ToUnversionedUpdate(/*sorted*/ false)
+ ->SetSchemaModification(schemaModification);
+
+ default:
+ YT_ABORT();
+ }
+}
+
+TComparator TTableSchema::ToComparator() const
+{
+ std::vector<ESortOrder> sortOrders(KeyColumnCount_);
+ for (int index = 0; index < KeyColumnCount_; ++index) {
+ YT_VERIFY(Columns()[index].SortOrder());
+ sortOrders[index] = *Columns()[index].SortOrder();
+ }
+ return TComparator(std::move(sortOrders));
+}
+
+void TTableSchema::Save(TStreamSaveContext& context) const
+{
+ using NYT::Save;
+ Save(context, ToProto<NTableClient::NProto::TTableSchemaExt>(*this));
+}
+
+void TTableSchema::Load(TStreamLoadContext& context)
+{
+ using NYT::Load;
+ auto protoSchema = NYT::Load<NTableClient::NProto::TTableSchemaExt>(context);
+ *this = FromProto<TTableSchema>(protoSchema);
+}
+
+i64 TTableSchema::GetMemoryUsage() const
+{
+ i64 usage = sizeof(TTableSchema);
+ for (const auto& column : Columns()) {
+ usage += column.GetMemoryUsage();
+ }
+ return usage;
+}
+
+TKeyColumnTypes TTableSchema::GetKeyColumnTypes() const
+{
+ TKeyColumnTypes result(KeyColumnCount_);
+ for (int index = 0; index < KeyColumnCount_; ++index) {
+ result[index] = Columns()[index].GetWireType();
+ }
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TTableSchema& schema, TStringBuf /*spec*/)
+{
+ builder->AppendFormat("<strict=%v;unique_keys=%v", schema.GetStrict(), schema.GetUniqueKeys());
+ if (schema.HasNontrivialSchemaModification()) {
+ builder->AppendFormat(";schema_modification=%v", schema.GetSchemaModification());
+ }
+ builder->AppendChar('>');
+ builder->AppendChar('[');
+ bool first = true;
+ for (const auto& column : schema.Columns()) {
+ if (!first) {
+ builder->AppendString(TStringBuf("; "));
+ }
+ builder->AppendFormat("%v", column);
+ first = false;
+ }
+ for (const auto& deletedColumn : schema.DeletedColumns()) {
+ if (!first) {
+ builder->AppendString(TStringBuf("; "));
+ }
+ builder->AppendFormat("%v", deletedColumn);
+ first = false;
+ }
+ builder->AppendChar(']');
+}
+
+TString ToString(const TTableSchema& schema)
+{
+ return ToStringViaBuilder(schema);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TTableSchemaPtr& schema, TStringBuf spec)
+{
+ if (schema) {
+ FormatValue(builder, *schema, spec);
+ } else {
+ builder->AppendString(TStringBuf("<null>"));
+ }
+}
+
+TString ToString(const TTableSchemaPtr& schema)
+{
+ return ToStringViaBuilder(schema);
+}
+
+TString SerializeToWireProto(const TTableSchemaPtr& schema)
+{
+ NTableClient::NProto::TTableSchemaExt protoSchema;
+ ToProto(&protoSchema, schema);
+ return protoSchema.SerializeAsString();
+}
+
+void DeserializeFromWireProto(TTableSchemaPtr* schema, const TString& serializedProto)
+{
+ NTableClient::NProto::TTableSchemaExt protoSchema;
+ if (!protoSchema.ParseFromString(serializedProto)) {
+ THROW_ERROR_EXCEPTION("Failed to deserialize table schema from wire proto");
+ }
+ FromProto(schema, protoSchema);
+}
+
+void ToProto(NProto::TTableSchemaExt* protoSchema, const TTableSchema& schema)
+{
+ ToProto(protoSchema->mutable_columns(), schema.Columns());
+ ToProto(protoSchema->mutable_deleted_columns(), schema.DeletedColumns());
+ protoSchema->set_strict(schema.GetStrict());
+ protoSchema->set_unique_keys(schema.GetUniqueKeys());
+ protoSchema->set_schema_modification(static_cast<int>(schema.GetSchemaModification()));
+}
+
+void FromProto(TTableSchema* schema, const NProto::TTableSchemaExt& protoSchema)
+{
+ *schema = TTableSchema(
+ FromProto<std::vector<TColumnSchema>>(protoSchema.columns()),
+ protoSchema.strict(),
+ protoSchema.unique_keys(),
+ CheckedEnumCast<ETableSchemaModification>(protoSchema.schema_modification()),
+ FromProto<std::vector<TDeletedColumn>>(protoSchema.deleted_columns()));
+}
+
+void FromProto(
+ TTableSchema* schema,
+ const NProto::TTableSchemaExt& protoSchema,
+ const NProto::TKeyColumnsExt& protoKeyColumns)
+{
+ auto columns = FromProto<std::vector<TColumnSchema>>(protoSchema.columns());
+ for (int columnIndex = 0; columnIndex < protoKeyColumns.names_size(); ++columnIndex) {
+ auto& columnSchema = columns[columnIndex];
+ YT_VERIFY(columnSchema.Name() == protoKeyColumns.names(columnIndex));
+ // TODO(gritukan): YT-14155
+ if (!columnSchema.SortOrder()) {
+ columnSchema.SetSortOrder(ESortOrder::Ascending);
+ }
+ }
+ for (int columnIndex = protoKeyColumns.names_size(); columnIndex < std::ssize(columns); ++columnIndex) {
+ auto& columnSchema = columns[columnIndex];
+ YT_VERIFY(!columnSchema.SortOrder());
+ }
+
+ auto deletedColumns = FromProto<std::vector<TDeletedColumn>>(protoSchema.deleted_columns());
+
+ *schema = TTableSchema(
+ std::move(columns),
+ protoSchema.strict(),
+ protoSchema.unique_keys(),
+ ETableSchemaModification::None,
+ deletedColumns);
+}
+
+void ToProto(NProto::TTableSchemaExt* protoSchema, const TTableSchemaPtr& schema)
+{
+ ToProto(protoSchema, *schema);
+}
+
+void FromProto(TTableSchemaPtr* schema, const NProto::TTableSchemaExt& protoSchema)
+{
+ *schema = New<TTableSchema>();
+ FromProto(schema->Get(), protoSchema);
+}
+
+void FromProto(
+ TTableSchemaPtr* schema,
+ const NProto::TTableSchemaExt& protoSchema,
+ const NProto::TKeyColumnsExt& keyColumnsExt)
+{
+ *schema = New<TTableSchema>();
+ FromProto(schema->Get(), protoSchema, keyColumnsExt);
+}
+
+void PrintTo(const TTableSchema& tableSchema, std::ostream* os)
+{
+ *os << Format("%v", tableSchema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TColumnSchema& lhs, const TColumnSchema& rhs)
+{
+ return
+ lhs.StableName() == rhs.StableName() &&
+ lhs.Name() == rhs.Name() &&
+ *lhs.LogicalType() == *rhs.LogicalType() &&
+ lhs.Required() == rhs.Required() &&
+ lhs.SortOrder() == rhs.SortOrder() &&
+ lhs.Lock() == rhs.Lock() &&
+ lhs.Expression() == rhs.Expression() &&
+ lhs.Aggregate() == rhs.Aggregate() &&
+ lhs.Group() == rhs.Group() &&
+ lhs.MaxInlineHunkSize() == rhs.MaxInlineHunkSize();
+}
+
+bool operator==(const TDeletedColumn& lhs, const TDeletedColumn& rhs)
+{
+ return lhs.StableName() == rhs.StableName();
+}
+
+bool operator==(const TTableSchema& lhs, const TTableSchema& rhs)
+{
+ return
+ lhs.Columns() == rhs.Columns() &&
+ lhs.GetStrict() == rhs.GetStrict() &&
+ lhs.GetUniqueKeys() == rhs.GetUniqueKeys() &&
+ lhs.GetSchemaModification() == rhs.GetSchemaModification() &&
+ lhs.DeletedColumns() == rhs.DeletedColumns();
+}
+
+// Compat code for https://st.yandex-team.ru/YT-10668 workaround.
+bool IsEqualIgnoringRequiredness(const TTableSchema& lhs, const TTableSchema& rhs)
+{
+ auto dropRequiredness = [] (const TTableSchema& schema) {
+ std::vector<TColumnSchema> resultColumns;
+ for (auto column : schema.Columns()) {
+ if (column.LogicalType()->GetMetatype() == ELogicalMetatype::Optional) {
+ column.SetLogicalType(column.LogicalType()->AsOptionalTypeRef().GetElement());
+ }
+ resultColumns.emplace_back(column);
+ }
+ return TTableSchema(resultColumns, schema.GetStrict(), schema.GetUniqueKeys());
+ };
+ return dropRequiredness(lhs) == dropRequiredness(rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateKeyColumns(const TKeyColumns& keyColumns)
+{
+ ValidateKeyColumnCount(keyColumns.size());
+
+ THashSet<TString> names;
+ for (const auto& name : keyColumns) {
+ if (!names.insert(name).second) {
+ THROW_ERROR_EXCEPTION("Duplicate key column name %Qv",
+ name);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateSystemColumnSchema(
+ const TColumnSchema& columnSchema,
+ bool isTableSorted,
+ bool allowUnversionedUpdateColumns)
+{
+ static const auto allowedSortedTablesSystemColumns = THashMap<TString, ESimpleLogicalValueType>{
+ };
+
+ static const auto allowedOrderedTablesSystemColumns = THashMap<TString, ESimpleLogicalValueType>{
+ {TimestampColumnName, ESimpleLogicalValueType::Uint64},
+ {CumulativeDataWeightColumnName, ESimpleLogicalValueType::Int64}
+ };
+
+ auto validateType = [&] (ESimpleLogicalValueType expected) {
+ if (!columnSchema.IsOfV1Type(expected)) {
+ THROW_ERROR_EXCEPTION("Invalid type of column %Qv: expected %Qlv, got %Qlv",
+ columnSchema.GetDiagnosticNameString(),
+ expected,
+ *columnSchema.LogicalType());
+ }
+ };
+
+ const auto& name = columnSchema.Name();
+
+ if (columnSchema.IsRenamed()) {
+ THROW_ERROR_EXCEPTION("System column schema must have equal name and stable name")
+ << TErrorAttribute("name", name)
+ << TErrorAttribute("stable_name", columnSchema.StableName().Get());
+ }
+
+ const auto& allowedSystemColumns = isTableSorted
+ ? allowedSortedTablesSystemColumns
+ : allowedOrderedTablesSystemColumns;
+ auto it = allowedSystemColumns.find(name);
+
+ // Ordinary system column.
+ if (it != allowedSystemColumns.end()) {
+ validateType(it->second);
+ return;
+ }
+
+ if (allowUnversionedUpdateColumns) {
+ // Unversioned update schema system column.
+ if (name == TUnversionedUpdateSchema::ChangeTypeColumnName) {
+ validateType(ESimpleLogicalValueType::Uint64);
+ return;
+ } else if (name.StartsWith(TUnversionedUpdateSchema::FlagsColumnNamePrefix)) {
+ validateType(ESimpleLogicalValueType::Uint64);
+ return;
+ } else if (name.StartsWith(TUnversionedUpdateSchema::ValueColumnNamePrefix)) {
+ // Value can have any type.
+ return;
+ }
+ }
+
+ // Unexpected system column.
+ THROW_ERROR_EXCEPTION("System column name %Qv is not allowed here",
+ name);
+}
+
+void ValidateColumnSchema(
+ const TColumnSchema& columnSchema,
+ bool isTableSorted,
+ bool isTableDynamic,
+ bool allowUnversionedUpdateColumns)
+{
+ static const auto allowedAggregates = THashSet<TString>{
+ "sum",
+ "min",
+ "max",
+ "first",
+ "xdelta"
+ };
+
+ const auto& stableName = columnSchema.StableName();
+ if (stableName.Get().empty()) {
+ THROW_ERROR_EXCEPTION("Column stable name cannot be empty");
+ }
+
+ const auto& name = columnSchema.Name();
+ if (name.empty()) {
+ THROW_ERROR_EXCEPTION("Column name cannot be empty");
+ }
+
+ try {
+ if (stableName.Get().StartsWith(SystemColumnNamePrefix) || name.StartsWith(SystemColumnNamePrefix)) {
+ ValidateSystemColumnSchema(columnSchema, isTableSorted, allowUnversionedUpdateColumns);
+ }
+
+ if (stableName.Get().size() > MaxColumnNameLength) {
+ THROW_ERROR_EXCEPTION("Column stable name is longer than maximum allowed: %v > %v",
+ stableName.Get().size(),
+ MaxColumnNameLength);
+ }
+
+ if (name.size() > MaxColumnNameLength) {
+ THROW_ERROR_EXCEPTION("Column name is longer than maximum allowed: %v > %v",
+ name.size(),
+ MaxColumnNameLength);
+ }
+
+ {
+ TComplexTypeFieldDescriptor descriptor(name, columnSchema.LogicalType());
+ ValidateLogicalType(descriptor, MaxSchemaDepth);
+ }
+
+ if (!IsComparable(columnSchema.LogicalType()) &&
+ columnSchema.SortOrder() &&
+ !columnSchema.IsOfV1Type(ESimpleLogicalValueType::Any))
+ {
+ THROW_ERROR_EXCEPTION("Key column cannot be of %Qv type",
+ *columnSchema.LogicalType());
+ }
+
+ if (*DetagLogicalType(columnSchema.LogicalType()) == *SimpleLogicalType(ESimpleLogicalValueType::Any)) {
+ THROW_ERROR_EXCEPTION("Column of type %Qlv cannot be required",
+ ESimpleLogicalValueType::Any);
+ }
+
+ if (columnSchema.Lock()) {
+ if (columnSchema.Lock()->empty()) {
+ THROW_ERROR_EXCEPTION("Column lock name cannot be empty");
+ }
+ if (columnSchema.Lock()->size() > MaxColumnLockLength) {
+ THROW_ERROR_EXCEPTION("Column lock name is longer than maximum allowed: %v > %v",
+ columnSchema.Lock()->size(),
+ MaxColumnLockLength);
+ }
+ if (columnSchema.SortOrder()) {
+ THROW_ERROR_EXCEPTION("Column lock cannot be set on a key column");
+ }
+ }
+
+ if (columnSchema.Group()) {
+ if (columnSchema.Group()->empty()) {
+ THROW_ERROR_EXCEPTION("Column group should either be unset or be non-empty");
+ }
+ if (columnSchema.Group()->size() > MaxColumnGroupLength) {
+ THROW_ERROR_EXCEPTION("Column group name is longer than maximum allowed: %v > %v",
+ columnSchema.Group()->size(),
+ MaxColumnGroupLength);
+ }
+ }
+
+ ValidateSchemaValueType(columnSchema.GetWireType());
+
+ if (columnSchema.Expression() && !columnSchema.SortOrder() && isTableDynamic) {
+ THROW_ERROR_EXCEPTION("Non-key column cannot be computed");
+ }
+
+ if (columnSchema.Aggregate() && columnSchema.SortOrder()) {
+ THROW_ERROR_EXCEPTION("Key column cannot be aggregated");
+ }
+
+ if (columnSchema.Aggregate() && allowedAggregates.find(*columnSchema.Aggregate()) == allowedAggregates.end()) {
+ THROW_ERROR_EXCEPTION("Invalid aggregate function %Qv",
+ *columnSchema.Aggregate());
+ }
+
+ if (columnSchema.Expression() && columnSchema.Required()) {
+ THROW_ERROR_EXCEPTION("Computed column cannot be required");
+ }
+
+ if (columnSchema.MaxInlineHunkSize()) {
+ if (columnSchema.MaxInlineHunkSize() <= 0) {
+ THROW_ERROR_EXCEPTION("Max inline hunk size must be positive");
+ }
+ if (!IsStringLikeType(columnSchema.GetWireType())) {
+ THROW_ERROR_EXCEPTION("Max inline hunk size can only be set for string-like columns, not %Qlv",
+ columnSchema.GetWireType());
+ }
+ if (columnSchema.SortOrder()) {
+ THROW_ERROR_EXCEPTION("Max inline hunk size cannot be set for key column");
+ }
+ if (columnSchema.Aggregate()) {
+ THROW_ERROR_EXCEPTION("Max inline hunk size cannot be set for aggregate column");
+ }
+ }
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error validating schema of column %v",
+ columnSchema.GetDiagnosticNameString())
+ << ex;
+ }
+}
+
+void ValidateDynamicTableConstraints(const TTableSchema& schema)
+{
+ if (!schema.GetStrict()) {
+ THROW_ERROR_EXCEPTION("\"strict\" cannot be \"false\" for a dynamic table");
+ }
+
+ if (schema.IsSorted() && !schema.GetUniqueKeys()) {
+ THROW_ERROR_EXCEPTION("\"unique_keys\" cannot be \"false\" for a sorted dynamic table");
+ }
+
+ if (schema.GetKeyColumnCount() == std::ssize(schema.Columns())) {
+ THROW_ERROR_EXCEPTION("There must be at least one non-key column");
+ }
+
+ if (schema.GetKeyColumnCount() > MaxKeyColumnCountInDynamicTable) {
+ THROW_ERROR_EXCEPTION("Too many key columns: limit %v, actual: %v",
+ MaxKeyColumnCountInDynamicTable,
+ schema.GetKeyColumnCount());
+ }
+
+ for (const auto& column : schema.Columns()) {
+ try {
+ if (column.SortOrder() && (
+ column.GetWireType() == EValueType::Any ||
+ column.GetWireType() == EValueType::Composite ||
+ !column.IsOfV1Type()))
+ {
+ THROW_ERROR_EXCEPTION("Dynamic table cannot have key column of type %Qv",
+ *column.LogicalType());
+ }
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error validating column %v in dynamic table schema",
+ column.GetDiagnosticNameString())
+ << ex;
+ }
+ }
+}
+
+//! Validates that there are no duplicates among the column names.
+void ValidateColumnUniqueness(const TTableSchema& schema)
+{
+ THashSet<TStringBuf> columnNames;
+ THashSet<TStringBuf> columnStableNames;
+ for (const auto& column : schema.Columns()) {
+ if (!columnStableNames.insert(column.StableName().Get()).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column stable name %Qv in table schema",
+ column.StableName());
+ }
+ if (!columnNames.insert(column.Name()).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column name %Qv in table schema",
+ column.Name());
+ }
+ }
+ for (const auto& deletedColumn : schema.DeletedColumns()) {
+ if (!columnStableNames.insert(deletedColumn.StableName().Get()).second) {
+ THROW_ERROR_EXCEPTION("Duplicate column stable name %Qv in table schema",
+ deletedColumn.StableName());
+ }
+ }
+}
+
+void ValidatePivotKey(
+ TUnversionedRow pivotKey,
+ const TTableSchema& schema,
+ TStringBuf keyType,
+ bool validateRequired)
+{
+ if (static_cast<int>(pivotKey.GetCount()) > schema.GetKeyColumnCount()) {
+ auto titleKeyType = TString(keyType);
+ titleKeyType.to_title();
+ THROW_ERROR_EXCEPTION(NTableClient::EErrorCode::SchemaViolation, "%v key must form a prefix of key", titleKeyType);
+ }
+
+ for (int index = 0; index < static_cast<int>(pivotKey.GetCount()); ++index) {
+ if (pivotKey[index].Type != EValueType::Null && pivotKey[index].Type != schema.Columns()[index].GetWireType()) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::SchemaViolation,
+ "Mismatched type of column %v in %v key: expected %Qlv, found %Qlv",
+ schema.Columns()[index].GetDiagnosticNameString(),
+ keyType,
+ schema.Columns()[index].GetWireType(),
+ pivotKey[index].Type);
+ }
+ if (validateRequired && pivotKey[index].Type == EValueType::Null && !schema.Columns()[index].LogicalType()->IsNullable()) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::SchemaViolation,
+ "Unexpected null for required column %v in %v key",
+ schema.Columns()[index].GetDiagnosticNameString(),
+ keyType);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Validates that number of locks doesn't exceed #MaxColumnLockCount.
+void ValidateLocks(const TTableSchema& schema)
+{
+ THashSet<TString> lockNames;
+ YT_VERIFY(lockNames.insert(PrimaryLockName).second);
+ for (const auto& column : schema.Columns()) {
+ if (column.Lock()) {
+ lockNames.insert(*column.Lock());
+ }
+ }
+
+ if (lockNames.size() > MaxColumnLockCount) {
+ THROW_ERROR_EXCEPTION("Too many column locks in table schema: actual %v, limit %v",
+ lockNames.size(),
+ MaxColumnLockCount);
+ }
+}
+
+//! Validates that key columns form a prefix of a table schema.
+void ValidateKeyColumnsFormPrefix(const TTableSchema& schema)
+{
+ for (int index = 0; index < schema.GetKeyColumnCount(); ++index) {
+ if (!schema.Columns()[index].SortOrder()) {
+ THROW_ERROR_EXCEPTION("Key columns must form a prefix of schema");
+ }
+ }
+ // The fact that first GetKeyColumnCount() columns have SortOrder automatically
+ // implies that the rest of columns don't have SortOrder, so we don't need to check it.
+}
+
+//! Validates |$timestamp| column, if any.
+/*!
+ * Validate that:
+ * - |$timestamp| column cannot be a part of key.
+ * - |$timestamp| column can only be present in ordered tables.
+ * - |$timestamp| column has type |uint64|.
+ */
+void ValidateTimestampColumn(const TTableSchema& schema)
+{
+ auto* column = schema.FindColumn(TimestampColumnName);
+ if (!column) {
+ return;
+ }
+
+ if (column->SortOrder()) {
+ THROW_ERROR_EXCEPTION("Column %Qv cannot be a part of key",
+ TimestampColumnName);
+ }
+
+ if (!column->IsOfV1Type(ESimpleLogicalValueType::Uint64)) {
+ THROW_ERROR_EXCEPTION("Column %Qv must have %Qlv type",
+ TimestampColumnName,
+ EValueType::Uint64);
+ }
+
+ if (schema.IsSorted()) {
+ THROW_ERROR_EXCEPTION("Column %Qv cannot appear in a sorted table",
+ TimestampColumnName);
+ }
+}
+
+//! Validates |$cumulative_data_weight| column, if any.
+/*!
+ * Validate that:
+ * - |$cumulative_data_weight| column cannot be a part of key.
+ * - |$cumulative_data_weight| column can only be present in ordered tables.
+ * - |$cumulative_data_weight| column has type |int64|.
+ */
+void ValidateCumulativeDataWeightColumn(const TTableSchema& schema)
+{
+ auto* column = schema.FindColumn(CumulativeDataWeightColumnName);
+ if (!column) {
+ return;
+ }
+
+ if (column->SortOrder()) {
+ THROW_ERROR_EXCEPTION(
+ "Column %Qv cannot be a part of key",
+ CumulativeDataWeightColumnName);
+ }
+
+ if (!column->IsOfV1Type(ESimpleLogicalValueType::Int64)) {
+ THROW_ERROR_EXCEPTION(
+ "Column %Qv must have %Qlv type",
+ CumulativeDataWeightColumnName,
+ EValueType::Int64);
+ }
+
+ if (schema.IsSorted()) {
+ THROW_ERROR_EXCEPTION(
+ "Column %Qv cannot appear in a sorted table",
+ CumulativeDataWeightColumnName);
+ }
+}
+
+// Validate schema attributes.
+void ValidateSchemaAttributes(const TTableSchema& schema)
+{
+ if (schema.GetUniqueKeys() && schema.GetKeyColumnCount() == 0) {
+ THROW_ERROR_EXCEPTION("\"unique_keys\" can only be true if key columns are present");
+ }
+}
+
+void ValidateTableSchema(const TTableSchema& schema, bool isTableDynamic, bool allowUnversionedUpdateColumns)
+{
+ int totalTypeComplexity = 0;
+ for (const auto& column : schema.Columns()) {
+ ValidateColumnSchema(
+ column,
+ schema.IsSorted(),
+ isTableDynamic,
+ allowUnversionedUpdateColumns);
+ if (!schema.GetStrict() && column.IsRenamed()) {
+ THROW_ERROR_EXCEPTION("Renamed column %v in non-strict schema",
+ column.GetDiagnosticNameString());
+ }
+ totalTypeComplexity += column.LogicalType()->GetTypeComplexity();
+ }
+ if (totalTypeComplexity >= MaxSchemaTotalTypeComplexity) {
+ THROW_ERROR_EXCEPTION("Table schema is too complex, reduce number of columns or simplify their types");
+ }
+ ValidateColumnUniqueness(schema);
+ ValidateLocks(schema);
+ ValidateKeyColumnsFormPrefix(schema);
+ ValidateTimestampColumn(schema);
+ ValidateCumulativeDataWeightColumn(schema);
+ ValidateSchemaAttributes(schema);
+ if (isTableDynamic) {
+ ValidateDynamicTableConstraints(schema);
+ }
+}
+
+void ValidateNoDescendingSortOrder(const TTableSchema& schema)
+{
+ for (const auto& column : schema.Columns()) {
+ if (column.SortOrder() == ESortOrder::Descending) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::InvalidSchemaValue,
+ "Descending sort order is not available in this context yet")
+ << TErrorAttribute("column_name", column.Name());
+ }
+ }
+}
+
+void ValidateNoRenamedColumns(const TTableSchema& schema)
+{
+ for (const auto& column : schema.Columns()) {
+ if (column.IsRenamed()) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::InvalidSchemaValue,
+ "Table column renaming is not available yet")
+ << TErrorAttribute("renamed_column", column.GetDiagnosticNameString());
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+THashMap<TString, int> GetLocksMapping(
+ const NTableClient::TTableSchema& schema,
+ bool fullAtomicity,
+ std::vector<int>* columnIndexToLockIndex,
+ std::vector<TString>* lockIndexToName)
+{
+ if (columnIndexToLockIndex) {
+ // Assign dummy lock indexes to key components.
+ columnIndexToLockIndex->assign(schema.Columns().size(), -1);
+ }
+
+ if (lockIndexToName) {
+ lockIndexToName->push_back(PrimaryLockName);
+ }
+
+ THashMap<TString, int> groupToIndex;
+ if (fullAtomicity) {
+ // Assign lock indexes to data components.
+ for (int index = schema.GetKeyColumnCount(); index < std::ssize(schema.Columns()); ++index) {
+ const auto& columnSchema = schema.Columns()[index];
+ int lockIndex = PrimaryLockIndex;
+
+ if (columnSchema.Lock()) {
+ auto emplaced = groupToIndex.emplace(*columnSchema.Lock(), groupToIndex.size() + 1);
+ if (emplaced.second && lockIndexToName) {
+ lockIndexToName->push_back(*columnSchema.Lock());
+ }
+ lockIndex = emplaced.first->second;
+ }
+
+ if (columnIndexToLockIndex) {
+ (*columnIndexToLockIndex)[index] = lockIndex;
+ }
+ }
+ } else if (columnIndexToLockIndex) {
+ // No locking supported for non-atomic tablets, however we still need the primary
+ // lock descriptor to maintain last commit timestamps.
+ for (int index = schema.GetKeyColumnCount(); index < std::ssize(schema.Columns()); ++index) {
+ (*columnIndexToLockIndex)[index] = PrimaryLockIndex;
+ }
+ }
+ return groupToIndex;
+}
+
+TLockMask GetLockMask(
+ const NTableClient::TTableSchema& schema,
+ bool fullAtomicity,
+ const std::vector<TString>& locks,
+ ELockType lockType)
+{
+ auto groupToIndex = GetLocksMapping(schema, fullAtomicity);
+
+ TLockMask lockMask;
+ for (const auto& lock : locks) {
+ auto it = groupToIndex.find(lock);
+ if (it != groupToIndex.end()) {
+ lockMask.Set(it->second, lockType);
+ } else {
+ THROW_ERROR_EXCEPTION("Lock group %Qv not found in schema", lock);
+ }
+ }
+ return lockMask;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NProto {
+
+using NYT::ToProto;
+using NYT::FromProto;
+
+void ToProto(TKeyColumnsExt* protoKeyColumns, const TKeyColumns& keyColumns)
+{
+ ToProto(protoKeyColumns->mutable_names(), keyColumns);
+}
+
+void FromProto(TKeyColumns* keyColumns, const TKeyColumnsExt& protoKeyColumns)
+{
+ *keyColumns = FromProto<TKeyColumns>(protoKeyColumns.names());
+}
+
+void ToProto(TColumnFilter* protoColumnFilter, const NTableClient::TColumnFilter& columnFilter)
+{
+ if (!columnFilter.IsUniversal()) {
+ for (auto index : columnFilter.GetIndexes()) {
+ protoColumnFilter->add_indexes(index);
+ }
+ }
+}
+
+void FromProto(NTableClient::TColumnFilter* columnFilter, const TColumnFilter& protoColumnFilter)
+{
+ *columnFilter = protoColumnFilter.indexes().empty()
+ ? NTableClient::TColumnFilter()
+ : NTableClient::TColumnFilter(FromProto<NTableClient::TColumnFilter::TIndexes>(protoColumnFilter.indexes()));
+}
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCellTaggedTableSchema::TCellTaggedTableSchema(TTableSchema tableSchema, TCellTag cellTag)
+ : TableSchema(std::move(tableSchema))
+ , CellTag(cellTag)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCellTaggedTableSchemaPtr::TCellTaggedTableSchemaPtr(TTableSchemaPtr tableSchema, TCellTag cellTag)
+ : TableSchema(std::move(tableSchema))
+ , CellTag(cellTag)
+{
+ YT_VERIFY(TableSchema);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+size_t THash<NYT::NTableClient::TStableName>::operator()(const NYT::NTableClient::TStableName& stableName) const
+{
+ return THash<TString>()(stableName.Get());
+}
+
+size_t THash<NYT::NTableClient::TColumnSchema>::operator()(const NYT::NTableClient::TColumnSchema& columnSchema) const
+{
+ return MultiHash(
+ columnSchema.StableName(),
+ columnSchema.Name(),
+ *columnSchema.LogicalType(),
+ columnSchema.SortOrder(),
+ columnSchema.Lock(),
+ columnSchema.Expression(),
+ columnSchema.Aggregate(),
+ columnSchema.Group(),
+ columnSchema.MaxInlineHunkSize());
+}
+
+size_t THash<NYT::NTableClient::TDeletedColumn>::operator()(const NYT::NTableClient::TDeletedColumn& columnSchema) const
+{
+ return THash<NYT::NTableClient::TStableName>()(columnSchema.StableName());
+}
+
+size_t THash<NYT::NTableClient::TTableSchema>::operator()(const NYT::NTableClient::TTableSchema& tableSchema) const
+{
+ size_t result = CombineHashes(THash<bool>()(tableSchema.GetUniqueKeys()), THash<bool>()(tableSchema.GetStrict()));
+ if (tableSchema.HasNontrivialSchemaModification()) {
+ result = CombineHashes(
+ result,
+ THash<NYT::NTableClient::ETableSchemaModification>()(tableSchema.GetSchemaModification()));
+ }
+ for (const auto& columnSchema : tableSchema.Columns()) {
+ result = CombineHashes(result, THash<NYT::NTableClient::TColumnSchema>()(columnSchema));
+ }
+ for (const auto& deletedColumnSchema : tableSchema.DeletedColumns()) {
+ result = CombineHashes(result, THash<NYT::NTableClient::TDeletedColumn>()(
+ deletedColumnSchema));
+ }
+ return result;
+}
diff --git a/yt/yt/client/table_client/schema.h b/yt/yt/client/table_client/schema.h
new file mode 100644
index 0000000000..e8420db240
--- /dev/null
+++ b/yt/yt/client/table_client/schema.h
@@ -0,0 +1,604 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/property.h>
+#include <yt/yt/core/misc/range.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <util/digest/multi.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr int PrimaryLockIndex = 0;
+
+DEFINE_ENUM(ELockType,
+ ((None) (0))
+ ((SharedWeak) (1))
+ ((SharedStrong) (2))
+ ((Exclusive) (3))
+);
+
+// COMPAT(gritukan)
+constexpr ELockType MaxOldLockType = ELockType::Exclusive;
+
+ELockType GetStrongestLock(ELockType lhs, ELockType rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLegacyLockMask
+{
+public:
+ explicit TLegacyLockMask(TLegacyLockBitmap value = 0);
+
+ ELockType Get(int index) const;
+ void Set(int index, ELockType lock);
+
+ void Enrich(int columnCount);
+
+ TLegacyLockBitmap GetBitmap() const;
+
+ TLegacyLockMask(const TLegacyLockMask& other) = default;
+ TLegacyLockMask& operator= (const TLegacyLockMask& other) = default;
+
+ static constexpr int BitsPerType = 2;
+ static constexpr TLegacyLockBitmap TypeMask = (1 << BitsPerType) - 1;
+ static constexpr int MaxCount = 8 * sizeof(TLegacyLockBitmap) / BitsPerType;
+
+private:
+ TLegacyLockBitmap Data_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLockMask
+{
+public:
+ TLockMask() = default;
+
+ TLockMask(TLockBitmap bitmap, int size);
+
+ ELockType Get(int index) const;
+ void Set(int index, ELockType lock);
+
+ void Enrich(int size);
+
+ int GetSize() const;
+ TLockBitmap GetBitmap() const;
+
+ // COMPAT(gritukan)
+ TLegacyLockMask ToLegacyMask() const;
+ bool HasNewLocks() const;
+
+ static constexpr int BitsPerType = 4;
+ static_assert(static_cast<int>(TEnumTraits<ELockType>::GetMaxValue()) < (1 << BitsPerType));
+
+ static constexpr ui64 LockMask = (1 << BitsPerType) - 1;
+
+ static constexpr int LocksPerWord = 8 * sizeof(TLockBitmap::value_type) / BitsPerType;
+ static_assert(IsPowerOf2(LocksPerWord));
+
+ // Size of the lock mask should fit into ui16 for wire protocol.
+ static constexpr int MaxSize = (1 << 16) - 1;
+
+private:
+ TLockBitmap Bitmap_;
+ int Size_ = 0;
+
+ void Reserve(int size);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLockMask MaxMask(TLockMask lhs, TLockMask rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//
+// Strong typedef to avoid mixing stable names and names.
+class TStableName
+{
+public:
+ explicit TStableName(TString stableName = "");
+ const TString& Get() const;
+
+private:
+ TString Name_;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TStableName& stableName, TStringBuf spec);
+
+bool operator == (const TStableName& lhs, const TStableName& rhs);
+bool operator < (const TStableName& lhs, const TStableName& rhs);
+
+void ToProto(TString* protoStableName, const TStableName& stableName);
+void FromProto(TStableName* stableName, const TString& protoStableName);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TColumnSchema
+{
+public:
+ // Keep in sync with hasher below.
+ DEFINE_BYREF_RO_PROPERTY(TStableName, StableName);
+ DEFINE_BYREF_RO_PROPERTY(TString, Name);
+ DEFINE_BYREF_RO_PROPERTY(TLogicalTypePtr, LogicalType);
+ DEFINE_BYREF_RO_PROPERTY(std::optional<ESortOrder>, SortOrder);
+ DEFINE_BYREF_RO_PROPERTY(std::optional<TString>, Lock);
+ DEFINE_BYREF_RO_PROPERTY(std::optional<TString>, Expression);
+ DEFINE_BYREF_RO_PROPERTY(std::optional<TString>, Aggregate);
+ DEFINE_BYREF_RO_PROPERTY(std::optional<TString>, Group);
+ DEFINE_BYREF_RO_PROPERTY(bool, Required);
+ DEFINE_BYREF_RO_PROPERTY(std::optional<i64>, MaxInlineHunkSize);
+
+public:
+ TColumnSchema();
+ TColumnSchema(
+ TString name,
+ EValueType type,
+ std::optional<ESortOrder> sortOrder = {});
+ TColumnSchema(
+ TString name,
+ ESimpleLogicalValueType type,
+ std::optional<ESortOrder> sortOrder = {});
+
+ TColumnSchema(
+ TString name,
+ TLogicalTypePtr type,
+ std::optional<ESortOrder> sortOrder = {});
+
+ TColumnSchema(const TColumnSchema&) = default;
+ TColumnSchema(TColumnSchema&&) = default;
+
+ TColumnSchema& operator=(const TColumnSchema&) = default;
+ TColumnSchema& operator=(TColumnSchema&&) = default;
+
+ TColumnSchema& SetStableName(TStableName stableName);
+ TColumnSchema& SetName(TString name);
+ TColumnSchema& SetLogicalType(TLogicalTypePtr valueType);
+ TColumnSchema& SetSimpleLogicalType(ESimpleLogicalValueType type);
+ TColumnSchema& SetSortOrder(std::optional<ESortOrder> value);
+ TColumnSchema& SetLock(std::optional<TString> value);
+ TColumnSchema& SetExpression(std::optional<TString> value);
+ TColumnSchema& SetAggregate(std::optional<TString> value);
+ TColumnSchema& SetGroup(std::optional<TString> value);
+ TColumnSchema& SetRequired(bool value);
+ TColumnSchema& SetMaxInlineHunkSize(std::optional<i64> value);
+
+ EValueType GetWireType() const;
+
+ i64 GetMemoryUsage() const;
+
+ // Check if column has plain old v1 type.
+ bool IsOfV1Type() const;
+
+ // Check if column has specified v1 type.
+ bool IsOfV1Type(ESimpleLogicalValueType type) const;
+
+ ESimpleLogicalValueType CastToV1Type() const;
+
+ bool IsRenamed() const;
+ TString GetDiagnosticNameString() const;
+
+private:
+ ESimpleLogicalValueType V1Type_;
+ bool IsOfV1Type_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDeletedColumn
+{
+public:
+ TDeletedColumn();
+ explicit TDeletedColumn(TStableName stableName);
+
+ DEFINE_BYREF_RO_PROPERTY(TStableName, StableName);
+ TDeletedColumn& SetStableName(TStableName stableName);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TColumnSchema& schema, TStringBuf spec);
+
+void Serialize(const TColumnSchema& schema, NYson::IYsonConsumer* consumer);
+
+void ToProto(NProto::TColumnSchema* protoSchema, const TColumnSchema& schema);
+void FromProto(TColumnSchema* schema, const NProto::TColumnSchema& protoSchema);
+
+void ToProto(NProto::TDeletedColumn* protoSchema, const TDeletedColumn& schema);
+void FromProto(TDeletedColumn* schema, const NProto::TDeletedColumn& protoSchema);
+
+void PrintTo(const TColumnSchema& columnSchema, std::ostream* os);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableSchema final
+{
+public:
+ class TNameMapping
+ {
+ public:
+ explicit TNameMapping(const TTableSchema& schema);
+
+ bool IsDeleted(const TStableName& stableName) const;
+ TString StableNameToName(const TStableName& stableName) const;
+ TStableName NameToStableName(TStringBuf name) const;
+
+ private:
+ const TTableSchema& Schema_;
+ };
+
+public:
+ const std::vector<TColumnSchema>& Columns() const;
+ const std::vector<TDeletedColumn>& DeletedColumns() const;
+
+ //! Strict schema forbids columns not specified in the schema.
+ DEFINE_BYVAL_RO_PROPERTY(bool, Strict, false);
+ DEFINE_BYVAL_RO_PROPERTY(bool, UniqueKeys, false);
+ DEFINE_BYVAL_RO_PROPERTY(ETableSchemaModification, SchemaModification, ETableSchemaModification::None);
+
+ //! Constructs an empty non-strict schema.
+ TTableSchema() = default;
+
+ //! Constructs a schema with given columns and strictness flag.
+ //! No validation is performed.
+ explicit TTableSchema(
+ std::vector<TColumnSchema> columns,
+ bool strict = true,
+ bool uniqueKeys = false,
+ ETableSchemaModification schemaModification = ETableSchemaModification::None,
+ std::vector<TDeletedColumn> deletedColumns = {});
+
+ const TColumnSchema* FindColumnByStableName(const TStableName& stableName) const;
+ const TDeletedColumn* FindDeletedColumn(const TStableName& stableName) const;
+
+ int GetColumnIndex(const TColumnSchema& column) const;
+
+ int GetColumnIndex(TStringBuf name) const;
+ int GetColumnIndexOrThrow(TStringBuf name) const;
+
+ TNameMapping GetNameMapping() const;
+
+ const TColumnSchema* FindColumn(TStringBuf name) const;
+ const TColumnSchema& GetColumn(TStringBuf name) const;
+ const TColumnSchema& GetColumnOrThrow(TStringBuf name) const;
+ std::vector<TString> GetColumnNames() const;
+
+ TTableSchemaPtr Filter(
+ const TColumnFilter& columnFilter,
+ bool discardSortOrder = false) const;
+ TTableSchemaPtr Filter(
+ const THashSet<TString>& columnNames,
+ bool discardSortOrder = false) const;
+ TTableSchemaPtr Filter(
+ const std::optional<std::vector<TString>>& columnNames,
+ bool discardSortOrder = false) const;
+
+ bool HasComputedColumns() const;
+ bool HasAggregateColumns() const;
+ bool HasHunkColumns() const;
+ bool HasTimestampColumn() const;
+ bool IsSorted() const;
+ bool IsUniqueKeys() const;
+ bool HasRenamedColumns() const;
+ bool IsEmpty() const;
+
+ std::vector<TStableName> GetKeyColumnStableNames() const;
+ TKeyColumns GetKeyColumnNames() const;
+ TKeyColumns GetKeyColumns() const;
+
+ int GetColumnCount() const;
+ int GetKeyColumnCount() const;
+ int GetValueColumnCount() const;
+ std::vector<TStableName> GetColumnStableNames() const;
+ const THunkColumnIds& GetHunkColumnIds() const;
+
+ TSortColumns GetSortColumns(const std::optional<TNameMapping>& nameMapping = std::nullopt) const;
+
+ bool HasNontrivialSchemaModification() const;
+
+ //! Constructs a non-strict schema from #keyColumns assigning all components EValueType::Any type.
+ //! #keyColumns could be empty, in which case an empty non-strict schema is returned.
+ //! The resulting schema is validated.
+ static TTableSchemaPtr FromKeyColumns(const TKeyColumns& keyColumns);
+
+ //! Same as above, but infers key column sort orders from #sortColumns.
+ static TTableSchemaPtr FromSortColumns(const TSortColumns& sortColumns);
+
+ //! Returns schema with `UniqueKeys' set to given value.
+ TTableSchemaPtr SetUniqueKeys(bool uniqueKeys) const;
+
+ //! Returns schema with `SchemaModification' set to given value.
+ TTableSchemaPtr SetSchemaModification(ETableSchemaModification schemaModification) const;
+
+ //! For sorted tables, return the current schema as-is.
+ //! For ordered tables, prepends the current schema with |(tablet_index, row_index)| key columns.
+ TTableSchemaPtr ToQuery() const;
+
+ //! For sorted tables, return the current schema without computed columns.
+ //! For ordered tables, prepends the current schema with |(tablet_index)| key column
+ //! but without |$timestamp| column, if any.
+ TTableSchemaPtr ToWrite() const;
+
+ //! For sorted tables, return the current schema
+ //! For ordered tables, prepends the current schema with |(tablet_index)| key column.
+ TTableSchemaPtr WithTabletIndex() const;
+
+ //! Returns the current schema as-is.
+ //! For ordered tables, prepends the current schema with |(tablet_index)| key column.
+ TTableSchemaPtr ToVersionedWrite() const;
+
+ //! For sorted tables, returns the non-computed key columns.
+ //! For ordered tables, returns an empty schema.
+ TTableSchemaPtr ToLookup() const;
+
+ //! For sorted tables, returns the non-computed key columns.
+ //! For ordered tables, returns an empty schema.
+ TTableSchemaPtr ToDelete() const;
+
+ //! Returns just the key columns.
+ TTableSchemaPtr ToKeys() const;
+
+ //! Returns the schema with UniqueKeys set to |true|.
+ TTableSchemaPtr ToUniqueKeys() const;
+
+ //! Returns the schema with all column attributes unset except
+ //! StableName, Name, Type and Required.
+ TTableSchemaPtr ToStrippedColumnAttributes() const;
+
+ //! Returns the schema with all column attributes unset except
+ //! StableName, Name, Type, Required and SortOrder.
+ TTableSchemaPtr ToSortedStrippedColumnAttributes() const;
+
+ //! Returns (possibly reordered) schema sorted by column names.
+ TTableSchemaPtr ToCanonical() const;
+
+ //! Returns (possibly reordered) schema with set key columns.
+ TTableSchemaPtr ToSorted(const TKeyColumns& keyColumns) const;
+ TTableSchemaPtr ToSorted(const TSortColumns& sortColumns) const;
+
+ //! Only applies to sorted replicated tables.
+ //! Returns the ordered schema used in replication logs.
+ TTableSchemaPtr ToReplicationLog() const;
+
+ //! Only applies to sorted dynamic tables.
+ //! Returns the static schema used for unversioned updates from bulk insert.
+ //! Key columns remain unchanged. Additional column |($change_type)| is prepended.
+ //! Each value column |name| is replaced with two columns |($value:name)| and |($flags:name)|.
+ //! If |sorted| is |false|, sort order is removed from key columns.
+ TTableSchemaPtr ToUnversionedUpdate(bool sorted = true) const;
+
+ TTableSchemaPtr ToModifiedSchema(ETableSchemaModification schemaModification) const;
+
+ TComparator ToComparator() const;
+
+ TKeyColumnTypes GetKeyColumnTypes() const;
+
+ void Save(TStreamSaveContext& context) const;
+ void Load(TStreamLoadContext& context);
+
+ i64 GetMemoryUsage() const;
+
+private:
+ std::shared_ptr<const std::vector<TColumnSchema>> Columns_;
+ std::vector<TDeletedColumn> DeletedColumns_;
+
+ int KeyColumnCount_ = 0;
+ bool HasComputedColumns_ = false;
+ bool HasAggregateColumns_ = false;
+ THunkColumnIds HunkColumnsIds_;
+
+ // NB: Strings are owned by Columns_, addresses are immutable
+ // inside TTableSchema.
+ THashMap<TStringBuf, int> StableNameToColumnIndex_;
+ THashMap<TStringBuf, int> NameToColumnIndex_;
+ THashMap<TStringBuf, int> StableNameToDeletedColumnIndex_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableSchema)
+
+void FormatValue(TStringBuilderBase* builder, const TTableSchema& schema, TStringBuf spec);
+void FormatValue(TStringBuilderBase* builder, const TTableSchemaPtr& schema, TStringBuf spec);
+
+TString ToString(const TTableSchema& schema);
+TString ToString(const TTableSchemaPtr& schema);
+
+//! Returns serialized NTableClient.NProto.TTableSchemaExt.
+TString SerializeToWireProto(const TTableSchemaPtr& schema);
+
+void DeserializeFromWireProto(TTableSchemaPtr* schema, const TString& serializedProto);
+
+void Serialize(const TTableSchema& schema, NYson::IYsonConsumer* consumer);
+void Deserialize(TTableSchema& schema, NYTree::INodePtr node);
+void Deserialize(TTableSchema& schema, NYson::TYsonPullParserCursor* cursor);
+
+void Serialize(const TTableSchemaPtr& schema, NYson::IYsonConsumer* consumer);
+void Deserialize(TTableSchemaPtr& schema, NYTree::INodePtr node);
+void Deserialize(TTableSchemaPtr& schema, NYson::TYsonPullParserCursor* cursor);
+
+void ToProto(NProto::TTableSchemaExt* protoSchema, const TTableSchema& schema);
+void FromProto(TTableSchema* schema, const NProto::TTableSchemaExt& protoSchema);
+void FromProto(
+ TTableSchema* schema,
+ const NProto::TTableSchemaExt& protoSchema,
+ const NProto::TKeyColumnsExt& keyColumnsExt);
+
+void ToProto(NProto::TTableSchemaExt* protoSchema, const TTableSchemaPtr& schema);
+void FromProto(TTableSchemaPtr* schema, const NProto::TTableSchemaExt& protoSchema);
+void FromProto(
+ TTableSchemaPtr* schema,
+ const NProto::TTableSchemaExt& protoSchema,
+ const NProto::TKeyColumnsExt& keyColumnsExt);
+
+void PrintTo(const TTableSchema& tableSchema, std::ostream* os);
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (const TColumnSchema& lhs, const TColumnSchema& rhs);
+
+bool operator == (const TDeletedColumn& lhs, const TDeletedColumn& rhs);
+
+bool operator == (const TTableSchema& lhs, const TTableSchema& rhs);
+
+// Compat function for https://st.yandex-team.ru/YT-10668 workaround.
+bool IsEqualIgnoringRequiredness(const TTableSchema& lhs, const TTableSchema& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr TStringBuf NonexistentColumnName = "$__YT_NONEXISTENT_COLUMN_NAME__";
+
+std::vector<TStableName> MapNamesToStableNames(
+ const TTableSchema& schema,
+ std::vector<TString> names,
+ const std::optional<TStringBuf>& missingColumnReplacement = std::nullopt);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateKeyColumns(const TKeyColumns& keyColumns);
+
+void ValidateColumnSchema(
+ const TColumnSchema& columnSchema,
+ bool isTableSorted = false,
+ bool isTableDynamic = false,
+ bool allowUnversionedUpdateColumns = false);
+
+void ValidateTableSchema(
+ const TTableSchema& schema,
+ bool isTableDynamic = false,
+ bool allowUnversionedUpdateColumns = false);
+
+void ValidateNoDescendingSortOrder(const TTableSchema& schema);
+
+void ValidateNoRenamedColumns(const TTableSchema& schema);
+
+void ValidateColumnUniqueness(const TTableSchema& schema);
+
+void ValidatePivotKey(
+ TUnversionedRow pivotKey,
+ const TTableSchema& schema,
+ TStringBuf keyType = "pivot",
+ bool validateRequired = false);
+
+////////////////////////////////////////////////////////////////////////////////
+
+THashMap<TString, int> GetLocksMapping(
+ const NTableClient::TTableSchema& schema,
+ bool fullAtomicity,
+ std::vector<int>* columnIndexToLockIndex = nullptr,
+ std::vector<TString>* lockIndexToName = nullptr);
+
+TLockMask GetLockMask(
+ const NTableClient::TTableSchema& schema,
+ bool fullAtomicity,
+ const std::vector<TString>& locks,
+ ELockType lockType = ELockType::SharedWeak);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Need to place this into NProto for ADL to work properly since TKeyColumns is std::vector.
+namespace NProto {
+
+void ToProto(NProto::TKeyColumnsExt* protoKeyColumns, const TKeyColumns& keyColumns);
+void FromProto(TKeyColumns* keyColumns, const NProto::TKeyColumnsExt& protoKeyColumns);
+
+void ToProto(TColumnFilter* protoColumnFilter, const NTableClient::TColumnFilter& columnFilter);
+void FromProto(NTableClient::TColumnFilter* columnFilter, const TColumnFilter& protoColumnFilter);
+
+} // namespace NProto
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Incompatible < RequireValidation < FullyCompatible
+constexpr bool operator < (ESchemaCompatibility lhs, ESchemaCompatibility rhs);
+constexpr bool operator <= (ESchemaCompatibility lhs, ESchemaCompatibility rhs);
+constexpr bool operator > (ESchemaCompatibility lhs, ESchemaCompatibility rhs);
+constexpr bool operator >= (ESchemaCompatibility lhs, ESchemaCompatibility rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTableSchemaHash
+{
+ size_t operator() (const TTableSchema& schema) const;
+ size_t operator() (const TTableSchemaPtr& schema) const;
+};
+
+struct TTableSchemaEquals
+{
+ bool operator() (const TTableSchema& lhs, const TTableSchema& rhs) const;
+ bool operator() (const TTableSchemaPtr& lhs, const TTableSchemaPtr& rhs) const;
+ bool operator() (const TTableSchemaPtr& lhs, const TTableSchema& rhs) const;
+};
+
+struct TCellTaggedTableSchema
+{
+ TCellTaggedTableSchema(TTableSchema tableSchema, NObjectClient::TCellTag cellTag);
+
+ TTableSchema TableSchema;
+ NObjectClient::TCellTag CellTag;
+};
+
+struct TCellTaggedTableSchemaPtr
+{
+ TCellTaggedTableSchemaPtr(TTableSchemaPtr tableSchema, NObjectClient::TCellTag cellTag);
+
+ TTableSchemaPtr TableSchema;
+ NObjectClient::TCellTag CellTag;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TCellTaggedTableSchemaHash
+{
+ size_t operator() (const TCellTaggedTableSchema& cellTaggedSchema) const;
+ size_t operator() (const TCellTaggedTableSchemaPtr& cellTaggedSchemaPtr) const;
+};
+
+struct TCellTaggedTableSchemaEquals
+{
+ bool operator() (const TCellTaggedTableSchema& lhs, const TCellTaggedTableSchema& rhs) const;
+ bool operator() (const TCellTaggedTableSchemaPtr& lhs, const TCellTaggedTableSchemaPtr& rhs) const;
+ bool operator() (const TCellTaggedTableSchemaPtr& lhs, const TCellTaggedTableSchema& rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <>
+struct THash<NYT::NTableClient::TStableName>
+{
+ size_t operator()(const NYT::NTableClient::TStableName& stableName) const;
+};
+
+template <>
+struct THash<NYT::NTableClient::TDeletedColumn>
+{
+ size_t operator()(const NYT::NTableClient::TDeletedColumn& deletedColumn) const;
+};
+
+template <>
+struct THash<NYT::NTableClient::TColumnSchema>
+{
+ size_t operator()(const NYT::NTableClient::TColumnSchema& columnSchema) const;
+};
+
+template <>
+struct THash<NYT::NTableClient::TTableSchema>
+{
+ size_t operator()(const NYT::NTableClient::TTableSchema& tableSchema) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define SCHEMA_INL_H_
+#include "schema-inl.h"
+#undef SCHEMA_INL_H_
diff --git a/yt/yt/client/table_client/schema_serialization_helpers.cpp b/yt/yt/client/table_client/schema_serialization_helpers.cpp
new file mode 100644
index 0000000000..f9ac42f59c
--- /dev/null
+++ b/yt/yt/client/table_client/schema_serialization_helpers.cpp
@@ -0,0 +1,343 @@
+
+#include "comparator.h"
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/client/table_client/schema_serialization_helpers.h>
+
+namespace NYT::NTableClient {
+
+void Deserialize(TMaybeDeletedColumnSchema& schema, NYson::TYsonPullParserCursor* cursor)
+{
+ TSerializableColumnSchema wrapper = TSerializableColumnSchema::Create();
+ wrapper.DeserializeFromCursor(cursor);
+ schema = wrapper;
+}
+
+void Deserialize(TMaybeDeletedColumnSchema& schema, NYTree::INodePtr node)
+{
+ TSerializableColumnSchema wrapper = TSerializableColumnSchema::Create();
+ Deserialize(static_cast<NYTree::TYsonStructLite&>(wrapper), node);
+ schema = static_cast<TMaybeDeletedColumnSchema>(wrapper);
+}
+
+TDeletedColumn TMaybeDeletedColumnSchema::GetDeletedColumnSchema() const
+{
+ return TDeletedColumn(StableName());
+}
+
+void TSerializableColumnSchema::Register(TRegistrar registrar)
+{
+ registrar.BaseClassParameter("name", &TThis::Name_)
+ .Default();
+ registrar.Parameter("stable_name", &TThis::SerializedStableName_)
+ .Default();
+ registrar.Parameter("type", &TThis::LogicalTypeV1_)
+ .Default(std::nullopt);
+ registrar.Parameter("required", &TThis::RequiredV1_)
+ .Default(std::nullopt);
+ registrar.Parameter("type_v3", &TThis::LogicalTypeV3_)
+ .Default();
+ registrar.BaseClassParameter("lock", &TThis::Lock_)
+ .Default();
+ registrar.BaseClassParameter("expression", &TThis::Expression_)
+ .Default();
+ registrar.BaseClassParameter("aggregate", &TThis::Aggregate_)
+ .Default();
+ registrar.BaseClassParameter("sort_order", &TThis::SortOrder_)
+ .Default();
+ registrar.BaseClassParameter("group", &TThis::Group_)
+ .Default();
+ registrar.BaseClassParameter("max_inline_hunk_size", &TThis::MaxInlineHunkSize_)
+ .Default();
+ registrar.BaseClassParameter("deleted", &TThis::Deleted_).Default(std::nullopt);
+
+ registrar.Postprocessor([] (TSerializableColumnSchema* schema) {
+ schema->RunPostprocessor();
+ });
+}
+
+void TSerializableColumnSchema::DeserializeFromCursor(NYson::TYsonPullParserCursor* cursor)
+{
+ cursor->ParseMap([&] (NYson::TYsonPullParserCursor* cursor) {
+ EnsureYsonToken("column schema attribute key", *cursor, NYson::EYsonItemType::StringValue);
+ auto key = (*cursor)->UncheckedAsString();
+ if (key == TStringBuf("name")) {
+ cursor->Next();
+ SetName(ExtractTo<TString>(cursor));
+ } else if (key == TStringBuf("required")) {
+ cursor->Next();
+ RequiredV1_ = ExtractTo<bool>(cursor);
+ } else if (key == TStringBuf("type")) {
+ cursor->Next();
+ LogicalTypeV1_ = ExtractTo<ESimpleLogicalValueType>(cursor);
+ } else if (key == TStringBuf("type_v3")) {
+ cursor->Next();
+ LogicalTypeV3_ = TTypeV3LogicalTypeWrapper();
+ Deserialize(*LogicalTypeV3_, cursor);
+ } else if (key == TStringBuf("lock")) {
+ cursor->Next();
+ SetLock(ExtractTo<std::optional<TString>>(cursor));
+ } else if (key == TStringBuf("expression")) {
+ cursor->Next();
+ SetExpression(ExtractTo<std::optional<TString>>(cursor));
+ } else if (key == TStringBuf("aggregate")) {
+ cursor->Next();
+ SetAggregate(ExtractTo<std::optional<TString>>(cursor));
+ } else if (key == TStringBuf("sort_order")) {
+ cursor->Next();
+ SetSortOrder(ExtractTo<std::optional<ESortOrder>>(cursor));
+ } else if (key == TStringBuf("group")) {
+ cursor->Next();
+ SetGroup(ExtractTo<std::optional<TString>>(cursor));
+ } else if (key == TStringBuf("max_inline_hunk_size")) {
+ cursor->Next();
+ SetMaxInlineHunkSize(ExtractTo<std::optional<i64>>(cursor));
+ } else if (key == TStringBuf("stable_name")) {
+ cursor->Next();
+ SerializedStableName_ = ExtractTo<TString>(cursor);
+ } else if (key == TStringBuf("deleted")) {
+ cursor->Next();
+ Deleted_ = ExtractTo<bool>(cursor);
+ } else {
+ cursor->Next();
+ cursor->SkipComplexValue();
+ }
+ });
+
+ RunPostprocessor();
+}
+
+void TSerializableColumnSchema::SetColumnSchema(const TColumnSchema& columnSchema)
+{
+ static_cast<TColumnSchema&>(*this) = columnSchema;
+ if (IsRenamed()) {
+ SerializedStableName_ = StableName().Get();
+ }
+ LogicalTypeV1_ = columnSchema.CastToV1Type();
+ RequiredV1_ = columnSchema.Required();
+ LogicalTypeV3_ = TTypeV3LogicalTypeWrapper{columnSchema.LogicalType()};
+}
+
+void TSerializableColumnSchema::SetDeletedColumnSchema(
+ const TDeletedColumn& deletedColumnSchema)
+{
+ Deleted_ = true;
+ StableName_ = deletedColumnSchema.StableName();
+}
+
+void TSerializableColumnSchema::RunPostprocessor()
+{
+ if (Deleted() && *Deleted()) {
+ if (!SerializedStableName_ || SerializedStableName_->empty()) {
+ THROW_ERROR_EXCEPTION("stable name should be set for a deleted column");
+ }
+ SetStableName(TStableName(*SerializedStableName_));
+ return;
+ }
+
+ // Name
+ if (Name().empty()) {
+ THROW_ERROR_EXCEPTION("Column name cannot be empty");
+ }
+
+ if (SerializedStableName_.has_value()) {
+ if (SerializedStableName_->empty()) {
+ THROW_ERROR_EXCEPTION("Column stable name cannot be empty");
+ }
+ SetStableName(TStableName(*SerializedStableName_));
+ } else {
+ SetStableName(TStableName(Name()));
+ }
+
+ try {
+ int setTypeVersion = 0;
+ if (LogicalTypeV3_) {
+ SetLogicalType(LogicalTypeV3_->LogicalType);
+ setTypeVersion = 3;
+ }
+
+ if (LogicalTypeV1_) {
+ if (setTypeVersion == 0) {
+ SetLogicalType(MakeLogicalType(*LogicalTypeV1_, RequiredV1_.value_or(false)));
+ setTypeVersion = 1;
+ } else {
+ if (*LogicalTypeV1_ != CastToV1Type()) {
+ THROW_ERROR_EXCEPTION(
+ "\"type_v%v\" does not match \"type\"; \"type_v%v\": %Qv \"type\": %Qlv expected \"type\": %Qlv",
+ setTypeVersion,
+ setTypeVersion,
+ *LogicalType(),
+ *LogicalTypeV1_,
+ CastToV1Type());
+ }
+ }
+ }
+
+ if (RequiredV1_ && setTypeVersion > 1 && *RequiredV1_ != Required()) {
+ THROW_ERROR_EXCEPTION(
+ "\"type_v%v\" does not match \"required\"; \"type_v%v\": %Qv \"required\": %Qlv",
+ setTypeVersion,
+ setTypeVersion,
+ *LogicalType(),
+ *RequiredV1_);
+ }
+
+ if (setTypeVersion == 0) {
+ THROW_ERROR_EXCEPTION("Column type is not specified");
+ }
+
+ if (*DetagLogicalType(LogicalType()) == *SimpleLogicalType(ESimpleLogicalValueType::Any)) {
+ THROW_ERROR_EXCEPTION("Column of type %Qlv cannot be \"required\"",
+ ESimpleLogicalValueType::Any);
+ }
+
+ // Lock
+ if (Lock() && Lock()->empty()) {
+ THROW_ERROR_EXCEPTION("Lock name cannot be empty");
+ }
+
+ // Group
+ if (Group() && Group()->empty()) {
+ THROW_ERROR_EXCEPTION("Group name cannot be empty");
+ }
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error validating column %Qv in table schema",
+ GetDiagnosticNameString())
+ << ex;
+ }
+}
+
+void Serialize(const TColumnSchema& schema, NYson::IYsonConsumer* consumer)
+{
+ TSerializableColumnSchema wrapper = TSerializableColumnSchema::Create();
+ wrapper.SetColumnSchema(schema);
+ Serialize(static_cast<const NYTree::TYsonStructLite&>(wrapper), consumer);
+}
+
+void Serialize(const TDeletedColumn& schema, NYson::IYsonConsumer* consumer)
+{
+ consumer->OnBeginMap();
+ consumer->OnKeyedItem("stable_name");
+ consumer->OnStringScalar(schema.StableName().Get());
+ consumer->OnKeyedItem("deleted");
+ consumer->OnBooleanScalar(true);
+ consumer->OnEndMap();
+}
+
+void Serialize(const TTableSchema& schema, NYson::IYsonConsumer* consumer)
+{
+ auto position = NYTree::BuildYsonFluently(consumer)
+ .BeginAttributes()
+ .Item("strict").Value(schema.GetStrict())
+ .Item("unique_keys").Value(schema.GetUniqueKeys())
+ .DoIf(schema.HasNontrivialSchemaModification(), [&] (NYTree::TFluentMap fluent) {
+ fluent.Item("schema_modification").Value(schema.GetSchemaModification());
+ })
+ .EndAttributes()
+ .BeginList();
+
+ for (const auto& column : schema.Columns()) {
+ Serialize(column, position.Item().GetConsumer());
+ }
+ for (const auto& deletedColumn : schema.DeletedColumns()) {
+ Serialize(deletedColumn, position.Item().GetConsumer());
+ }
+
+ position.EndList();
+}
+
+void Serialize(const TTableSchemaPtr& schema, NYson::IYsonConsumer* consumer)
+{
+ Serialize(*schema, consumer);
+}
+
+void Deserialize(TTableSchema& schema, NYTree::INodePtr node)
+{
+ auto childNodes = node->AsList()->GetChildren();
+
+ std::vector<TColumnSchema> columns;
+ std::vector<TDeletedColumn> deletedColumns;
+
+ for (auto childNode : childNodes) {
+ auto wrapper = TSerializableColumnSchema::Create();
+ Deserialize(static_cast<NYTree::TYsonStructLite&>(wrapper), childNode);
+ if (wrapper.Deleted() && *wrapper.Deleted()) {
+ deletedColumns.push_back(TDeletedColumn(wrapper.StableName()));
+ } else {
+ columns.push_back(wrapper);
+ }
+ }
+
+ schema = TTableSchema(
+ columns,
+ node->Attributes().Get<bool>("strict", true),
+ node->Attributes().Get<bool>("unique_keys", false),
+ node->Attributes().Get<ETableSchemaModification>(
+ "schema_modification",
+ ETableSchemaModification::None),
+ deletedColumns);
+}
+
+void Deserialize(TTableSchema& schema, NYson::TYsonPullParserCursor* cursor)
+{
+ bool strict = true;
+ bool uniqueKeys = false;
+ ETableSchemaModification modification = ETableSchemaModification::None;
+
+ if ((*cursor)->GetType() == NYson::EYsonItemType::BeginAttributes) {
+ cursor->ParseAttributes([&] (NYson::TYsonPullParserCursor* cursor) {
+ EnsureYsonToken(TStringBuf("table schema attribute key"), *cursor, NYson::EYsonItemType::StringValue);
+ auto key = (*cursor)->UncheckedAsString();
+ if (key == TStringBuf("strict")) {
+ cursor->Next();
+ strict = ExtractTo<bool>(cursor);
+ } else if (key == TStringBuf("unique_keys")) {
+ cursor->Next();
+ uniqueKeys = ExtractTo<bool>(cursor);
+ } else if (key == TStringBuf("schema_modification")) {
+ cursor->Next();
+ modification = ExtractTo<ETableSchemaModification>(cursor);
+ } else {
+ cursor->Next();
+ cursor->SkipComplexValue();
+ }
+ });
+ }
+
+ EnsureYsonToken(TStringBuf("table schema"), *cursor, NYson::EYsonItemType::BeginList);
+
+ auto maybeDeletedColumns = ExtractTo<std::vector<TMaybeDeletedColumnSchema>>(cursor);
+
+ std::vector<TColumnSchema> columns;
+ std::vector<TDeletedColumn> deletedColumns;
+
+ for (const auto& maybeDeletedColumn : maybeDeletedColumns) {
+ if (maybeDeletedColumn.Deleted() && *maybeDeletedColumn.Deleted()) {
+ deletedColumns.push_back(maybeDeletedColumn.GetDeletedColumnSchema());
+ } else {
+ columns.push_back(static_cast<TColumnSchema>(maybeDeletedColumn));
+ }
+ }
+
+ schema = TTableSchema(columns, strict, uniqueKeys, modification, deletedColumns);
+}
+
+void Deserialize(TTableSchemaPtr& schema, NYTree::INodePtr node)
+{
+ TTableSchema actualSchema;
+ Deserialize(actualSchema, node);
+ schema = New<TTableSchema>(std::move(actualSchema));
+}
+
+void Deserialize(TTableSchemaPtr& schema, NYson::TYsonPullParserCursor* cursor)
+{
+ TTableSchema actualSchema;
+ Deserialize(actualSchema, cursor);
+ schema = New<TTableSchema>(std::move(actualSchema));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/schema_serialization_helpers.h b/yt/yt/client/table_client/schema_serialization_helpers.h
new file mode 100644
index 0000000000..43b9ad6070
--- /dev/null
+++ b/yt/yt/client/table_client/schema_serialization_helpers.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+
+#include <yt/yt/client/table_client/schema.h>
+
+namespace NYT::NTableClient {
+
+struct TMaybeDeletedColumnSchema : public TColumnSchema
+{
+ DEFINE_BYREF_RO_PROPERTY(std::optional<bool>, Deleted);
+
+ TDeletedColumn GetDeletedColumnSchema() const;
+};
+
+void Deserialize(TMaybeDeletedColumnSchema& schema, NYson::TYsonPullParserCursor* cursor);
+void Deserialize(TMaybeDeletedColumnSchema& schema, NYTree::INodePtr node);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSerializableColumnSchema
+ : public TMaybeDeletedColumnSchema
+ , public NYTree::TYsonStructLite
+{
+ REGISTER_YSON_STRUCT_LITE(TSerializableColumnSchema);
+
+ static void Register(TRegistrar registrar);
+
+ void RunPostprocessor();
+
+public:
+ void DeserializeFromCursor(NYson::TYsonPullParserCursor* cursor);
+
+ void SetColumnSchema(const TColumnSchema& columnSchema);
+ void SetDeletedColumnSchema(const TDeletedColumn& deletedColumnSchema);
+
+private:
+ std::optional<TString> SerializedStableName_;
+
+ std::optional<ESimpleLogicalValueType> LogicalTypeV1_;
+ std::optional<bool> RequiredV1_;
+
+ std::optional<TTypeV3LogicalTypeWrapper> LogicalTypeV3_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/schemaless_row_reorderer.cpp b/yt/yt/client/table_client/schemaless_row_reorderer.cpp
new file mode 100644
index 0000000000..88a11d7cc8
--- /dev/null
+++ b/yt/yt/client/table_client/schemaless_row_reorderer.cpp
@@ -0,0 +1,86 @@
+#include "schemaless_row_reorderer.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSchemalessRowReorderer::TSchemalessRowReorderer(
+ TNameTablePtr nameTable,
+ TRowBufferPtr rowBuffer,
+ bool captureValues,
+ const TKeyColumns& keyColumns)
+ : KeyColumns_(keyColumns)
+ , RowBuffer_(std::move(rowBuffer))
+ , CaptureValues_(captureValues)
+ , NameTable_(nameTable)
+{
+ EmptyKey_.resize(KeyColumns_.size(), MakeUnversionedSentinelValue(EValueType::Null));
+ for (int i = 0; i < std::ssize(KeyColumns_); ++i) {
+ auto id = NameTable_->GetIdOrRegisterName(KeyColumns_[i]);
+ EmptyKey_[i].Id = id;
+ if (id >= std::ssize(IdMapping_)) {
+ IdMapping_.resize(id + 1, -1);
+ }
+ IdMapping_[id] = i;
+ }
+}
+
+TMutableUnversionedRow TSchemalessRowReorderer::ReorderRow(TUnversionedRow row)
+{
+ int valueCount = KeyColumns_.size() + row.GetCount();
+ auto result = RowBuffer_->AllocateUnversioned(valueCount);
+
+ // Initialize with empty key.
+ ::memcpy(result.Begin(), EmptyKey_.data(), KeyColumns_.size() * sizeof(TUnversionedValue));
+
+ int nextValueIndex = KeyColumns_.size();
+ int idMappingSize = static_cast<int>(IdMapping_.size());
+ for (auto value : row) {
+ if (CaptureValues_) {
+ RowBuffer_->CaptureValue(&value);
+ }
+ if (value.Id < idMappingSize) {
+ int keyIndex = IdMapping_[value.Id];
+ if (keyIndex >= 0) {
+ result.Begin()[keyIndex] = value;
+ --valueCount;
+ continue;
+ }
+ }
+ result.Begin()[nextValueIndex] = value;
+ ++nextValueIndex;
+ }
+
+ result.SetCount(valueCount);
+ return result;
+}
+
+TMutableUnversionedRow TSchemalessRowReorderer::ReorderKey(TUnversionedRow row)
+{
+ auto result = RowBuffer_->AllocateUnversioned(KeyColumns_.size());
+
+ // Initialize with empty key.
+ ::memcpy(result.Begin(), EmptyKey_.data(), KeyColumns_.size() * sizeof(TUnversionedValue));
+
+ int idMappingSize = static_cast<int>(IdMapping_.size());
+ for (auto value : row) {
+ if (CaptureValues_) {
+ RowBuffer_->CaptureValue(&value);
+ }
+ if (value.Id < idMappingSize) {
+ int keyIndex = IdMapping_[value.Id];
+ if (keyIndex >= 0) {
+ result.Begin()[keyIndex] = value;
+ }
+ }
+ }
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/schemaless_row_reorderer.h b/yt/yt/client/table_client/schemaless_row_reorderer.h
new file mode 100644
index 0000000000..9f001b464c
--- /dev/null
+++ b/yt/yt/client/table_client/schemaless_row_reorderer.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "unversioned_row.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Reorders values in original rows, putting key columns at the front.
+//! Omitted key columns are filled in with null values.
+//! All non-key columns are positioned after key ones, preserving order.
+class TSchemalessRowReorderer
+ : public TNonCopyable
+{
+public:
+ TSchemalessRowReorderer(
+ TNameTablePtr nameTable,
+ TRowBufferPtr rowBuffer,
+ bool captureValues,
+ const TKeyColumns& keyColumns);
+
+ TMutableUnversionedRow ReorderRow(TUnversionedRow row);
+
+ //! Preserves only key columns, non-key column are ignored.
+ TMutableUnversionedRow ReorderKey(TUnversionedRow row);
+
+private:
+ const TKeyColumns KeyColumns_;
+ const TRowBufferPtr RowBuffer_;
+ const bool CaptureValues_;
+ const TNameTablePtr NameTable_;
+
+ std::vector<int> IdMapping_;
+ std::vector<TUnversionedValue> EmptyKey_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/serialize.cpp b/yt/yt/client/table_client/serialize.cpp
new file mode 100644
index 0000000000..5f9ca0d24d
--- /dev/null
+++ b/yt/yt/client/table_client/serialize.cpp
@@ -0,0 +1,16 @@
+#include "serialize.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLoadContext::TLoadContext(
+ IZeroCopyInput* input,
+ TRowBufferPtr rowBuffer)
+ : NPhoenix::TLoadContext(input)
+ , RowBuffer_(std::move(rowBuffer))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/serialize.h b/yt/yt/client/table_client/serialize.h
new file mode 100644
index 0000000000..de4a59364d
--- /dev/null
+++ b/yt/yt/client/table_client/serialize.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/property.h>
+#include <yt/yt/core/misc/phoenix.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSaveContext
+ : public NPhoenix::TSaveContext
+{
+public:
+ using NPhoenix::TSaveContext::TSaveContext;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLoadContext
+ : public NPhoenix::TLoadContext
+{
+public:
+ DEFINE_BYVAL_RO_PROPERTY(TRowBufferPtr, RowBuffer);
+
+public:
+ TLoadContext(
+ IZeroCopyInput* input,
+ TRowBufferPtr rowBuffer);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/table_consumer.cpp b/yt/yt/client/table_client/table_consumer.cpp
new file mode 100644
index 0000000000..68a38b549f
--- /dev/null
+++ b/yt/yt/client/table_client/table_consumer.cpp
@@ -0,0 +1,601 @@
+#include "table_consumer.h"
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <util/string/cast.h>
+
+#include <cmath>
+#include <variant>
+
+namespace NYT::NTableClient {
+
+using namespace NFormats;
+using namespace NYson;
+using namespace NConcurrency;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TYsonToUnversionedValueConverter::TYsonToUnversionedValueConverter(
+ const TYsonConverterConfig& config,
+ IValueConsumer* valueConsumer)
+ : TYsonToUnversionedValueConverter(config, std::vector<IValueConsumer*>{valueConsumer})
+{ }
+
+TYsonToUnversionedValueConverter::TYsonToUnversionedValueConverter(
+ const TYsonConverterConfig& config,
+ std::vector<IValueConsumer*> valueConsumers,
+ int tableIndex)
+ : ValueConsumers_(std::move(valueConsumers))
+ , ValueWriter_(&ValueBuffer_)
+ , ConvertedWriter_(&ConvertedBuffer_)
+{
+ SwitchToTable(tableIndex);
+
+ for (size_t tableIndex = 0; tableIndex < ValueConsumers_.size(); ++tableIndex) {
+ const auto& valueConsumer = ValueConsumers_[tableIndex];
+ const auto& nameTable = valueConsumer->GetNameTable();
+
+ for (const auto& column : valueConsumer->GetSchema()->Columns()) {
+ const auto id = nameTable->GetIdOrRegisterName(column.Name());
+ const auto key = std::pair(tableIndex, id);
+ auto converter = CreateYsonClientToServerConverter(TComplexTypeFieldDescriptor(column), config);
+ if (IsV3Composite(column.LogicalType())) {
+ ComplexTypeConverters_.emplace(key, std::move(converter));
+ } else if (converter) {
+ SimpleValueConverters_.emplace(key, std::move(converter));
+ }
+ }
+ }
+}
+
+IValueConsumer* TYsonToUnversionedValueConverter::SwitchToTable(int tableIndex)
+{
+ TableIndex_ = tableIndex;
+ YT_VERIFY(0 <= tableIndex && tableIndex < std::ssize(ValueConsumers_));
+ CurrentValueConsumer_ = ValueConsumers_[tableIndex];
+ YT_VERIFY(CurrentValueConsumer_ != nullptr);
+ return CurrentValueConsumer_;
+}
+
+void TYsonToUnversionedValueConverter::SetColumnIndex(int columnIndex)
+{
+ ColumnIndex_ = columnIndex;
+}
+
+void TYsonToUnversionedValueConverter::OnStringScalar(TStringBuf value)
+{
+ if (Depth_ == 0) {
+ auto unversionedValue = MakeUnversionedStringValue(value, ColumnIndex_);
+ if (!TryConvertAndFeedValueConsumer(unversionedValue)) {
+ CurrentValueConsumer_->OnValue(unversionedValue);
+ }
+ } else {
+ ValueWriter_.OnStringScalar(value);
+ }
+}
+
+void TYsonToUnversionedValueConverter::OnInt64Scalar(i64 value)
+{
+ if (Depth_ == 0) {
+ auto unversionedValue = MakeUnversionedInt64Value(value, ColumnIndex_);
+ if (!TryConvertAndFeedValueConsumer(unversionedValue)) {
+ CurrentValueConsumer_->OnValue(unversionedValue);
+ }
+ } else {
+ ValueWriter_.OnInt64Scalar(value);
+ }
+}
+
+void TYsonToUnversionedValueConverter::OnUint64Scalar(ui64 value)
+{
+ if (Depth_ == 0) {
+ auto unversionedValue = MakeUnversionedUint64Value(value, ColumnIndex_);
+ if (!TryConvertAndFeedValueConsumer(unversionedValue)) {
+ CurrentValueConsumer_->OnValue(unversionedValue);
+ }
+ } else {
+ ValueWriter_.OnUint64Scalar(value);
+ }
+}
+
+void TYsonToUnversionedValueConverter::OnDoubleScalar(double value)
+{
+ if (Depth_ == 0) {
+ CurrentValueConsumer_->OnValue(MakeUnversionedDoubleValue(value, ColumnIndex_));
+ } else {
+ ValueWriter_.OnDoubleScalar(value);
+ }
+}
+
+void TYsonToUnversionedValueConverter::OnBooleanScalar(bool value)
+{
+ if (Depth_ == 0) {
+ CurrentValueConsumer_->OnValue(MakeUnversionedBooleanValue(value, ColumnIndex_));
+ } else {
+ ValueWriter_.OnBooleanScalar(value);
+ }
+}
+
+void TYsonToUnversionedValueConverter::OnEntity()
+{
+ if (Depth_ == 0) {
+ CurrentValueConsumer_->OnValue(MakeUnversionedSentinelValue(EValueType::Null, ColumnIndex_));
+ } else {
+ ValueWriter_.OnEntity();
+ }
+}
+
+void TYsonToUnversionedValueConverter::OnBeginList()
+{
+ ValueWriter_.OnBeginList();
+ ++Depth_;
+}
+
+void TYsonToUnversionedValueConverter::OnBeginAttributes()
+{
+ if (Depth_ == 0) {
+ THROW_ERROR_EXCEPTION("Table values cannot have top-level attributes");
+ }
+
+ ValueWriter_.OnBeginAttributes();
+ ++Depth_;
+}
+
+void TYsonToUnversionedValueConverter::OnListItem()
+{
+ if (Depth_ > 0) {
+ ValueWriter_.OnListItem();
+ }
+}
+
+void TYsonToUnversionedValueConverter::OnBeginMap()
+{
+ ValueWriter_.OnBeginMap();
+ ++Depth_;
+}
+
+void TYsonToUnversionedValueConverter::OnKeyedItem(TStringBuf name)
+{
+ ValueWriter_.OnKeyedItem(name);
+}
+
+void TYsonToUnversionedValueConverter::OnEndMap()
+{
+ YT_VERIFY(Depth_ > 0);
+
+ --Depth_;
+ ValueWriter_.OnEndMap();
+ FlushCurrentValueIfCompleted();
+}
+
+void TYsonToUnversionedValueConverter::OnEndList()
+{
+ YT_VERIFY(Depth_ > 0);
+
+ --Depth_;
+ ValueWriter_.OnEndList();
+ FlushCurrentValueIfCompleted();
+}
+
+void TYsonToUnversionedValueConverter::OnEndAttributes()
+{
+ --Depth_;
+
+ YT_VERIFY(Depth_ > 0);
+ ValueWriter_.OnEndAttributes();
+}
+
+void TYsonToUnversionedValueConverter::FlushCurrentValueIfCompleted()
+{
+ if (Depth_ == 0) {
+ ValueWriter_.Flush();
+ auto accumulatedYson = TStringBuf(ValueBuffer_.Begin(), ValueBuffer_.Begin() + ValueBuffer_.Size());
+ auto it = ComplexTypeConverters_.find(std::pair(TableIndex_, ColumnIndex_));
+ TUnversionedValue value;
+ if (it == ComplexTypeConverters_.end()) {
+ value = MakeUnversionedAnyValue(accumulatedYson, ColumnIndex_);
+ } else {
+ const auto& converter = it->second;
+ if (converter) {
+ value = converter(MakeUnversionedStringValue(ValueBuffer_.Blob().ToStringBuf()));
+ value.Id = ColumnIndex_;
+ } else {
+ value = MakeUnversionedCompositeValue(accumulatedYson, ColumnIndex_);
+ }
+ }
+ CurrentValueConsumer_->OnValue(value);
+ ValueBuffer_.Clear();
+ }
+}
+
+bool TYsonToUnversionedValueConverter::TryConvertAndFeedValueConsumer(TUnversionedValue value)
+{
+ auto it = SimpleValueConverters_.find(std::pair(TableIndex_, ColumnIndex_));
+ if (it != SimpleValueConverters_.end()) {
+ auto converted = it->second(value);
+ converted.Id = ColumnIndex_;
+ CurrentValueConsumer_->OnValue(converted);
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTableConsumer::TTableConsumer(
+ const TYsonConverterConfig& config,
+ std::vector<IValueConsumer*> valueConsumers,
+ int tableIndex)
+ : YsonToUnversionedValueConverter_(config, std::move(valueConsumers))
+{
+ for (auto* consumer : YsonToUnversionedValueConverter_.ValueConsumers()) {
+ NameTableWriters_.emplace_back(std::make_unique<TNameTableWriter>(consumer->GetNameTable()));
+ }
+ SwitchToTable(tableIndex);
+}
+
+TTableConsumer::TTableConsumer(const TYsonConverterConfig& config, IValueConsumer* valueConsumer)
+ : TTableConsumer(config, std::vector<IValueConsumer*>(1, valueConsumer))
+{ }
+
+TError TTableConsumer::AttachLocationAttributes(TError error) const
+{
+ return error << TErrorAttribute("row_index", RowIndex_);
+}
+
+void TTableConsumer::OnControlInt64Scalar(i64 value)
+{
+ switch (ControlAttribute_) {
+ case EControlAttribute::TableIndex:
+ if (value < 0 || value >= GetTableCount()) {
+ THROW_ERROR AttachLocationAttributes(TError(
+ "Invalid table index %v: expected integer in range [0,%v]",
+ value,
+ GetTableCount() - 1));
+ }
+ SwitchToTable(value);
+ break;
+
+ default:
+ ThrowControlAttributesNotSupported();
+ }
+}
+
+void TTableConsumer::OnControlStringScalar(TStringBuf /*value*/)
+{
+ ThrowControlAttributesNotSupported();
+}
+
+void TTableConsumer::OnStringScalar(TStringBuf value)
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ YT_ASSERT(Depth_ == 1);
+ OnControlStringScalar(value);
+ ControlState_ = EControlState::ExpectEndAttributes;
+ return;
+ } else if (ControlState_ == EControlState::ExpectEntity) {
+ ThrowEntityExpected();
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ ThrowMapExpected();
+ } else {
+ YsonToUnversionedValueConverter_.OnStringScalar(value);
+ }
+}
+
+void TTableConsumer::OnInt64Scalar(i64 value)
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ YT_ASSERT(Depth_ == 1);
+ OnControlInt64Scalar(value);
+ ControlState_ = EControlState::ExpectEndAttributes;
+ return;
+ } else if (ControlState_ == EControlState::ExpectEntity) {
+ ThrowEntityExpected();
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ ThrowMapExpected();
+ } else {
+ YsonToUnversionedValueConverter_.OnInt64Scalar(value);
+ }
+}
+
+void TTableConsumer::OnUint64Scalar(ui64 value)
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ ThrowInvalidControlAttribute("be an unsigned integer");
+ } else if (ControlState_ == EControlState::ExpectEntity) {
+ ThrowEntityExpected();
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ ThrowMapExpected();
+ } else {
+ YsonToUnversionedValueConverter_.OnUint64Scalar(value);
+ }
+}
+
+void TTableConsumer::OnDoubleScalar(double value)
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ YT_ASSERT(Depth_ == 1);
+ ThrowInvalidControlAttribute("be a double value");
+ return;
+ } else if (ControlState_ == EControlState::ExpectEntity) {
+ ThrowEntityExpected();
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ ThrowMapExpected();
+ } else {
+ YsonToUnversionedValueConverter_.OnDoubleScalar(value);
+ }
+}
+
+void TTableConsumer::OnBooleanScalar(bool value)
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ YT_ASSERT(Depth_ == 1);
+ ThrowInvalidControlAttribute("be a boolean value");
+ return;
+ } else if (ControlState_ == EControlState::ExpectEntity) {
+ ThrowEntityExpected();
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ ThrowMapExpected();
+ } else {
+ YsonToUnversionedValueConverter_.OnBooleanScalar(value);
+ }
+}
+
+void TTableConsumer::OnEntity()
+{
+ switch (ControlState_) {
+ case EControlState::None:
+ break;
+
+ case EControlState::ExpectEntity:
+ YT_ASSERT(Depth_ == 0);
+ // Successfully processed control statement.
+ ControlState_ = EControlState::None;
+ return;
+
+ case EControlState::ExpectValue:
+ ThrowInvalidControlAttribute("be an entity");
+ break;
+
+ default:
+ YT_ABORT();
+ }
+
+ if (Depth_ == 0) {
+ ThrowMapExpected();
+ } else {
+ YsonToUnversionedValueConverter_.OnEntity();
+ }
+}
+
+void TTableConsumer::OnBeginList()
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ YT_ASSERT(Depth_ == 1);
+ ThrowInvalidControlAttribute("be a list");
+ return;
+ } else if (ControlState_ == EControlState::ExpectEntity) {
+ ThrowEntityExpected();
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ ThrowMapExpected();
+ } else {
+ YsonToUnversionedValueConverter_.OnBeginList();
+ }
+ ++Depth_;
+}
+
+void TTableConsumer::OnBeginAttributes()
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ YT_ASSERT(Depth_ == 1);
+ ThrowInvalidControlAttribute("have attributes");
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ ControlState_ = EControlState::ExpectName;
+ } else {
+ YsonToUnversionedValueConverter_.OnBeginAttributes();
+ }
+
+ ++Depth_;
+}
+
+void TTableConsumer::ThrowControlAttributesNotSupported() const
+{
+ THROW_ERROR AttachLocationAttributes(TError("Control attributes are not supported"));
+}
+
+void TTableConsumer::ThrowMapExpected() const
+{
+ THROW_ERROR AttachLocationAttributes(TError("Invalid row format, map expected"));
+}
+
+void TTableConsumer::ThrowEntityExpected() const
+{
+ THROW_ERROR AttachLocationAttributes(TError("Invalid control attributes syntax, entity expected"));
+}
+
+void TTableConsumer::ThrowInvalidControlAttribute(const TString& whatsWrong) const
+{
+ THROW_ERROR AttachLocationAttributes(TError("Control attribute %Qlv cannot %v",
+ ControlAttribute_,
+ whatsWrong));
+}
+
+void TTableConsumer::OnListItem()
+{
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ // Row separator, do nothing.
+ } else {
+ YsonToUnversionedValueConverter_.OnListItem();
+ }
+}
+
+void TTableConsumer::OnBeginMap()
+{
+ if (ControlState_ == EControlState::ExpectValue) {
+ YT_ASSERT(Depth_ == 1);
+ ThrowInvalidControlAttribute("be a map");
+ } else if (ControlState_ == EControlState::ExpectEntity) {
+ ThrowEntityExpected();
+ }
+
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ if (Depth_ == 0) {
+ CurrentValueConsumer_->OnBeginRow();
+ } else {
+ YsonToUnversionedValueConverter_.OnBeginMap();
+ }
+ ++Depth_;
+}
+
+void TTableConsumer::OnKeyedItem(TStringBuf name)
+{
+ switch (ControlState_) {
+ case EControlState::None:
+ break;
+
+ case EControlState::ExpectName:
+ YT_ASSERT(Depth_ == 1);
+ try {
+ ControlAttribute_ = ParseEnum<EControlAttribute>(ToString(name));
+ } catch (const std::exception&) {
+ // Ignore ex, our custom message is more meaningful.
+ THROW_ERROR AttachLocationAttributes(TError("Failed to parse control attribute name %Qv", name));
+ }
+ ControlState_ = EControlState::ExpectValue;
+ return;
+
+ case EControlState::ExpectEndAttributes:
+ YT_ASSERT(Depth_ == 1);
+ THROW_ERROR AttachLocationAttributes(TError("Too many control attributes per record: at most one attribute is allowed"));
+
+ default:
+ YT_ABORT();
+ }
+
+ YT_ASSERT(Depth_ > 0);
+ if (Depth_ == 1) {
+ int columnIndex = -1;
+ if (CurrentValueConsumer_->GetAllowUnknownColumns()) {
+ try {
+ columnIndex = CurrentNameTableWriter_->GetIdOrRegisterName(name);
+ } catch (const std::exception& ex) {
+ THROW_ERROR AttachLocationAttributes(TError("Failed to add column to name table for table writer")
+ << ex);
+ }
+ } else {
+ auto id = CurrentNameTableWriter_->FindId(name);
+ if (!id) {
+ THROW_ERROR AttachLocationAttributes(
+ TError(NTableClient::EErrorCode::SchemaViolation, "No column %Qv in table schema",
+ name));
+ }
+ columnIndex = *id;
+ }
+ YT_VERIFY(columnIndex != -1);
+ YsonToUnversionedValueConverter_.SetColumnIndex(columnIndex);
+ } else {
+ YsonToUnversionedValueConverter_.OnKeyedItem(name);
+ }
+}
+
+void TTableConsumer::OnEndMap()
+{
+ YT_ASSERT(Depth_ > 0);
+ // No control attribute allows map or composite values.
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ --Depth_;
+ if (Depth_ == 0) {
+ CurrentValueConsumer_->OnEndRow();
+ ++RowIndex_;
+ } else {
+ YsonToUnversionedValueConverter_.OnEndMap();
+ }
+}
+
+void TTableConsumer::OnEndList()
+{
+ // No control attribute allow list or composite values.
+ YT_ASSERT(ControlState_ == EControlState::None);
+
+ --Depth_;
+ YT_ASSERT(Depth_ > 0);
+
+ YsonToUnversionedValueConverter_.OnEndList();
+}
+
+void TTableConsumer::OnEndAttributes()
+{
+ --Depth_;
+
+ switch (ControlState_) {
+ case EControlState::ExpectName:
+ THROW_ERROR AttachLocationAttributes(TError("Too few control attributes per record: at least one attribute is required"));
+ break;
+
+ case EControlState::ExpectEndAttributes:
+ YT_ASSERT(Depth_ == 0);
+ ControlState_ = EControlState::ExpectEntity;
+ break;
+
+ case EControlState::None:
+ YT_ASSERT(Depth_ > 0);
+ YsonToUnversionedValueConverter_.OnEndAttributes();
+ break;
+
+ default:
+ YT_ABORT();
+ }
+}
+
+void TTableConsumer::SwitchToTable(int tableIndex)
+{
+ YT_VERIFY(tableIndex >= 0 && tableIndex < GetTableCount());
+ CurrentValueConsumer_ = YsonToUnversionedValueConverter_.SwitchToTable(tableIndex);
+ CurrentNameTableWriter_ = NameTableWriters_[tableIndex].get();
+}
+
+int TTableConsumer::GetTableCount() const
+{
+ return NameTableWriters_.size();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/table_consumer.h b/yt/yt/client/table_client/table_consumer.h
new file mode 100644
index 0000000000..9355d695ba
--- /dev/null
+++ b/yt/yt/client/table_client/table_consumer.h
@@ -0,0 +1,159 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/complex_types/yson_format_conversion.h>
+#include <yt/yt/client/formats/public.h>
+
+#include <yt/yt/client/table_client/value_consumer.h>
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/consumer.h>
+#include <yt/yt/core/yson/writer.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYsonToUnversionedValueConverter
+ : public NYson::TYsonConsumerBase
+{
+public:
+ DEFINE_BYREF_RO_PROPERTY(std::vector<IValueConsumer*>, ValueConsumers);
+
+public:
+ TYsonToUnversionedValueConverter(
+ const NComplexTypes::TYsonConverterConfig& config,
+ IValueConsumer* valueConsumers);
+
+ TYsonToUnversionedValueConverter(
+ const NComplexTypes::TYsonConverterConfig& config,
+ std::vector<IValueConsumer*> valueConsumers,
+ int tableIndex = 0);
+
+ IValueConsumer* SwitchToTable(int tableIndex);
+
+ // Set column index of next emitted value.
+ void SetColumnIndex(int columnIndex);
+
+ int GetDepth() const;
+
+ void OnStringScalar(TStringBuf value) override;
+ void OnInt64Scalar(i64 value) override;
+ void OnUint64Scalar(ui64 value) override;
+ void OnDoubleScalar(double value) override;
+ void OnBooleanScalar(bool value) override;
+ void OnEntity() override;
+ void OnBeginList() override;
+ void OnListItem() override;
+ void OnBeginMap() override;
+ void OnKeyedItem(TStringBuf name) override;
+ void OnEndMap() override;
+ void OnBeginAttributes() override;
+ void OnEndList() override;
+ void OnEndAttributes() override;
+
+private:
+ bool TryConvertAndFeedValueConsumer(TUnversionedValue value);
+
+ TBlobOutput ValueBuffer_;
+ NYson::TBufferedBinaryYsonWriter ValueWriter_;
+
+ // Key of ComplexTypeConverters_ map is <TableIndex;ColumnId>.
+ // All complex columns are present in this map.
+ //
+ // If no conversion is needed then values are empty functions.
+ // Otherwise values are converter functions.
+ THashMap<std::pair<int,int>, NComplexTypes::TYsonClientToServerConverter> ComplexTypeConverters_;
+ THashMap<std::pair<int,int>, NComplexTypes::TYsonClientToServerConverter> SimpleValueConverters_;
+
+ TBlobOutput ConvertedBuffer_;
+ NYson::TBufferedBinaryYsonWriter ConvertedWriter_;
+
+ IValueConsumer* CurrentValueConsumer_;
+ int Depth_ = 0;
+ int ColumnIndex_ = 0;
+ int TableIndex_ = 0;
+
+private:
+ void FlushCurrentValueIfCompleted();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ETableConsumerControlState,
+ (None)
+ (ExpectName)
+ (ExpectValue)
+ (ExpectEndAttributes)
+ (ExpectEntity)
+);
+
+class TTableConsumer
+ : public NYson::TYsonConsumerBase
+{
+public:
+ TTableConsumer(
+ const NComplexTypes::TYsonConverterConfig& config,
+ IValueConsumer* consumer);
+ TTableConsumer(
+ const NComplexTypes::TYsonConverterConfig& config,
+ std::vector<IValueConsumer*> consumers,
+ int tableIndex = 0);
+
+protected:
+ using EControlState = ETableConsumerControlState;
+
+ TError AttachLocationAttributes(TError error) const;
+
+ void OnStringScalar(TStringBuf value) override;
+ void OnInt64Scalar(i64 value) override;
+ void OnUint64Scalar(ui64 value) override;
+ void OnDoubleScalar(double value) override;
+ void OnBooleanScalar(bool value) override;
+ void OnEntity() override;
+ void OnBeginList() override;
+ void OnListItem() override;
+ void OnBeginMap() override;
+ void OnKeyedItem(TStringBuf name) override;
+ void OnEndMap() override;
+
+ void OnBeginAttributes() override;
+
+ void ThrowMapExpected() const;
+ void ThrowEntityExpected() const;
+ void ThrowControlAttributesNotSupported() const;
+ void ThrowInvalidControlAttribute(const TString& whatsWrong) const;
+
+ void OnEndList() override;
+ void OnEndAttributes() override;
+
+ void OnControlInt64Scalar(i64 value);
+ void OnControlStringScalar(TStringBuf value);
+
+ void SwitchToTable(int tableIndex);
+
+private:
+ int GetTableCount() const;
+
+protected:
+ std::vector<std::unique_ptr<TNameTableWriter>> NameTableWriters_;
+
+ IValueConsumer* CurrentValueConsumer_ = nullptr;
+ TNameTableWriter* CurrentNameTableWriter_ = nullptr;
+
+ EControlState ControlState_ = EControlState::None;
+ EControlAttribute ControlAttribute_;
+
+ TYsonToUnversionedValueConverter YsonToUnversionedValueConverter_;
+
+ int Depth_ = 0;
+
+ i64 RowIndex_ = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/table_output.cpp b/yt/yt/client/table_client/table_output.cpp
new file mode 100644
index 0000000000..f96252f2ca
--- /dev/null
+++ b/yt/yt/client/table_client/table_output.cpp
@@ -0,0 +1,36 @@
+#include "table_output.h"
+
+namespace NYT::NTableClient {
+
+using namespace NFormats;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTableOutput::TTableOutput(std::unique_ptr<IParser> parser)
+ : Parser_(std::move(parser))
+{ }
+
+TTableOutput::~TTableOutput() = default;
+
+void TTableOutput::DoWrite(const void* buf, size_t len)
+{
+ YT_VERIFY(ParserValid_);
+ try {
+ Parser_->Read(TStringBuf(static_cast<const char*>(buf), len));
+ } catch (const std::exception& ex) {
+ ParserValid_ = false;
+ throw;
+ }
+}
+
+void TTableOutput::DoFinish()
+{
+ if (ParserValid_) {
+ // Dump everything into consumer.
+ Parser_->Finish();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/table_output.h b/yt/yt/client/table_client/table_output.h
new file mode 100644
index 0000000000..d52c35bd1e
--- /dev/null
+++ b/yt/yt/client/table_client/table_output.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/formats/parser.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableOutput
+ : public IOutputStream
+{
+public:
+ explicit TTableOutput(std::unique_ptr<NFormats::IParser> parser);
+ ~TTableOutput() override;
+
+private:
+ void DoWrite(const void* buf, size_t len) override;
+ void DoFinish() override;
+
+ const std::unique_ptr<NFormats::IParser> Parser_;
+
+ bool ParserValid_ = true;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unittests/columnar_statistics_ut.cpp b/yt/yt/client/table_client/unittests/columnar_statistics_ut.cpp
new file mode 100644
index 0000000000..2508a5f225
--- /dev/null
+++ b/yt/yt/client/table_client/unittests/columnar_statistics_ut.cpp
@@ -0,0 +1,634 @@
+#include <yt/yt/client/table_client/columnar_statistics.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_value.h>
+
+#include <yt/yt/client/table_client/unittests/helpers/helpers.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::vector<TUnversionedRow> CaptureRows(TRowBufferPtr rowBuffer, const std::vector<std::vector<TUnversionedValue>>& rows)
+{
+ std::vector<TUnversionedRow> result(rows.size());
+ for (size_t i = 0; i < rows.size(); ++i) {
+ result[i] = rowBuffer->CaptureRow(rows[i]);
+ }
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TUpdateColumnarStatisticsTest, EmptyStruct)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {
+ MakeUnversionedInt64Value(12, 0),
+ MakeUnversionedStringValue("buzz", 1),
+ MakeUnversionedDoubleValue(1e70, 2),
+ },
+ {
+ MakeUnversionedInt64Value(-7338, 0),
+ MakeUnversionedStringValue("foo", 1),
+ MakeUnversionedDoubleValue(-0.16, 2),
+ },
+ {
+ MakeUnversionedNullValue(0),
+ MakeUnversionedStringValue("chyt", 1),
+ MakeUnversionedDoubleValue(-std::numeric_limits<double>::infinity(), 2),
+ },
+ });
+
+ auto statistics = TColumnarStatistics::MakeEmpty(3);
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {16, 11, 24},
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(-7338),
+ MakeUnversionedStringValue("buzz"),
+ MakeUnversionedDoubleValue(-std::numeric_limits<double>::infinity()),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(12),
+ MakeUnversionedStringValue("foo"),
+ MakeUnversionedDoubleValue(1e70),
+ },
+ .ColumnNonNullValueCounts = {2, 3, 3},
+ .ChunkRowCount = 3,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, InitializedStruct)
+{
+ TColumnarStatistics statistics{
+ .ColumnDataWeights = {40, 80, 400, 128},
+ .ColumnMinValues = {
+ MakeUnversionedUint64Value(5),
+ MakeUnversionedStringValue("gaga"),
+ MakeUnversionedInt64Value(-77777),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedUint64Value(500),
+ MakeUnversionedStringValue("gugu"),
+ MakeUnversionedInt64Value(10),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ },
+ .ColumnNonNullValueCounts = {5, 10, 50, 3},
+ .ChunkRowCount = 50,
+ .LegacyChunkRowCount = 3,
+ };
+
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {
+ MakeUnversionedUint64Value(2, 0),
+ MakeUnversionedStringValue("blabla", 1),
+ MakeUnversionedAnyValue("[1,2,3]", 2),
+ MakeUnversionedInt64Value(2, 3),
+ },
+ {
+ MakeUnversionedUint64Value(22, 0),
+ MakeUnversionedStringValue("radio", 1),
+ MakeUnversionedInt64Value(29292, 2),
+ MakeUnversionedInt64Value(-10000, 3),
+ },
+ });
+
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {56, 91, 415, 144},
+ .ColumnMinValues = {
+ MakeUnversionedUint64Value(2),
+ MakeUnversionedStringValue("blabla"),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedUint64Value(500),
+ MakeUnversionedStringValue("radio"),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ },
+ .ColumnNonNullValueCounts = {7, 12, 52, 5},
+ .ChunkRowCount = 52,
+ .LegacyChunkRowCount = 3,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, DefaultStruct)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {
+ MakeUnversionedInt64Value(12, 0),
+ MakeUnversionedStringValue("buzz", 1),
+ MakeUnversionedDoubleValue(1e70, 2),
+ },
+ });
+
+ TColumnarStatistics statistics;
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {8, 4, 8},
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(12),
+ MakeUnversionedStringValue("buzz"),
+ MakeUnversionedDoubleValue(1e70),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(12),
+ MakeUnversionedStringValue("buzz"),
+ MakeUnversionedDoubleValue(1e70),
+ },
+ .ColumnNonNullValueCounts = {1, 1, 1},
+ .ChunkRowCount = 1,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, StructSizeLessThanRowSize)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {
+ MakeUnversionedInt64Value(12, 0),
+ MakeUnversionedStringValue("buzz", 1),
+ MakeUnversionedDoubleValue(1e70, 2),
+ },
+ });
+
+ auto statistics = TColumnarStatistics::MakeEmpty(1);
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {8, 4, 8},
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(12),
+ MakeUnversionedStringValue("buzz"),
+ MakeUnversionedDoubleValue(1e70),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(12),
+ MakeUnversionedStringValue("buzz"),
+ MakeUnversionedDoubleValue(1e70),
+ },
+ .ColumnNonNullValueCounts = {1, 1, 1},
+ .ChunkRowCount = 1,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, NoValueStatistics)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {
+ MakeUnversionedInt64Value(12, 0),
+ MakeUnversionedStringValue("buzz", 1),
+ MakeUnversionedDoubleValue(1e70, 2),
+ },
+ });
+
+ auto statistics = TColumnarStatistics::MakeEmpty(3, /*hasValueStatistics*/ false);
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {8, 4, 8},
+ .ColumnMinValues = {},
+ .ColumnMaxValues = {},
+ .ColumnNonNullValueCounts = {},
+ .ChunkRowCount = 1,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, LegacyStruct)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {
+ MakeUnversionedInt64Value(12, 0),
+ MakeUnversionedStringValue("buzz", 1),
+ MakeUnversionedDoubleValue(1e70, 2),
+ },
+ });
+
+ auto statistics = TColumnarStatistics::MakeLegacy(3, 82, 2);
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {8, 4, 8},
+ .LegacyChunkDataWeight = 82,
+ .ColumnMinValues = {},
+ .ColumnMaxValues = {},
+ .ColumnNonNullValueCounts = {},
+ .ChunkRowCount = 1,
+ .LegacyChunkRowCount = 2,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, CheckStringApproximation)
+{
+ constexpr int MaxStringValueLength = 100;
+
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {MakeUnversionedStringValue(std::string(MaxStringValueLength + 1, 'c'))},
+ {MakeUnversionedStringValue("cb")},
+ {MakeUnversionedStringValue(std::string(70, 'a') + std::string(MaxStringValueLength - 70 + 1, 'x'))},
+ });
+
+ auto statistics = TColumnarStatistics::MakeEmpty(1);
+ statistics.Update(rows);
+
+ std::vector<TUnversionedOwningValue> expectedColumnMinValues =
+ {MakeUnversionedStringValue(std::string(70, 'a') + std::string(MaxStringValueLength - 70, 'x'))};
+ std::vector<TUnversionedOwningValue> expectedColumnMaxValues =
+ {MakeUnversionedStringValue(std::string(MaxStringValueLength - 1, 'c') + "d")};
+ EXPECT_EQ(statistics.ColumnMinValues, expectedColumnMinValues);
+ EXPECT_EQ(statistics.ColumnMaxValues, expectedColumnMaxValues);
+}
+
+TEST(TUpdateColumnarStatisticsTest, NullColumn)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {MakeUnversionedNullValue()},
+ {MakeUnversionedNullValue()},
+ {MakeUnversionedNullValue()},
+ });
+
+ auto statistics = TColumnarStatistics::MakeEmpty(1);
+ statistics.Update(rows);
+
+ auto expected = TColumnarStatistics::MakeEmpty(1);
+ expected.ChunkRowCount = 3;
+ expected.LegacyChunkRowCount = 0;
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, DifferentTypesInOneColumn)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ auto rows = CaptureRows(rowBuffer, {
+ {MakeUnversionedInt64Value(1000)},
+ {MakeUnversionedStringValue("chyt")},
+ {MakeUnversionedBooleanValue(true)},
+ });
+
+ auto statistics = TColumnarStatistics::MakeEmpty(1);
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {13},
+ .ColumnMinValues = {MakeUnversionedInt64Value(1000)},
+ .ColumnMaxValues = {MakeUnversionedStringValue("chyt")},
+ .ColumnNonNullValueCounts = {3},
+ .ChunkRowCount = 3,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+TEST(TUpdateColumnarStatisticsTest, VersionedRow)
+{
+ std::vector<TVersionedRow> rows;
+ TRowBufferPtr buffer = New<TRowBuffer>();
+ TVersionedRowBuilder builder(buffer);
+
+ builder.AddKey(MakeUnversionedInt64Value(12));
+ builder.AddValue(MakeVersionedStringValue("b", 10, 1));
+ builder.AddValue(MakeVersionedInt64Value(1, 11, 2));
+ rows.emplace_back(builder.FinishRow());
+
+ builder.AddKey(MakeUnversionedInt64Value(33));
+ builder.AddValue(MakeVersionedStringValue("c", 15, 1));
+ builder.AddValue(MakeVersionedStringValue("a", 20, 1));
+ builder.AddValue(MakeVersionedInt64Value(143, 11, 2));
+ rows.emplace_back(builder.FinishRow());
+
+ auto statistics = TColumnarStatistics::MakeEmpty(3);
+ statistics.Update(rows);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {16, 3 + 3 * sizeof(TTimestamp), 16 + 2 * sizeof(TTimestamp)},
+ .TimestampTotalWeight = 40,
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(12),
+ MakeUnversionedStringValue("a"),
+ MakeUnversionedInt64Value(1)
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(33),
+ MakeUnversionedStringValue("c"),
+ MakeUnversionedInt64Value(143)
+ },
+ .ColumnNonNullValueCounts = {2, 3, 2},
+ .ChunkRowCount = 2,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(statistics, expected);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TMergeColumnarStatisticsTest, EmptyAndNonEmpty)
+{
+ auto lhs = TColumnarStatistics::MakeEmpty(4);
+ TColumnarStatistics rhs{
+ .ColumnDataWeights = {40, 80, 400, 128},
+ .ColumnMinValues = {
+ MakeUnversionedUint64Value(5),
+ MakeUnversionedStringValue("gaga"),
+ MakeUnversionedInt64Value(-77777),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedUint64Value(500),
+ MakeUnversionedStringValue("gugu"),
+ MakeUnversionedInt64Value(10),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ },
+ .ColumnNonNullValueCounts = {5, 10, 50, 3},
+ .ChunkRowCount = 50,
+ .LegacyChunkRowCount = 5,
+ };
+ lhs += rhs;
+ EXPECT_EQ(lhs, rhs);
+}
+
+TEST(TMergeColumnarStatisticsTest, NonEmptyAndEmpty)
+{
+ TColumnarStatistics lhs{
+ .ColumnDataWeights = {40, 80, 400, 128},
+ .ColumnMinValues = {
+ MakeUnversionedUint64Value(5),
+ MakeUnversionedStringValue("gaga"),
+ MakeUnversionedInt64Value(-77777),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedUint64Value(500),
+ MakeUnversionedStringValue("gugu"),
+ MakeUnversionedInt64Value(10),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ },
+ .ColumnNonNullValueCounts = {5, 10, 50, 3},
+ .ChunkRowCount = 50,
+ .LegacyChunkRowCount = 5,
+ };
+ auto rhs = TColumnarStatistics::MakeEmpty(4);
+ auto oldLhs = lhs;
+ lhs += rhs;
+ EXPECT_EQ(lhs, oldLhs);
+}
+
+TEST(TMergeColumnarStatisticsTest, DifferentTypes)
+{
+ TColumnarStatistics lhs{
+ .ColumnDataWeights = {64, 33, 10, 64, 100, 72, 0},
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(-10),
+ MakeUnversionedStringValue("fax"),
+ MakeUnversionedBooleanValue(true),
+ MakeUnversionedDoubleValue(0.12),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ MakeUnversionedUint64Value(42),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(20),
+ MakeUnversionedStringValue("syrok"),
+ MakeUnversionedBooleanValue(true),
+ MakeUnversionedDoubleValue(34.43),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ MakeUnversionedUint64Value(50),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnNonNullValueCounts = {8, 5, 10, 8, 3, 9, 0},
+ .ChunkRowCount = 10,
+ .LegacyChunkRowCount = 2,
+ };
+
+ TColumnarStatistics rhs{
+ .ColumnDataWeights = {96, 33, 10, 80, 237, 64, 0},
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(-20),
+ MakeUnversionedStringValue("b.y"),
+ MakeUnversionedBooleanValue(false),
+ MakeUnversionedDoubleValue(-100),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ MakeUnversionedUint64Value(46),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(10),
+ MakeUnversionedStringValue("pop"),
+ MakeUnversionedBooleanValue(false),
+ MakeUnversionedDoubleValue(std::numeric_limits<double>::infinity()),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ MakeUnversionedUint64Value(47),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnNonNullValueCounts = {12, 7, 10, 10, 2, 8, 0},
+ .ChunkRowCount = 12,
+ .LegacyChunkRowCount = 3,
+ };
+
+ lhs += rhs;
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {160, 66, 20, 144, 337, 136, 0},
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(-20),
+ MakeUnversionedStringValue("b.y"),
+ MakeUnversionedBooleanValue(false),
+ MakeUnversionedDoubleValue(-100),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ MakeUnversionedUint64Value(42),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(20),
+ MakeUnversionedStringValue("syrok"),
+ MakeUnversionedBooleanValue(true),
+ MakeUnversionedDoubleValue(std::numeric_limits<double>::infinity()),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ MakeUnversionedUint64Value(50),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnNonNullValueCounts = {20, 12, 20, 18, 5, 17, 0},
+ .ChunkRowCount = 22,
+ .LegacyChunkRowCount = 5,
+ };
+ EXPECT_EQ(lhs, expected);
+}
+
+TEST(TMergeColumnarStatisticsTest, DifferentTypesInOneColumn)
+{
+ TColumnarStatistics lhs{
+ .ColumnDataWeights = {100},
+ .ColumnMinValues = {MakeUnversionedInt64Value(10)},
+ .ColumnMaxValues = {MakeUnversionedBooleanValue(false)},
+ .ColumnNonNullValueCounts = {20},
+ .ChunkRowCount = 20,
+ };
+
+ TColumnarStatistics rhs{
+ .ColumnDataWeights = {200},
+ .ColumnMinValues = {MakeUnversionedDoubleValue(1.9)},
+ .ColumnMaxValues = {MakeUnversionedStringValue("pick")},
+ .ColumnNonNullValueCounts = {13},
+ .ChunkRowCount = 13,
+ };
+
+ lhs += rhs;
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {300},
+ .ColumnMinValues = {MakeUnversionedInt64Value(10)},
+ .ColumnMaxValues = {MakeUnversionedStringValue("pick")},
+ .ColumnNonNullValueCounts = {33},
+ .ChunkRowCount = 33,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(lhs, expected);
+}
+
+TEST(TMergeColumnarStatisticsTest, NoValueStatistics)
+{
+ TColumnarStatistics lhs{
+ .ColumnDataWeights = {64},
+ .ColumnMinValues = {MakeUnversionedInt64Value(10)},
+ .ColumnMaxValues = {MakeUnversionedInt64Value(22)},
+ .ColumnNonNullValueCounts = {8},
+ .ChunkRowCount = 8,
+ };
+
+ auto rhs = TColumnarStatistics::MakeEmpty(1, /*hasValueStatistics*/ false);
+
+ lhs += rhs;
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {64},
+ .ColumnMinValues = {},
+ .ColumnMaxValues = {},
+ .ColumnNonNullValueCounts = {},
+ .ChunkRowCount = 8,
+ .LegacyChunkRowCount = 0,
+ };
+ EXPECT_EQ(lhs, expected);
+}
+
+TEST(TMergeColumnarStatisticsTest, LegacyStruct)
+{
+ TColumnarStatistics lhs{
+ .ColumnDataWeights = {64},
+ .ColumnMinValues = {MakeUnversionedInt64Value(10)},
+ .ColumnMaxValues = {MakeUnversionedInt64Value(22)},
+ .ColumnNonNullValueCounts = {8},
+ .ChunkRowCount = 8,
+ };
+
+ auto rhs = TColumnarStatistics::MakeLegacy(1, 178, 3);
+
+ lhs += rhs;
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {64},
+ .LegacyChunkDataWeight = 178,
+ .ColumnMinValues = {},
+ .ColumnMaxValues = {},
+ .ColumnNonNullValueCounts = {},
+ .ChunkRowCount = 8,
+ .LegacyChunkRowCount = 3,
+ };
+ EXPECT_EQ(lhs, expected);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TColumnarStatisticsColumnSelectionTest, ColumnSelect)
+{
+ TColumnarStatistics statistics{
+ .ColumnDataWeights = {64, 33, 10, 64, 100, 72, 0},
+ .ColumnMinValues = {
+ MakeUnversionedInt64Value(-10),
+ MakeUnversionedStringValue("fax"),
+ MakeUnversionedBooleanValue(true),
+ MakeUnversionedDoubleValue(0.12),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ MakeUnversionedUint64Value(42),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedInt64Value(20),
+ MakeUnversionedStringValue("syrok"),
+ MakeUnversionedBooleanValue(true),
+ MakeUnversionedDoubleValue(34.43),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ MakeUnversionedUint64Value(50),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnNonNullValueCounts = {8, 5, 10, 8, 3, 9, 0},
+ .ChunkRowCount = 10,
+ .LegacyChunkRowCount = 2,
+ };
+ auto nameTable = TNameTable::FromKeyColumns({"buzz", "off", "taken", "sec", "list", "size", "friend"});
+ std::vector<TStableName> stableNames = {
+ TStableName("friend"),
+ TStableName("taken"),
+ TStableName("buzz"),
+ TStableName("foo"),
+ TStableName("list"),
+ TStableName("bar"),
+ };
+
+ auto selectedStatistics = statistics.SelectByColumnNames(nameTable, stableNames);
+
+ TColumnarStatistics expected{
+ .ColumnDataWeights = {0, 10, 64, 0, 100, 0},
+ .ColumnMinValues = {
+ MakeUnversionedNullValue(),
+ MakeUnversionedBooleanValue(true),
+ MakeUnversionedInt64Value(-10),
+ MakeUnversionedNullValue(),
+ MakeUnversionedSentinelValue(EValueType::Min),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnMaxValues = {
+ MakeUnversionedNullValue(),
+ MakeUnversionedBooleanValue(true),
+ MakeUnversionedInt64Value(20),
+ MakeUnversionedNullValue(),
+ MakeUnversionedSentinelValue(EValueType::Max),
+ MakeUnversionedNullValue(),
+ },
+ .ColumnNonNullValueCounts = {0, 10, 8, 0, 3, 0},
+ .ChunkRowCount = 10,
+ .LegacyChunkRowCount = 2,
+ };
+ EXPECT_EQ(selectedStatistics, expected);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unittests/columnar_ut.cpp b/yt/yt/client/table_client/unittests/columnar_ut.cpp
new file mode 100644
index 0000000000..66a5067af7
--- /dev/null
+++ b/yt/yt/client/table_client/unittests/columnar_ut.cpp
@@ -0,0 +1,717 @@
+#include <yt/yt/client/table_client/columnar.h>
+#include <yt/yt/client/table_client/logical_type.h>
+
+#include <yt/yt/client/table_client/unittests/helpers/helpers.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <array>
+
+namespace NYT::NTableClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TBuildValidityBitmapFromDictionaryIndexesWithZeroNullTest, Empty)
+{
+ BuildValidityBitmapFromDictionaryIndexesWithZeroNull(
+ TRange<ui32>(),
+ TMutableRef());
+}
+
+TEST(TBuildValidityBitmapFromDictionaryIndexesWithZeroNullTest, LessThanByte)
+{
+ std::array<ui32, 6> indexes{0, 0, 1, 3, 4, 0};
+ ui8 result;
+ BuildValidityBitmapFromDictionaryIndexesWithZeroNull(
+ MakeRange(indexes),
+ TMutableRef(&result, 1));
+ EXPECT_EQ(0x1c, result);
+}
+
+TEST(TBuildValidityBitmapFromDictionaryIndexesWithZeroNullTest, TenBytes)
+{
+ std::array<ui32, 80> indexes;
+ for (int i = 0; i < std::ssize(indexes); ++i) {
+ indexes[i] = i % 2;
+ }
+ std::array<ui8, indexes.size() / 8> result;
+ BuildValidityBitmapFromDictionaryIndexesWithZeroNull(
+ MakeRange(indexes),
+ TMutableRef(&result, result.size()));
+ for (int i = 0; i < std::ssize(result); ++i) {
+ EXPECT_EQ(0xaa, result[i]);
+ }
+}
+
+TEST(TBuildValidityBitmapFromDictionaryIndexesWithZeroNullTest, ManyBits)
+{
+ std::array<ui32, 8001> indexes;
+ for (int i = 0; i < std::ssize(indexes); ++i) {
+ indexes[i] = i % 2;
+ }
+ std::array<ui8, indexes.size() / 8 + 1> result;
+ BuildValidityBitmapFromDictionaryIndexesWithZeroNull(
+ MakeRange(indexes),
+ TMutableRef(&result, result.size()));
+ for (int i = 0; i < std::ssize(result) - 1; ++i) {
+ EXPECT_EQ(0xaa, result[i]);
+ }
+ EXPECT_EQ(0, result[result.size() - 1]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TBuildDictionaryIndexesFromDictionaryIndexesWithZeroNullTest, Empty)
+{
+ BuildDictionaryIndexesFromDictionaryIndexesWithZeroNull(
+ TRange<ui32>(),
+ TMutableRange<ui32>());
+}
+
+TEST(TBuildDictionaryIndexesFromDictionaryIndexesWithZeroNullTest, NonEmpty)
+{
+ std::array<ui32, 6> indexes{0, 1, 2, 3, 4, 5};
+ std::array<ui32, indexes.size()> result;
+ BuildDictionaryIndexesFromDictionaryIndexesWithZeroNull(
+ MakeRange(indexes),
+ MakeMutableRange(result));
+ for (int i = 1; i < std::ssize(result); ++i) {
+ EXPECT_EQ(indexes[i] - 1, result[i]);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TCountNullsInDictionaryIndexesWithZeroNullTest, Empty)
+{
+ EXPECT_EQ(0, CountNullsInDictionaryIndexesWithZeroNull(TRange<ui32>()));
+}
+
+TEST(TCountNullsInDictionaryIndexesWithZeroNullTest, NonEmpty)
+{
+ std::array<ui32, 6> indexes{0, 1, 2, 0, 4, 5};
+ EXPECT_EQ(2, CountNullsInDictionaryIndexesWithZeroNull(MakeRange(indexes)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TCountOnesInBitmapTest, Empty)
+{
+ EXPECT_EQ(0, CountOnesInBitmap(TRef(), 0, 0));
+}
+
+TEST(TCountOnesInBitmapTest, SingleByte)
+{
+ ui8 byte = 0xFF;
+ for (int i = 0; i < 8; ++i) {
+ for (int j = i; j < 8; ++j) {
+ EXPECT_EQ(j - i, CountOnesInBitmap(TRef(&byte, 1), i, j))
+ << "i = " << i << " j = " << j;
+ }
+ }
+}
+
+TEST(TCountOnesInBitmapTest, SevenBytes)
+{
+ std::array<ui8, 7> bytes = {0, 0xff, 0xff, 0xff, 0, 0, 1};
+ auto bitmap = TRef(bytes.data(), bytes.size());
+ EXPECT_EQ(25, CountOnesInBitmap(bitmap, 0, 56));
+}
+
+TEST(TCountOnesInBitmapTest, ThreeQwords)
+{
+ std::array<ui64, 3> qwords = {1, 1, 1};
+ auto bitmap = TRef(qwords.data(), 8 * qwords.size());
+ EXPECT_EQ(3, CountOnesInBitmap(bitmap, 0, 64 * 3));
+}
+
+TEST(TCountOnesInBitmapTest, QwordBorder)
+{
+ std::array<ui64, 2> qwords = {0xffffffffffffffffULL, 0xffffffffffffffffULL};
+ auto bitmap = TRef(qwords.data(), 8 * qwords.size());
+ for (int i = 0; i < 10; ++i) {
+ for (int j = 64; j < 74; ++j) {
+ EXPECT_EQ(j - i, CountOnesInBitmap(bitmap, i, j))
+ << "i = " << i << " j = " << j;
+ }
+ }
+}
+
+TEST(TCountOnesInBitmapTest, WholeQwordInTheMiddle)
+{
+ std::array<ui64, 3> qwords = {0xffffffffffffffffULL, 1, 0xffffffffffffffffULL};
+ auto bitmap = TRef(qwords.data(), 8 * qwords.size());
+ for (int i = 0; i < 10; ++i) {
+ for (int j = 128; j < 138; ++j) {
+ EXPECT_EQ(j - i - 63, CountOnesInBitmap(bitmap, i, j))
+ << "i = " << i << " j = " << j;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCopyBitmapRangeToBitmapTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int>>
+{ };
+
+TEST_P(TCopyBitmapRangeToBitmapTest, CheckAll)
+{
+ auto [startIndex, endIndex] = GetParam();
+
+ std::array<ui64, 3> qwords = {0x1234567812345678ULL, 0x1234567812345678ULL, 0xabcdabcdabcdabcdULL};
+ auto bitmap = TRef(qwords.data(), 8 * qwords.size());
+
+ constexpr ssize_t ResultByteSize = 24;
+ auto byteCount = GetBitmapByteSize(endIndex - startIndex);
+ EXPECT_LE(byteCount, ResultByteSize);
+
+ std::array<ui8, ResultByteSize> guard;
+ for (int i = 0; i < ResultByteSize; ++i) {
+ guard[i] = i;
+ }
+
+ auto result = guard;
+ auto resultNegated = guard;
+
+ CopyBitmapRangeToBitmap(bitmap, startIndex, endIndex, TMutableRef(result.data(), byteCount));
+ CopyBitmapRangeToBitmapNegated(bitmap, startIndex, endIndex, TMutableRef(resultNegated.data(), byteCount));
+
+ auto getBit = [] (const auto& array, int index) {
+ return (reinterpret_cast<const char*>(array.begin())[index / 8] & (1U << (index % 8))) != 0;
+ };
+
+ for (int i = startIndex; i < endIndex; ++i) {
+ auto srcBit = getBit(bitmap, i);
+ auto dstBit = getBit(result, i - startIndex);
+ auto invertedDstBit = getBit(resultNegated, i - startIndex);
+ EXPECT_EQ(srcBit, dstBit) << "i = " << i;
+ EXPECT_NE(srcBit, invertedDstBit) << "i = " << i;
+ }
+
+ for (int i = byteCount; i < ResultByteSize; ++i) {
+ EXPECT_EQ(guard[i], result[i]);
+ EXPECT_EQ(guard[i], resultNegated[i]);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TCopyBitmapRangeToBitmapTest,
+ TCopyBitmapRangeToBitmapTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0),
+ std::make_tuple( 0, 64),
+ std::make_tuple( 0, 192),
+ std::make_tuple( 64, 128),
+ std::make_tuple( 8, 16),
+ std::make_tuple( 10, 13),
+ std::make_tuple( 5, 120),
+ std::make_tuple( 23, 67),
+ std::make_tuple( 1, 191)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TTranslateRleIndexTest, CheckAll)
+{
+ std::array<ui64, 8> rleIndexes{0, 1, 3, 10, 11, 15, 20, 21};
+ for (ui64 i = 0; i < rleIndexes[rleIndexes.size() - 1] + 10; ++i) {
+ auto j = TranslateRleIndex(MakeRange(rleIndexes), i);
+ EXPECT_LE(rleIndexes[j], i);
+ if (j < std::ssize(rleIndexes) - 1) {
+ EXPECT_LT(i, rleIndexes[j + 1]);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecodeRleVectorTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int, std::vector<int>>>
+{ };
+
+TEST_P(TDecodeRleVectorTest, CheckAll)
+{
+ std::vector<int> values{1, 2, 3, 4, 5, 6};
+ std::vector<ui64> rleIndexes{0, 1, 10, 11, 20, 25};
+ const auto& [startIndex, endIndex, expected] = GetParam();
+ std::vector<int> decoded;
+ DecodeRawVector<int>(
+ startIndex,
+ endIndex,
+ {},
+ rleIndexes,
+ [&] (auto index) {
+ return values[index];
+ },
+ [&] (auto value) {
+ decoded.push_back(value);
+ });
+ EXPECT_EQ(expected, decoded);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TDecodeRleVectorTest,
+ TDecodeRleVectorTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0, std::vector<int>{}),
+ std::make_tuple( 0, 1, std::vector<int>{1}),
+ std::make_tuple( 0, 5, std::vector<int>{1, 2, 2, 2, 2}),
+ std::make_tuple( 9, 13, std::vector<int>{2, 3, 4, 4}),
+ std::make_tuple( 20, 25, std::vector<int>{5, 5, 5, 5, 5}),
+ std::make_tuple( 25, 27, std::vector<int>{6, 6}),
+ std::make_tuple( 50, 53, std::vector<int>{6, 6, 6})));
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TDecodeIntegerValueTest, CheckAll)
+{
+ EXPECT_EQ(100, DecodeIntegerValue<int>(100, 0, false));
+ EXPECT_EQ(100, DecodeIntegerValue<int>( 40, 60, false));
+ EXPECT_EQ( 9, DecodeIntegerValue<int>( 18, 0, true));
+ EXPECT_EQ(-10, DecodeIntegerValue<int>( 19, 0, true));
+ EXPECT_EQ( 9, DecodeIntegerValue<int>( 1, 17, true));
+ EXPECT_EQ(-10, DecodeIntegerValue<int>( 2, 17, true));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecodeStringsTest
+ : public ::testing::Test
+{
+protected:
+ std::vector<ui32> Offsets = {1, 2, 3, 4, 5};
+ static constexpr ui32 AvgLength = 10;
+ std::vector<ui32> Expected = {0, 9, 21, 28, 42, 47};
+};
+
+TEST_F(TDecodeStringsTest, Single)
+{
+ std::vector<ui32> result;
+ for (int index = 0; index <= std::ssize(Offsets); ++index) {
+ result.push_back(DecodeStringOffset(MakeRange(Offsets), AvgLength, index));
+ }
+ EXPECT_EQ(Expected, result);
+}
+
+TEST_F(TDecodeStringsTest, Multiple)
+{
+ for (int i = 0; i <= std::ssize(Offsets); ++i) {
+ for (int j = i; j <= std::ssize(Offsets); ++j) {
+ std::vector<ui32> result(j - i + 1);
+ DecodeStringOffsets(MakeRange(Offsets), AvgLength, i, j, MakeMutableRange(result));
+ std::vector<ui32> expected;
+ for (int k = i; k <= j; ++k) {
+ expected.push_back(Expected[k] - DecodeStringOffset(MakeRange(Offsets), AvgLength, i));
+ }
+ EXPECT_EQ(expected, result);
+ }
+ }
+}
+
+TEST_F(TDecodeStringsTest, PointersAndLengths)
+{
+ std::vector<char> chars(100);
+ std::vector<const char*> strings(Offsets.size());
+ std::vector<i32> lengths(Offsets.size());
+ DecodeStringPointersAndLengths(
+ MakeRange(Offsets),
+ AvgLength,
+ TRef(chars.data(), chars.size()),
+ MakeMutableRange(strings),
+ MakeMutableRange(lengths));
+ for (int i = 0; i < std::ssize(Offsets); ++i) {
+ EXPECT_EQ(Expected[i], strings[i] - chars.data());
+ EXPECT_EQ(static_cast<ssize_t>(Expected[i + 1] - Expected[i]), lengths[i]);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecodeNullsFromRleDictionaryIndexesWithZeroNullTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int>>
+{
+protected:
+ void SetUp() override
+ {
+ Expected.resize(800);
+ std::fill(Expected.begin() + 3, Expected.begin() + 5, true);
+ std::fill(Expected.begin() + 20, Expected.begin() + 100, true);
+ std::fill(Expected.begin() + 200, Expected.begin() + 800, true);
+ }
+
+
+ std::vector<ui32> DictionaryIndexes = {0, 1, 0, 1, 0, 1};
+ std::vector<ui64> RleIndexes = {0, 3, 5, 20, 100, 200};
+ std::vector<bool> Expected;
+};
+
+TEST_P(TDecodeNullsFromRleDictionaryIndexesWithZeroNullTest, ValidityBitmap)
+{
+ auto [startIndex, endIndex] = GetParam();
+ std::vector<ui8> bitmap(GetBitmapByteSize(endIndex - startIndex));
+ BuildValidityBitmapFromRleDictionaryIndexesWithZeroNull(
+ MakeRange(DictionaryIndexes),
+ MakeRange(RleIndexes),
+ startIndex,
+ endIndex,
+ TMutableRef(bitmap.data(), bitmap.size()));
+ for (int i = startIndex; i < endIndex; ++i) {
+ EXPECT_EQ(Expected[i], GetBit(TRef(bitmap.data(), bitmap.size()), i - startIndex))
+ << "i = " << i;
+ }
+}
+
+TEST_P(TDecodeNullsFromRleDictionaryIndexesWithZeroNullTest, NullBytemap)
+{
+ auto [startIndex, endIndex] = GetParam();
+ std::vector<ui8> bytemap(endIndex - startIndex);
+ BuildNullBytemapFromRleDictionaryIndexesWithZeroNull(
+ MakeRange(DictionaryIndexes),
+ MakeRange(RleIndexes),
+ startIndex,
+ endIndex,
+ MakeMutableRange(bytemap));
+ for (int i = startIndex; i < endIndex; ++i) {
+ EXPECT_TRUE(bytemap[i - startIndex] == 0 || bytemap[i - startIndex] == 1);
+ EXPECT_EQ(Expected[i], bytemap[i - startIndex] == 0)
+ << "i = " << i;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TDecodeNullsFromRleDictionaryIndexesWithZeroNullTest,
+ TDecodeNullsFromRleDictionaryIndexesWithZeroNullTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0),
+ std::make_tuple( 0, 800),
+ std::make_tuple(256, 512),
+ std::make_tuple( 10, 20),
+ std::make_tuple( 10, 20),
+ std::make_tuple( 20, 100),
+ std::make_tuple( 90, 110)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNullTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int>>
+{
+protected:
+ std::vector<ui32> DictionaryIndexes = {0, 1, 0, 2, 3};
+ std::vector<ui64> RleIndexes = {0, 3, 5, 10, 12};
+
+ static constexpr ui32 Z = static_cast<ui32>(-1);
+ std::vector<ui32> Expected = {Z, Z, Z, 0, 0, Z, Z, Z, Z, Z, 1, 1, 2, 2, 2};
+};
+
+TEST_P(TBuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNullTest, CheckAll)
+{
+ auto [startIndex, endIndex] = GetParam();
+ std::vector<ui32> result(endIndex - startIndex);
+ BuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNull(
+ MakeRange(DictionaryIndexes),
+ MakeRange(RleIndexes),
+ startIndex,
+ endIndex,
+ MakeMutableRange(result));
+ for (int i = startIndex; i < endIndex; ++i) {
+ EXPECT_EQ(Expected[i], result[i - startIndex])
+ << "i = " << i;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TBuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNullTest,
+ TBuildDictionaryIndexesFromRleDictionaryIndexesWithZeroNullTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0),
+ std::make_tuple( 0, 15),
+ std::make_tuple( 3, 5),
+ std::make_tuple( 1, 10),
+ std::make_tuple( 13, 15)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBuildIotaDictionaryIndexesFromRleIndexesTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int, std::vector<ui32>>>
+{
+protected:
+ std::vector<ui64> RleIndexes = {0, 3, 5, 10, 12};
+};
+
+TEST_P(TBuildIotaDictionaryIndexesFromRleIndexesTest, CheckAll)
+{
+ auto [startIndex, endIndex, expected] = GetParam();
+ std::vector<ui32> result(endIndex - startIndex);
+ BuildIotaDictionaryIndexesFromRleIndexes(
+ MakeRange(RleIndexes),
+ startIndex,
+ endIndex,
+ MakeMutableRange(result));
+ EXPECT_EQ(expected, result);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TBuildIotaDictionaryIndexesFromRleIndexesTest,
+ TBuildIotaDictionaryIndexesFromRleIndexesTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0, std::vector<ui32>{}),
+ std::make_tuple( 0, 15, std::vector<ui32>{0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 4, 4}),
+ std::make_tuple( 3, 5, std::vector<ui32>{0, 0}),
+ std::make_tuple( 1, 10, std::vector<ui32>{0, 0, 1, 1, 2, 2, 2, 2, 2}),
+ std::make_tuple( 13, 15, std::vector<ui32>{0, 0})));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCountNullsInDictionaryIndexesWithZeroNullTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<std::vector<ui32>, int>>
+{ };
+
+TEST_P(TCountNullsInDictionaryIndexesWithZeroNullTest, CheckAll)
+{
+ const auto& [indexes, expected] = GetParam();
+ EXPECT_EQ(expected, CountNullsInDictionaryIndexesWithZeroNull(MakeRange(indexes)));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TCountNullsInDictionaryIndexesWithZeroNullTest,
+ TCountNullsInDictionaryIndexesWithZeroNullTest,
+ ::testing::Values(
+ std::make_tuple(std::vector<ui32>{}, 0),
+ std::make_tuple(std::vector<ui32>{0, 0, 0}, 3),
+ std::make_tuple(std::vector<ui32>{1, 2, 3}, 0),
+ std::make_tuple(std::vector<ui32>{1, 0, 3}, 1)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCountOnesInRleBitmapTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int, int>>
+{
+protected:
+ std::vector<ui64> RleIndexes = {0, 3, 5, 20, 50};
+ ui8 Bitmap = 0b10101;
+};
+
+TEST_P(TCountOnesInRleBitmapTest, CheckAll)
+{
+ auto [startIndex, endIndex, expected] = GetParam();
+ EXPECT_EQ(expected, CountOnesInRleBitmap(TRef(&Bitmap, 1), MakeRange(RleIndexes), startIndex, endIndex));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TCountOnesInRleBitmapTest,
+ TCountOnesInRleBitmapTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0, 0),
+ std::make_tuple( 50, 60, 10),
+ std::make_tuple( 40, 60, 10),
+ std::make_tuple( 60, 100, 40),
+ std::make_tuple( 3, 5, 0),
+ std::make_tuple( 2, 6, 2)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecodeNullsFromRleNullBitmapTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int>>
+{
+protected:
+ void SetUp() override
+ {
+ Expected.resize(800);
+ std::fill(Expected.begin() + 3, Expected.begin() + 5, true);
+ std::fill(Expected.begin() + 20, Expected.begin() + 100, true);
+ std::fill(Expected.begin() + 200, Expected.begin() + 800, true);
+ }
+
+
+ std::vector<ui64> RleIndexes = {0, 3, 5, 20, 100, 200};
+ ui8 Bitmap = 0b010101;
+ std::vector<bool> Expected;
+};
+
+TEST_P(TDecodeNullsFromRleNullBitmapTest, ValidityBitmap)
+{
+ auto [startIndex, endIndex] = GetParam();
+ std::vector<ui8> bitmap(GetBitmapByteSize(endIndex - startIndex));
+ BuildValidityBitmapFromRleNullBitmap(
+ TRef(&Bitmap, 1),
+ MakeRange(RleIndexes),
+ startIndex,
+ endIndex,
+ TMutableRef(bitmap.data(), bitmap.size()));
+ for (int i = startIndex; i < endIndex; ++i) {
+ EXPECT_EQ(Expected[i], GetBit(TRef(bitmap.data(), bitmap.size()), i - startIndex))
+ << "i = " << i;
+ }
+}
+
+TEST_P(TDecodeNullsFromRleNullBitmapTest, NullBytemap)
+{
+ auto [startIndex, endIndex] = GetParam();
+ std::vector<ui8> bytemap(endIndex - startIndex);
+ BuildNullBytemapFromRleNullBitmap(
+ TRef(&Bitmap, 1),
+ MakeRange(RleIndexes),
+ startIndex,
+ endIndex,
+ MakeMutableRange(bytemap));
+ for (int i = startIndex; i < endIndex; ++i) {
+ EXPECT_TRUE(bytemap[i - startIndex] == 0 || bytemap[i - startIndex] == 1);
+ EXPECT_EQ(Expected[i], bytemap[i - startIndex] == 0)
+ << "i = " << i;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TDecodeNullsFromRleNullBitmapTest,
+ TDecodeNullsFromRleNullBitmapTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0),
+ std::make_tuple( 0, 800),
+ std::make_tuple(256, 512),
+ std::make_tuple( 10, 20),
+ std::make_tuple( 10, 20),
+ std::make_tuple( 20, 100),
+ std::make_tuple( 90, 110)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecodeBytemapFromBitmapTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int>>
+{
+protected:
+ void SetUp() override
+ {
+ Expected.resize(800);
+ std::fill(Expected.begin() + 3, Expected.begin() + 5, true);
+ std::fill(Expected.begin() + 20, Expected.begin() + 100, true);
+ std::fill(Expected.begin() + 200, Expected.begin() + 800, true);
+
+ Bitmap.resize(GetBitmapByteSize(Expected.size()));
+ for (int i = 0; i < std::ssize(Expected); ++i) {
+ SetBit(TMutableRef(Bitmap.data(), Bitmap.size()), i, Expected[i]);
+ }
+ }
+
+ std::vector<bool> Expected;
+ std::vector<ui8> Bitmap;
+};
+
+TEST_P(TDecodeBytemapFromBitmapTest, CheckAll)
+{
+ auto [startIndex, endIndex] = GetParam();
+ std::vector<ui8> bytemap(endIndex - startIndex);
+ DecodeBytemapFromBitmap(
+ TRef(Bitmap.data(), Bitmap.size()),
+ startIndex,
+ endIndex,
+ MakeMutableRange(bytemap));
+ for (int i = startIndex; i < endIndex; ++i) {
+ EXPECT_TRUE(bytemap[i - startIndex] == 0 || bytemap[i - startIndex] == 1);
+ EXPECT_EQ(Expected[i], bytemap[i - startIndex])
+ << "i = " << i;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TDecodeBytemapFromBitmapTest,
+ TDecodeBytemapFromBitmapTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0),
+ std::make_tuple( 18, 19),
+ std::make_tuple( 0, 512),
+ std::make_tuple( 0, 800),
+ std::make_tuple(256, 512),
+ std::make_tuple( 10, 20),
+ std::make_tuple( 10, 20),
+ std::make_tuple( 20, 100),
+ std::make_tuple( 90, 110)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCountTotalStringLengthInRleDictionaryIndexesWithZeroNullTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<int, int>>
+{
+protected:
+ void SetUp() override
+ {
+ int j = -1;
+ for (int i = 0; i < 40; ++i) {
+ if (j + 1 < std::ssize(RleIndexes) && i == static_cast<ssize_t>(RleIndexes[j + 1])) {
+ ++j;
+ }
+ auto dictionaryIndex = DictionaryIndexes[j];
+ if (dictionaryIndex != 0) {
+ auto stringIndex = dictionaryIndex - 1;
+ DecodedOffsets.push_back(DecodeStringOffset(Offsets, AvgLength, stringIndex));
+ } else {
+ DecodedOffsets.push_back(DecodedOffsets.empty() ? 0 : DecodedOffsets.back());
+ }
+ }
+
+ for (int i = 0; i < std::ssize(Offsets); ++i) {
+ auto [startOffset, endOffset] = DecodeStringRange(
+ MakeRange(Offsets),
+ AvgLength,
+ i);
+ Lengths.push_back(endOffset - startOffset);
+ }
+ }
+
+ std::vector<ui64> RleIndexes = {0, 1, 3, 10, 15, 16, 18};
+ std::vector<ui32> DictionaryIndexes = {0, 1, 0, 2, 3, 4, 5};
+ std::vector<ui32> Offsets = {1, 2, 3, 4, 5};
+ static constexpr ui32 AvgLength = 10;
+ std::vector<i32> Lengths;
+ std::vector<i64> DecodedOffsets;
+};
+
+TEST_P(TCountTotalStringLengthInRleDictionaryIndexesWithZeroNullTest, CheckAll)
+{
+ auto [startIndex, endIndex] = GetParam();
+
+ auto actual = CountTotalStringLengthInRleDictionaryIndexesWithZeroNull(
+ MakeRange(DictionaryIndexes),
+ MakeRange(RleIndexes),
+ MakeRange(Lengths),
+ startIndex,
+ endIndex);
+
+ i64 expected = 0;
+ for (int i = startIndex; i < endIndex; ++i) {
+ int j = TranslateRleIndex(MakeRange(RleIndexes), i);
+ auto k = DictionaryIndexes[j];
+ if (k != 0) {
+ auto [startOffset, endOffset] = DecodeStringRange(MakeRange(Offsets), AvgLength, k - 1);
+ expected += (endOffset - startOffset);
+ }
+ }
+
+ EXPECT_EQ(expected, actual);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TCountTotalStringLengthInRleDictionaryIndexesWithZeroNullTest,
+ TCountTotalStringLengthInRleDictionaryIndexesWithZeroNullTest,
+ ::testing::Values(
+ std::make_tuple( 0, 0),
+ std::make_tuple( 0, 30),
+ std::make_tuple( 1, 3),
+ std::make_tuple( 5, 10),
+ std::make_tuple( 4, 25),
+ std::make_tuple( 2, 4)));
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unittests/helpers/helpers.cpp b/yt/yt/client/table_client/unittests/helpers/helpers.cpp
new file mode 100644
index 0000000000..c28b576218
--- /dev/null
+++ b/yt/yt/client/table_client/unittests/helpers/helpers.cpp
@@ -0,0 +1,282 @@
+#include "helpers.h"
+
+#include <yt/yt/client/chunk_client/read_limit.h>
+
+#include <yt/yt/client/table_client/columnar_statistics.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/versioned_reader.h>
+
+namespace NYT::NTableClient {
+
+using namespace NChunkClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CheckEqual(const TUnversionedValue& expected, const TUnversionedValue& actual)
+{
+ // Fast path.
+ if (TBitwiseUnversionedValueEqual()(expected, actual)) {
+ return;
+ }
+
+ SCOPED_TRACE(Format("Expected: %v; Actual: %v", expected, actual));
+ ASSERT_TRUE(TBitwiseUnversionedValueEqual()(expected, actual));
+}
+
+void CheckEqual(const TVersionedValue& expected, const TVersionedValue& actual)
+{
+ // Fast path.
+ if (TBitwiseVersionedValueEqual()(expected, actual)) {
+ return;
+ }
+
+ SCOPED_TRACE(Format("Expected: %v; Actual: %v", expected, actual));
+ ASSERT_TRUE(TBitwiseVersionedValueEqual()(expected, actual));
+}
+
+void ExpectSchemafulRowsEqual(TUnversionedRow expected, TUnversionedRow actual)
+{
+ // Fast path.
+ if (TBitwiseUnversionedRowEqual()(expected, actual)) {
+ return;
+ }
+
+ SCOPED_TRACE(Format("Expected: %v; Actual: %v", expected, actual));
+
+ ASSERT_EQ(static_cast<bool>(expected), static_cast<bool>(actual));
+ if (!expected || !actual) {
+ return;
+ }
+ ASSERT_EQ(expected.GetCount(), actual.GetCount());
+
+ for (int valueIndex = 0; valueIndex < static_cast<int>(expected.GetCount()); ++valueIndex) {
+ SCOPED_TRACE(Format("Value index %v", valueIndex));
+ CheckEqual(expected[valueIndex], actual[valueIndex]);
+ }
+}
+
+void ExpectSchemalessRowsEqual(TUnversionedRow expected, TUnversionedRow actual, int keyColumnCount)
+{
+ // Fast path.
+ if (TBitwiseUnversionedRowEqual()(expected, actual)) {
+ return;
+ }
+
+ SCOPED_TRACE(Format("Expected: %v; Actual: %v", expected, actual));
+
+ ASSERT_EQ(static_cast<bool>(expected), static_cast<bool>(actual));
+ if (!expected || !actual) {
+ return;
+ }
+ ASSERT_EQ(expected.GetCount(), actual.GetCount());
+
+ for (int valueIndex = 0; valueIndex < keyColumnCount; ++valueIndex) {
+ SCOPED_TRACE(Format("Value index %v", valueIndex));
+ CheckEqual(expected[valueIndex], actual[valueIndex]);
+ }
+
+ for (int valueIndex = keyColumnCount; valueIndex < static_cast<int>(expected.GetCount()); ++valueIndex) {
+ SCOPED_TRACE(Format("Value index %v", valueIndex));
+
+ // Find value with the same id. Since this in schemaless read, value positions can be different.
+ bool found = false;
+ for (int index = keyColumnCount; index < static_cast<int>(expected.GetCount()); ++index) {
+ if (expected[valueIndex].Id == actual[index].Id) {
+ CheckEqual(expected[valueIndex], actual[index]);
+ found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(found);
+ }
+}
+
+void ExpectSchemafulRowsEqual(TVersionedRow expected, TVersionedRow actual)
+{
+ // Fast path.
+ if (TBitwiseVersionedRowEqual()(expected, actual)) {
+ return;
+ }
+
+ SCOPED_TRACE(Format("Expected: %v; Actual: %v", expected, actual));
+
+ ASSERT_EQ(static_cast<bool>(expected), static_cast<bool>(actual));
+ if (!expected || !actual) {
+ return;
+ }
+
+ ASSERT_EQ(expected.GetWriteTimestampCount(), actual.GetWriteTimestampCount());
+ for (int i = 0; i < expected.GetWriteTimestampCount(); ++i) {
+ SCOPED_TRACE(Format("Write Timestamp %v", i));
+ ASSERT_EQ(expected.WriteTimestamps()[i], actual.WriteTimestamps()[i]);
+ }
+
+ ASSERT_EQ(expected.GetDeleteTimestampCount(), actual.GetDeleteTimestampCount());
+ for (int i = 0; i < expected.GetDeleteTimestampCount(); ++i) {
+ SCOPED_TRACE(Format("Delete Timestamp %v", i));
+ ASSERT_EQ(expected.DeleteTimestamps()[i], actual.DeleteTimestamps()[i]);
+ }
+
+ ASSERT_EQ(expected.GetKeyCount(), actual.GetKeyCount());
+ for (int index = 0; index < expected.GetKeyCount(); ++index) {
+ SCOPED_TRACE(Format("Key index %v", index));
+ CheckEqual(expected.Keys()[index], actual.Keys()[index]);
+ }
+
+ ASSERT_EQ(expected.GetValueCount(), actual.GetValueCount());
+ for (int index = 0; index < expected.GetValueCount(); ++index) {
+ SCOPED_TRACE(Format("Value index %v", index));
+ CheckEqual(expected.Values()[index], actual.Values()[index]);
+ }
+}
+
+void CheckResult(std::vector<TVersionedRow> expected, IVersionedReaderPtr reader, bool filterOutNullRows)
+{
+ auto it = expected.begin();
+ std::vector<TVersionedRow> actual;
+ actual.reserve(100);
+
+ while (auto batch = reader->Read({.MaxRowsPerRead = 100})) {
+ if (batch->IsEmpty()) {
+ ASSERT_TRUE(reader->GetReadyEvent().Get().IsOK());
+ continue;
+ }
+
+ auto range = batch->MaterializeRows();
+ std::vector<TVersionedRow> actual(range.begin(), range.end());
+
+ if (filterOutNullRows) {
+ actual.erase(
+ std::remove_if(
+ actual.begin(),
+ actual.end(),
+ [] (TVersionedRow row) {
+ return !row;
+ }),
+ actual.end());
+ }
+
+ std::vector<TVersionedRow> ex(it, std::min(it + actual.size(), expected.end()));
+
+ CheckSchemafulResult(ex, actual);
+ it += ex.size();
+ }
+
+ ASSERT_TRUE(it == expected.end());
+}
+
+std::vector<std::pair<ui32, ui32>> GetTimestampIndexRanges(
+ TRange<TVersionedRow> rows,
+ TTimestamp timestamp)
+{
+ std::vector<std::pair<ui32, ui32>> indexRanges;
+ for (auto row : rows) {
+ // Find delete timestamp.
+ auto deleteTimestamp = NTableClient::NullTimestamp;
+ for (auto currentTimestamp : row.DeleteTimestamps()) {
+ if (currentTimestamp <= timestamp) {
+ deleteTimestamp = std::max(currentTimestamp, deleteTimestamp);
+ }
+ }
+
+ int lowerTimestampIndex = 0;
+ while (lowerTimestampIndex < row.GetWriteTimestampCount() &&
+ row.WriteTimestamps()[lowerTimestampIndex] > timestamp)
+ {
+ ++lowerTimestampIndex;
+ }
+
+ int upperTimestampIndex = lowerTimestampIndex;
+ while (upperTimestampIndex < row.GetWriteTimestampCount() &&
+ row.WriteTimestamps()[upperTimestampIndex] > deleteTimestamp)
+ {
+ ++upperTimestampIndex;
+ }
+
+ indexRanges.push_back(std::make_pair(lowerTimestampIndex, upperTimestampIndex));
+ }
+ return indexRanges;
+}
+
+std::vector<TUnversionedRow> CreateFilteredRangedRows(
+ const std::vector<TUnversionedRow>& initial,
+ TNameTablePtr writeNameTable,
+ TNameTablePtr readNameTable,
+ TColumnFilter columnFilter,
+ TLegacyReadRange readRange,
+ TChunkedMemoryPool* pool,
+ int keyColumnCount)
+{
+ std::vector<TUnversionedRow> rows;
+
+ int lowerRowIndex = 0;
+ if (readRange.LowerLimit().HasRowIndex()) {
+ lowerRowIndex = readRange.LowerLimit().GetRowIndex();
+ }
+
+ int upperRowIndex = initial.size();
+ if (readRange.UpperLimit().HasRowIndex()) {
+ upperRowIndex = readRange.UpperLimit().GetRowIndex();
+ }
+
+ auto fulfillLowerKeyLimit = [&] (TUnversionedRow row) {
+ return !readRange.LowerLimit().HasLegacyKey() ||
+ CompareValueRanges(
+ row.FirstNElements(keyColumnCount),
+ readRange.LowerLimit().GetLegacyKey().Elements()) >= 0;
+ };
+
+ auto fulfillUpperKeyLimit = [&] (TUnversionedRow row) {
+ return !readRange.UpperLimit().HasLegacyKey() ||
+ CompareValueRanges(
+ row.FirstNElements(keyColumnCount),
+ readRange.UpperLimit().GetLegacyKey().Elements()) < 0;
+ };
+
+ for (int rowIndex = lowerRowIndex; rowIndex < upperRowIndex; ++rowIndex) {
+ auto initialRow = initial[rowIndex];
+ if (fulfillLowerKeyLimit(initialRow) && fulfillUpperKeyLimit(initialRow)) {
+ auto row = TMutableUnversionedRow::Allocate(pool, initialRow.GetCount());
+ int count = 0;
+ for (const auto* it = initialRow.Begin(); it != initialRow.End(); ++it) {
+ auto name = writeNameTable->GetName(it->Id);
+ auto readerId = readNameTable->GetId(name);
+
+ if (columnFilter.ContainsIndex(readerId)) {
+ row[count] = *it;
+ row[count].Id = readerId;
+ ++count;
+ }
+ }
+ row.SetCount(count);
+ rows.push_back(row);
+ }
+ }
+
+ return rows;
+}
+
+void PrintTo(const TColumnarStatistics& statistics, std::ostream* os)
+{
+ *os
+ << "ColumnDataWeights: "
+ << ::testing::PrintToString(statistics.ColumnDataWeights) << "\n"
+ << "TimestampTotalWeight: "
+ << ::testing::PrintToString(statistics.TimestampTotalWeight) << "\n"
+ << "LegacyChunkDataWeight: "
+ << ::testing::PrintToString(statistics.LegacyChunkDataWeight) << "\n"
+ << "ColumnMinValues: "
+ << ::testing::PrintToString(statistics.ColumnMinValues) << "\n"
+ << "ColumnMaxValues: "
+ << ::testing::PrintToString(statistics.ColumnMaxValues) << "\n"
+ << "ColumnNonNullValueCounts: "
+ << ::testing::PrintToString(statistics.ColumnNonNullValueCounts) << "\n"
+ << "ChunkRowCount: "
+ << ::testing::PrintToString(statistics.ChunkRowCount) << "\n"
+ << "LegacyChunkRowCount: "
+ << ::testing::PrintToString(statistics.LegacyChunkRowCount);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unittests/helpers/helpers.h b/yt/yt/client/table_client/unittests/helpers/helpers.h
new file mode 100644
index 0000000000..2aa50d5b46
--- /dev/null
+++ b/yt/yt/client/table_client/unittests/helpers/helpers.h
@@ -0,0 +1,232 @@
+#pragma once
+
+#include <yt/yt/core/test_framework/framework.h>
+
+
+#include <yt/yt/client/table_client/columnar.h>
+#include <yt/yt/client/table_client/config.h>
+#include <yt/yt/client/table_client/public.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <iostream>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ExpectSchemafulRowsEqual(TUnversionedRow expected, TUnversionedRow actual);
+
+void ExpectSchemalessRowsEqual(TUnversionedRow expected, TUnversionedRow actual, int keyColumnCount);
+
+void ExpectSchemafulRowsEqual(TVersionedRow expected, TVersionedRow actual);
+
+void CheckResult(std::vector<TVersionedRow> expected, IVersionedReaderPtr reader, bool filterOutNullRows = false);
+
+template <class TExpectedRow, class TActualRow>
+void CheckSchemafulResult(const std::vector<TExpectedRow>& expected, const std::vector<TActualRow>& actual)
+{
+ ASSERT_EQ(expected.size(), actual.size());
+ for (int i = 0; i < std::ssize(expected); ++i) {
+ ExpectSchemafulRowsEqual(expected[i], actual[i]);
+ }
+}
+
+template <class TExpectedRow, class TActualRow>
+void CheckSchemalessResult(
+ TRange<TExpectedRow> expected,
+ TRange<TActualRow> actual,
+ int keyColumnCount)
+{
+ ASSERT_EQ(expected.size(), actual.size());
+ for (int i = 0; i < std::ssize(expected); ++i) {
+ ExpectSchemalessRowsEqual(expected[i], actual[i], keyColumnCount);
+ }
+}
+
+template <class TRow, class TReader>
+void CheckSchemalessResult(
+ const std::vector<TRow>& expected,
+ TIntrusivePtr<TReader> reader,
+ int keyColumnCount,
+ TRowBatchReadOptions options = {})
+{
+ size_t offset = 0;
+ while (auto batch = reader->Read(options)) {
+ auto actual = batch->MaterializeRows();
+ if (actual.empty()) {
+ ASSERT_TRUE(reader->GetReadyEvent().Get().IsOK());
+ continue;
+ }
+
+ i64 dataWeight = 0;
+ for (int i = 0; i < std::ssize(actual) - 1; i++) {
+ dataWeight += GetDataWeight(actual[i]);
+ }
+
+ ASSERT_LT(dataWeight, options.MaxDataWeightPerRead);
+ ASSERT_LE(std::ssize(actual), options.MaxRowsPerRead);
+
+ CheckSchemalessResult(
+ MakeRange(expected).Slice(offset, std::min(expected.size(), offset + actual.size())),
+ actual,
+ keyColumnCount);
+ offset += actual.size();
+ }
+
+ ASSERT_EQ(offset, expected.size());
+}
+
+std::vector<std::pair<ui32, ui32>> GetTimestampIndexRanges(
+ TRange<NTableClient::TVersionedRow> rows,
+ NTableClient::TTimestamp timestamp);
+
+template <class T>
+void AppendVector(std::vector<T>* data, const std::vector<T> toAppend)
+{
+ data->insert(data->end(), toAppend.begin(), toAppend.end());
+}
+
+template <class T>
+TRange<T> GetTypedData(const NTableClient::IUnversionedColumnarRowBatch::TValueBuffer& buffer)
+{
+ return MakeRange(
+ reinterpret_cast<const T*>(buffer.Data.Begin()),
+ reinterpret_cast<const T*>(buffer.Data.End()));
+}
+
+inline bool GetBit(const NTableClient::IUnversionedColumnarRowBatch::TValueBuffer& buffer, int index)
+{
+ return (buffer.Data[index / 8] & (1 << (index % 8))) != 0;
+}
+
+inline bool GetBit(TRef data, int index)
+{
+ return (data[index / 8] & (1 << (index % 8))) != 0;
+}
+
+inline bool GetBit(const NTableClient::IUnversionedColumnarRowBatch::TBitmap& bitmap, int index)
+{
+ return GetBit(bitmap.Data, index);
+}
+
+inline void SetBit(TMutableRef data, int index, bool value)
+{
+ auto& byte = data[index / 8];
+ auto mask = 1ULL << (index % 8);
+ if (value) {
+ byte |= mask;
+ } else {
+ byte &= ~mask;
+ }
+}
+
+inline void ResolveRleEncoding(
+ const NTableClient::IUnversionedColumnarRowBatch::TColumn*& column,
+ i64& index)
+{
+ if (!column->Rle) {
+ return;
+ }
+
+ YT_ASSERT(column->Values->BitWidth == 64);
+ YT_ASSERT(!column->Values->ZigZagEncoded);
+ auto rleIndexes = GetTypedData<ui64>(*column->Values);
+ index = TranslateRleIndex(rleIndexes, index);
+ column = column->Rle->ValueColumn;
+}
+
+inline bool IsColumnValueNull(
+ const NTableClient::IUnversionedColumnarRowBatch::TColumn* column,
+ int index)
+{
+ return column->NullBitmap && GetBit(*column->NullBitmap, index);
+}
+
+inline bool ResolveDictionaryEncoding(
+ const NTableClient::IUnversionedColumnarRowBatch::TColumn*& column,
+ i64& index)
+{
+ if (!column->Dictionary) {
+ return true;
+ }
+
+ const auto& dictionary = *column->Dictionary;
+ YT_ASSERT(dictionary.ZeroMeansNull);
+ YT_ASSERT(column->Values->BitWidth == 32);
+ YT_ASSERT(!column->Values->ZigZagEncoded);
+ index = static_cast<i64>(GetTypedData<ui32>(*column->Values)[index]) - 1;
+ if (index < 0) {
+ return false;
+ }
+ column = column->Dictionary->ValueColumn;
+ return true;
+}
+
+inline TStringBuf DecodeStringFromColumn(
+ const NTableClient::IUnversionedColumnarRowBatch::TColumn& column,
+ i64 index)
+{
+ const auto& strings = *column.Strings;
+ YT_ASSERT(strings.AvgLength);
+ YT_ASSERT(column.Values->BitWidth == 32);
+ YT_ASSERT(column.Values->ZigZagEncoded);
+
+ auto getOffset = [&] (i64 index) {
+ return (index == 0)
+ ? 0
+ : *strings.AvgLength * index + ZigZagDecode64(GetTypedData<ui32>(*column.Values)[index - 1]);
+ };
+
+ i64 offset = getOffset(index);
+ i64 nextOffset = getOffset(index + 1);
+ return TStringBuf(strings.Data.Begin() + offset, strings.Data.Begin() + nextOffset);
+}
+
+template <class T>
+T DecodeIntegerFromColumn(
+ const NTableClient::IUnversionedColumnarRowBatch::TColumn& column,
+ i64 index)
+{
+ YT_ASSERT(column.Values->BitWidth == 64);
+ auto value = GetTypedData<ui64>(*column.Values)[index];
+ value += column.Values->BaseValue;
+ if (column.Values->ZigZagEncoded) {
+ value = static_cast<ui64>(ZigZagDecode64(value));
+ }
+ return static_cast<T>(value);
+}
+
+template <typename T>
+T DecodeFloatingPointFromColumn(
+ const NTableClient::IUnversionedColumnarRowBatch::TColumn& column,
+ i64 index)
+{
+ static_assert(std::is_floating_point_v<T>);
+
+ YT_ASSERT(column.Values->BitWidth == sizeof(T) * 8);
+ return GetTypedData<T>(*column.Values)[index];
+}
+
+inline bool DecodeBoolFromColumn(
+ const NTableClient::IUnversionedColumnarRowBatch::TColumn& column,
+ i64 index)
+{
+ YT_ASSERT(column.Values->BitWidth == 1);
+ return GetBit(*column.Values, index);
+}
+
+std::vector<TUnversionedRow> CreateFilteredRangedRows(
+ const std::vector<TUnversionedRow>& initial,
+ TNameTablePtr writeNameTable,
+ TNameTablePtr readNameTable,
+ TColumnFilter columnFilter,
+ NChunkClient::TLegacyReadRange readRange,
+ TChunkedMemoryPool* pool,
+ int keyColumnCount);
+
+void PrintTo(const TColumnarStatistics& statistics, std::ostream* os);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
diff --git a/yt/yt/client/table_client/unittests/helpers/ya.make b/yt/yt/client/table_client/unittests/helpers/ya.make
new file mode 100644
index 0000000000..8c5a0219f1
--- /dev/null
+++ b/yt/yt/client/table_client/unittests/helpers/ya.make
@@ -0,0 +1,14 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ helpers.cpp
+)
+
+PEERDIR(
+ yt/yt/client
+ yt/yt/core/test_framework
+)
+
+END()
diff --git a/yt/yt/client/table_client/unittests/serialization_ut.cpp b/yt/yt/client/table_client/unittests/serialization_ut.cpp
new file mode 100644
index 0000000000..ed1c64a113
--- /dev/null
+++ b/yt/yt/client/table_client/unittests/serialization_ut.cpp
@@ -0,0 +1,71 @@
+#include <yt/yt/client/formats/format.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/misc/blob_output.h>
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/test_framework/framework.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSchemaSerialization, ParseUsingNodeAndSerialize)
+{
+ const char* schemaString = "<strict=%true;unique_keys=%false>"
+ "[{name=a;required=%false;type=int64;};{deleted=%true;stable_name=b}]";
+ TTableSchema schema;
+ Deserialize(schema, NYTree::ConvertToNode(NYson::TYsonString(TString(schemaString))));
+
+ EXPECT_EQ(1, std::ssize(schema.Columns()));
+ EXPECT_EQ("a", schema.Columns()[0].Name());
+ EXPECT_EQ(1, std::ssize(schema.DeletedColumns()));
+ EXPECT_EQ("b", schema.DeletedColumns()[0].StableName().Get());
+
+ NYT::NFormats::TFormat format(NFormats::EFormatType::Json);
+
+ TBlobOutput buffer;
+ auto consumer = CreateConsumerForFormat(format, NFormats::EDataType::Structured, &buffer);
+
+ Serialize(schema, consumer.get());
+ consumer->Flush();
+ auto ref = buffer.Flush();
+ auto buf = ref.ToStringBuf();
+
+ EXPECT_EQ(TString(buf.data(), buf.size()),
+R"RR({"$attributes":{"strict":true,"unique_keys":false},"$value":[{"name":"a","required":false,"type":"int64","type_v3":{"type_name":"optional","item":"int64"}},{"stable_name":"b","deleted":true}]})RR");
+}
+
+TEST(TSchemaSerialization, Cursor)
+{
+ const char* schemaString = "<strict=%true;unique_keys=%false>"
+ "[{name=a;required=%false;type=int64;};{deleted=%true;stable_name=b}]";
+
+ TMemoryInput input(schemaString);
+ NYson::TYsonPullParser parser(&input, NYson::EYsonType::Node);
+ NYson::TYsonPullParserCursor cursor(&parser);
+
+ TTableSchema schema;
+ Deserialize(schema, &cursor);
+
+ EXPECT_EQ(1, std::ssize(schema.Columns()));
+ EXPECT_EQ("a", schema.Columns()[0].Name());
+ EXPECT_EQ(1, std::ssize(schema.DeletedColumns()));
+ EXPECT_EQ("b", schema.DeletedColumns()[0].StableName().Get());
+}
+
+TEST(TSchemaSerialization, Deleted)
+{
+ const char* schemaString = "<strict=%true;unique_keys=%false>"
+ "[{name=a;required=%false;type=int64;};{deleted=%true;name=b}]";
+
+ TTableSchema schema;
+ EXPECT_THROW_WITH_SUBSTRING(
+ Deserialize(schema, NYTree::ConvertToNode(NYson::TYsonString(TString(schemaString)))),
+ "stable name should be set for a deleted column");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unittests/ya.make b/yt/yt/client/table_client/unittests/ya.make
new file mode 100644
index 0000000000..87dff43ad2
--- /dev/null
+++ b/yt/yt/client/table_client/unittests/ya.make
@@ -0,0 +1,25 @@
+GTEST(unittester-client-table-client)
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+ALLOCATOR(YT)
+
+SRCS(
+ columnar_statistics_ut.cpp
+ columnar_ut.cpp
+ serialization_ut.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ yt/yt/client
+ yt/yt/client/formats
+ yt/yt/client/table_client/unittests/helpers
+ yt/yt/client/unittests/mock
+ yt/yt/core/test_framework
+)
+
+SIZE(MEDIUM)
+
+END()
diff --git a/yt/yt/client/table_client/unordered_schemaful_reader.cpp b/yt/yt/client/table_client/unordered_schemaful_reader.cpp
new file mode 100644
index 0000000000..56037eeac0
--- /dev/null
+++ b/yt/yt/client/table_client/unordered_schemaful_reader.cpp
@@ -0,0 +1,312 @@
+#include "unordered_schemaful_reader.h"
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/unversioned_reader.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/actions/cancelable_context.h>
+
+#include <library/cpp/yt/threading/spin_lock.h>
+
+namespace NYT::NTableClient {
+
+using namespace NConcurrency;
+using namespace NChunkClient::NProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// 1. Sequential prefetch
+// - 0
+// - 1
+// - all
+// 2. Unordered
+// - full concurrency and prefetch
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnorderedSchemafulReader
+ : public ISchemafulUnversionedReader
+{
+public:
+ TUnorderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader,
+ int concurrency)
+ : GetNextReader_(std::move(getNextReader))
+ {
+ Sessions_.reserve(concurrency);
+ for (int index = 0; index < concurrency; ++index) {
+ auto reader = GetNextReader_();
+ if (!reader) {
+ Exhausted_ = true;
+ break;
+ }
+ Sessions_.emplace_back(std::move(reader));
+ }
+ }
+
+ ~TUnorderedSchemafulReader()
+ {
+ CancelableContext_->Cancel(TError("Reader destroyed"));
+ }
+
+ IUnversionedRowBatchPtr Read(const TRowBatchReadOptions& options) override
+ {
+ bool hasPending = false;
+
+ for (auto& session : Sessions_) {
+ if (session.Exhausted) {
+ continue;
+ }
+
+ if (session.ReadyEvent) {
+ if (!session.ReadyEvent->IsSet()) {
+ hasPending = true;
+ continue;
+ }
+
+ const auto& error = session.ReadyEvent->Get();
+ if (!error.IsOK()) {
+ auto guard = WriterGuard(SpinLock_);
+ ReadyEvent_ = MakePromise<void>(error);
+ return CreateEmptyUnversionedRowBatch();
+ }
+
+ session.ReadyEvent->Reset();
+ }
+
+ // TODO(babenko): consider adjusting options w.r.t. concurrency.
+ auto batch = session.Reader->Read(options);
+ if (!batch) {
+ session.Exhausted = true;
+ if (RefillSession(session)) {
+ hasPending = true;
+ }
+ continue;
+ }
+
+ if (!batch->IsEmpty()) {
+ return batch;
+ }
+
+ YT_ASSERT(!session.ReadyEvent);
+ UpdateSession(session);
+ hasPending = true;
+ }
+
+ if (!hasPending) {
+ return nullptr;
+ }
+
+ auto readyEvent = NewPromise<void>();
+ {
+ auto guard = WriterGuard(SpinLock_);
+ ReadyEvent_ = readyEvent;
+ }
+
+ for (auto& session : Sessions_) {
+ if (session.ReadyEvent) {
+ readyEvent.TrySetFrom(*session.ReadyEvent);
+ }
+ }
+
+ readyEvent.OnCanceled(BIND(&TUnorderedSchemafulReader::OnCanceled, MakeWeak(this)));
+
+ return CreateEmptyUnversionedRowBatch();
+ }
+
+ TFuture<void> GetReadyEvent() const override
+ {
+ return DoGetReadyEvent();
+ }
+
+ TDataStatistics GetDataStatistics() const override
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ auto dataStatistics = DataStatistics_;
+ for (const auto& session : Sessions_) {
+ if (session.Reader) {
+ dataStatistics += session.Reader->GetDataStatistics();
+ }
+ }
+ return dataStatistics;
+ }
+
+ NChunkClient::TCodecStatistics GetDecompressionStatistics() const override
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ auto result = DecompressionStatistics_;
+ for (const auto& session : Sessions_) {
+ if (session.Reader) {
+ result += session.Reader->GetDecompressionStatistics();
+ }
+ }
+ return result;
+ }
+
+ bool IsFetchingCompleted() const override
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ for (const auto& session : Sessions_) {
+ if (session.Reader && !session.Reader->IsFetchingCompleted()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ std::vector<NChunkClient::TChunkId> GetFailedChunkIds() const override
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ auto result = FailedChunkIds_;
+ for (const auto& session : Sessions_) {
+ if (session.Reader) {
+ auto failedChunkIds = session.Reader->GetFailedChunkIds();
+ result.insert(result.end(), failedChunkIds.begin(), failedChunkIds.end());
+ }
+ }
+ return result;
+ }
+
+private:
+ const std::function<ISchemafulUnversionedReaderPtr()> GetNextReader_;
+
+ struct TSession
+ {
+ explicit TSession(ISchemafulUnversionedReaderPtr reader)
+ : Reader(std::move(reader))
+ { }
+
+ ISchemafulUnversionedReaderPtr Reader;
+ TFutureHolder<void> ReadyEvent;
+ bool Exhausted = false;
+ };
+
+ std::vector<TSession> Sessions_;
+ bool Exhausted_ = false;
+ TDataStatistics DataStatistics_;
+ NChunkClient::TCodecStatistics DecompressionStatistics_;
+ std::vector<NChunkClient::TChunkId> FailedChunkIds_;
+
+ TPromise<void> ReadyEvent_ = MakePromise<void>(TError());
+ const TCancelableContextPtr CancelableContext_ = New<TCancelableContext>();
+ YT_DECLARE_SPIN_LOCK(NThreading::TReaderWriterSpinLock, SpinLock_);
+
+ TPromise<void> DoGetReadyEvent() const
+ {
+ auto guard = ReaderGuard(SpinLock_);
+ return ReadyEvent_;
+ }
+
+ void UpdateSession(TSession& session)
+ {
+ session.ReadyEvent = session.Reader->GetReadyEvent();
+ session.ReadyEvent->Subscribe(BIND(&TUnorderedSchemafulReader::OnReady, MakeStrong(this)));
+ CancelableContext_->PropagateTo(*session.ReadyEvent);
+ }
+
+ bool RefillSession(TSession& session)
+ {
+ auto dataStatistics = session.Reader->GetDataStatistics();
+ auto cpuCompressionStatistics = session.Reader->GetDecompressionStatistics();
+ {
+ auto guard = WriterGuard(SpinLock_);
+ DataStatistics_ += dataStatistics;
+ DecompressionStatistics_ += cpuCompressionStatistics;
+ auto failedChunkIds = session.Reader->GetFailedChunkIds();
+ FailedChunkIds_.insert(FailedChunkIds_.end(), failedChunkIds.begin(), failedChunkIds.end());
+ session.Reader.Reset();
+ }
+
+ if (Exhausted_) {
+ return false;
+ }
+
+ auto reader = GetNextReader_();
+ if (!reader) {
+ Exhausted_ = true;
+ return false;
+ }
+
+ {
+ auto guard = WriterGuard(SpinLock_);
+ session.Exhausted = false;
+ session.Reader = std::move(reader);
+ }
+
+ UpdateSession(session);
+ return true;
+ }
+
+ void OnReady(const TError& value)
+ {
+ DoGetReadyEvent().TrySet(value);
+ }
+
+ void OnCanceled(const TError& error)
+ {
+ DoGetReadyEvent().TrySet(TError(NYT::EErrorCode::Canceled, "Table reader canceled")
+ << error);
+ CancelableContext_->Cancel(error);
+ }
+};
+
+ISchemafulUnversionedReaderPtr CreateUnorderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader,
+ int concurrency)
+{
+ return New<TUnorderedSchemafulReader>(
+ std::move(getNextReader),
+ concurrency);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemafulUnversionedReaderPtr CreateOrderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader)
+{
+ return CreateUnorderedSchemafulReader(getNextReader, 1);
+}
+
+ISchemafulUnversionedReaderPtr CreatePrefetchingOrderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader)
+{
+ auto nextReader = getNextReader();
+ auto readerGenerator = [
+ nextReader = std::move(nextReader),
+ getNextReader = std::move(getNextReader)
+ ] () mutable -> ISchemafulUnversionedReaderPtr {
+ auto currentReader = nextReader;
+ if (currentReader) {
+ nextReader = getNextReader();
+ }
+ return currentReader;
+ };
+
+ return CreateUnorderedSchemafulReader(readerGenerator, 1);
+}
+
+ISchemafulUnversionedReaderPtr CreateFullPrefetchingOrderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader)
+{
+ std::vector<ISchemafulUnversionedReaderPtr> readers;
+
+ while (auto nextReader = getNextReader()) {
+ readers.push_back(nextReader);
+ }
+
+ auto readerGenerator = [
+ index = 0,
+ readers = std::move(readers)
+ ] () mutable -> ISchemafulUnversionedReaderPtr {
+ if (index == std::ssize(readers)) {
+ return nullptr;
+ }
+ return readers[index++];
+ };
+
+ return CreateUnorderedSchemafulReader(readerGenerator, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unordered_schemaful_reader.h b/yt/yt/client/table_client/unordered_schemaful_reader.h
new file mode 100644
index 0000000000..4137a61ca7
--- /dev/null
+++ b/yt/yt/client/table_client/unordered_schemaful_reader.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "public.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemafulUnversionedReaderPtr CreateUnorderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader,
+ int concurrency);
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemafulUnversionedReaderPtr CreateOrderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader);
+
+ISchemafulUnversionedReaderPtr CreatePrefetchingOrderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader);
+
+ISchemafulUnversionedReaderPtr CreateFullPrefetchingOrderedSchemafulReader(
+ std::function<ISchemafulUnversionedReaderPtr()> getNextReader);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unversioned_reader.h b/yt/yt/client/table_client/unversioned_reader.h
new file mode 100644
index 0000000000..ec15a766b9
--- /dev/null
+++ b/yt/yt/client/table_client/unversioned_reader.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+
+#include <yt/yt/client/chunk_client/reader_base.h>
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IUnversionedReaderBase
+ : public virtual NChunkClient::IReaderBase
+{
+ //! NB: The returned batch can be invalidated by the next call to `Read`.
+ virtual IUnversionedRowBatchPtr Read(const TRowBatchReadOptions& options = {}) = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ISchemafulUnversionedReader
+ : public IUnversionedReaderBase
+{ };
+
+DEFINE_REFCOUNTED_TYPE(ISchemafulUnversionedReader)
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ISchemalessUnversionedReader
+ : public IUnversionedReaderBase
+{
+ virtual const TNameTablePtr& GetNameTable() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ISchemalessUnversionedReader)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unversioned_row.cpp b/yt/yt/client/table_client/unversioned_row.cpp
new file mode 100644
index 0000000000..07062c2690
--- /dev/null
+++ b/yt/yt/client/table_client/unversioned_row.cpp
@@ -0,0 +1,2136 @@
+#include "unversioned_row.h"
+
+#include "composite_compare.h"
+#include "helpers.h"
+#include "serialize.h"
+#include "unversioned_value.h"
+#include "validate_logical_type.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/library/decimal/decimal.h>
+
+#include <yt/yt/core/misc/range.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/helpers.h>
+#include <yt/yt/core/ytree/node.h>
+#include <yt/yt/core/ytree/convert.h>
+
+#include <library/cpp/yt/misc/hash.h>
+
+#include <library/cpp/yt/farmhash/farm_hash.h>
+
+#include <library/cpp/yt/coding/varint.h>
+
+#include <util/generic/ymath.h>
+
+#include <util/charset/utf8.h>
+#include <util/stream/str.h>
+
+#include <cmath>
+
+namespace NYT::NTableClient {
+
+using namespace NChunkClient;
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString SerializedNullRow;
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t EstimateRowValueSize(const TUnversionedValue& value, bool isInlineHunkValue)
+{
+ size_t result = MaxVarUint32Size * 2; // id and type
+
+ switch (value.Type) {
+ case EValueType::Null:
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+
+ case EValueType::Int64:
+ case EValueType::Uint64:
+ result += MaxVarInt64Size;
+ break;
+
+ case EValueType::Double:
+ result += sizeof(double);
+ break;
+
+ case EValueType::Boolean:
+ result += 1;
+ break;
+
+ case EValueType::String:
+ case EValueType::Any:
+ case EValueType::Composite:
+ if (isInlineHunkValue) {
+ result += 1; // hunk value tag
+ }
+ result += MaxVarUint32Size + value.Length;
+ break;
+ }
+
+ return result;
+}
+
+size_t WriteRowValue(char* output, const TUnversionedValue& value, bool isInlineHunkValue)
+{
+ char* current = output;
+
+ current += WriteVarUint32(current, value.Id);
+ current += WriteVarUint32(current, static_cast<ui16>(value.Type));
+
+ switch (value.Type) {
+ case EValueType::Null:
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+
+ case EValueType::Int64:
+ current += WriteVarInt64(current, value.Data.Int64);
+ break;
+
+ case EValueType::Uint64:
+ current += WriteVarUint64(current, value.Data.Uint64);
+ break;
+
+ case EValueType::Double:
+ ::memcpy(current, &value.Data.Double, sizeof (double));
+ current += sizeof (double);
+ break;
+
+ case EValueType::Boolean:
+ *current++ = value.Data.Boolean ? '\x01' : '\x00';
+ break;
+
+ case EValueType::String:
+ case EValueType::Any:
+ case EValueType::Composite:
+ current += WriteVarUint32(current, value.Length + (isInlineHunkValue ? 1 : 0));
+ if (isInlineHunkValue) {
+ *current++ = static_cast<char>(EHunkValueTag::Inline);
+ }
+ ::memcpy(current, value.Data.String, value.Length);
+ current += value.Length;
+ break;
+
+ default:
+ YT_ABORT();
+ }
+
+ return current - output;
+}
+
+size_t ReadRowValue(const char* input, TUnversionedValue* value)
+{
+ const char* current = input;
+
+ ui32 id;
+ current += ReadVarUint32(current, &id);
+
+ ui32 typeValue;
+ current += ReadVarUint32(current, &typeValue);
+ auto type = static_cast<EValueType>(typeValue);
+
+ switch (type) {
+ case EValueType::Null:
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ *value = MakeUnversionedSentinelValue(type, id);
+ break;
+
+ case EValueType::Int64: {
+ i64 data;
+ current += ReadVarInt64(current, &data);
+ *value = MakeUnversionedInt64Value(data, id);
+ break;
+ }
+
+ case EValueType::Uint64: {
+ ui64 data;
+ current += ReadVarUint64(current, &data);
+ *value = MakeUnversionedUint64Value(data, id);
+ break;
+ }
+
+ case EValueType::Double: {
+ double data;
+ ::memcpy(&data, current, sizeof (double));
+ current += sizeof (double);
+ *value = MakeUnversionedDoubleValue(data, id);
+ break;
+ }
+
+ case EValueType::Boolean: {
+ bool data = (*current) == 1;
+ current += 1;
+ *value = MakeUnversionedBooleanValue(data, id);
+ break;
+ }
+
+ case EValueType::Any:
+ case EValueType::Composite:
+ case EValueType::String: {
+ ui32 length;
+ current += ReadVarUint32(current, &length);
+ TStringBuf data(current, current + length);
+ current += length;
+ *value = MakeUnversionedStringLikeValue(type, data, id);
+ break;
+ }
+
+ default:
+ ThrowUnexpectedValueType(type);
+ }
+
+ return current - input;
+}
+
+void Save(TStreamSaveContext& context, const TUnversionedValue& value)
+{
+ auto* output = context.GetOutput();
+ if (IsStringLikeType(value.Type)) {
+ output->Write(&value, sizeof (ui16) + sizeof (ui16) + sizeof (ui32)); // Id, Type, Length
+ if (value.Length != 0) {
+ output->Write(value.Data.String, value.Length);
+ }
+ } else {
+ output->Write(&value, sizeof (TUnversionedValue));
+ }
+}
+
+void Load(TStreamLoadContext& context, TUnversionedValue& value, TChunkedMemoryPool* pool)
+{
+ auto* input = context.GetInput();
+ const size_t fixedSize = sizeof (ui16) + sizeof (ui16) + sizeof (ui32); // Id, Type, Length
+ YT_VERIFY(input->Load(&value, fixedSize) == fixedSize);
+ if (IsStringLikeType(value.Type)) {
+ if (value.Length != 0) {
+ value.Data.String = pool->AllocateUnaligned(value.Length);
+ YT_VERIFY(input->Load(const_cast<char*>(value.Data.String), value.Length) == value.Length);
+ } else {
+ value.Data.String = nullptr;
+ }
+ } else {
+ YT_VERIFY(input->Load(&value.Data, sizeof (value.Data)) == sizeof (value.Data));
+ }
+}
+
+size_t GetYsonSize(const TUnversionedValue& value)
+{
+ switch (value.Type) {
+ case EValueType::Any:
+ case EValueType::Composite:
+ return value.Length;
+
+ case EValueType::Null:
+ // Marker type.
+ return 1;
+
+ case EValueType::Int64:
+ case EValueType::Uint64:
+ // Type marker + size;
+ return 1 + MaxVarInt64Size;
+
+ case EValueType::Double:
+ // Type marker + sizeof double.
+ return 1 + 8;
+
+ case EValueType::String:
+ // Type marker + length + string bytes.
+ return 1 + MaxVarInt32Size + value.Length;
+
+ case EValueType::Boolean:
+ // Type marker + value.
+ return 1 + 1;
+
+ default:
+ YT_ABORT();
+ }
+}
+
+size_t WriteYson(char* buffer, const TUnversionedValue& unversionedValue)
+{
+ // TODO(psushin): get rid of output stream.
+ TMemoryOutput output(buffer, GetYsonSize(unversionedValue));
+ TYsonWriter writer(&output, EYsonFormat::Binary);
+ switch (unversionedValue.Type) {
+ case EValueType::Int64:
+ writer.OnInt64Scalar(unversionedValue.Data.Int64);
+ break;
+ case EValueType::Uint64:
+ writer.OnUint64Scalar(unversionedValue.Data.Uint64);
+ break;
+
+ case EValueType::Double:
+ writer.OnDoubleScalar(unversionedValue.Data.Double);
+ break;
+
+ case EValueType::String:
+ writer.OnStringScalar(unversionedValue.AsStringBuf());
+ break;
+
+ case EValueType::Boolean:
+ writer.OnBooleanScalar(unversionedValue.Data.Boolean);
+ break;
+
+ case EValueType::Null:
+ writer.OnEntity();
+ break;
+
+ default:
+ YT_ABORT();
+ }
+ return output.Buf() - buffer;
+}
+
+namespace {
+
+[[noreturn]] void ThrowIncomparableTypes(const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::IncomparableTypes,
+ "Cannot compare values of types %Qlv and %Qlv; only scalar types are allowed for key columns",
+ lhs.Type,
+ rhs.Type)
+ << TErrorAttribute("lhs_value", lhs)
+ << TErrorAttribute("rhs_value", rhs);
+}
+
+} // namespace
+
+Y_FORCE_INLINE bool IsSentinel(EValueType valueType)
+{
+ return valueType == EValueType::Min || valueType == EValueType::Max;
+}
+
+int CompareRowValues(const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ // TODO(babenko): check flags; forbid comparing hunks and aggregates.
+
+ if (lhs.Type == EValueType::Any || rhs.Type == EValueType::Any) {
+ if (!IsSentinel(lhs.Type) && !IsSentinel(rhs.Type)) {
+ // Never compare composite values with non-sentinels.
+ ThrowIncomparableTypes(lhs, rhs);
+ }
+ }
+
+ if (lhs.Type == EValueType::Composite || rhs.Type == EValueType::Composite) {
+ if (lhs.Type != rhs.Type) {
+ if (!IsSentinel(lhs.Type) && lhs.Type != EValueType::Null &&
+ !IsSentinel(rhs.Type) && rhs.Type != EValueType::Null)
+ {
+ ThrowIncomparableTypes(lhs, rhs);
+ }
+ return static_cast<int>(lhs.Type) - static_cast<int>(rhs.Type);
+ }
+ try {
+ auto lhsData = TYsonStringBuf(lhs.AsStringBuf());
+ auto rhsData = TYsonStringBuf(rhs.AsStringBuf());
+ return CompareCompositeValues(lhsData, rhsData);
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::IncomparableComplexValues,
+ "Cannot compare complex values")
+ << TErrorAttribute("lhs_value", lhs)
+ << TErrorAttribute("rhs_value", rhs)
+ << ex;
+ }
+ }
+
+ if (Y_UNLIKELY(lhs.Type != rhs.Type)) {
+ return static_cast<int>(lhs.Type) - static_cast<int>(rhs.Type);
+ }
+
+ switch (lhs.Type) {
+ case EValueType::Int64: {
+ auto lhsValue = lhs.Data.Int64;
+ auto rhsValue = rhs.Data.Int64;
+ if (lhsValue < rhsValue) {
+ return -1;
+ } else if (lhsValue > rhsValue) {
+ return +1;
+ } else {
+ return 0;
+ }
+ }
+
+ case EValueType::Uint64: {
+ auto lhsValue = lhs.Data.Uint64;
+ auto rhsValue = rhs.Data.Uint64;
+ if (lhsValue < rhsValue) {
+ return -1;
+ } else if (lhsValue > rhsValue) {
+ return +1;
+ } else {
+ return 0;
+ }
+ }
+
+ case EValueType::Double: {
+ double lhsValue = lhs.Data.Double;
+ double rhsValue = rhs.Data.Double;
+ if (lhsValue < rhsValue) {
+ return -1;
+ } else if (lhsValue > rhsValue) {
+ return +1;
+ } else if (std::isnan(lhsValue)) {
+ if (std::isnan(rhsValue)) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if (std::isnan(rhsValue)) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ case EValueType::Boolean: {
+ bool lhsValue = lhs.Data.Boolean;
+ bool rhsValue = rhs.Data.Boolean;
+ if (lhsValue < rhsValue) {
+ return -1;
+ } else if (lhsValue > rhsValue) {
+ return +1;
+ } else {
+ return 0;
+ }
+ }
+
+ case EValueType::String: {
+ size_t lhsLength = lhs.Length;
+ size_t rhsLength = rhs.Length;
+ size_t minLength = std::min(lhsLength, rhsLength);
+ int result = ::memcmp(lhs.Data.String, rhs.Data.String, minLength);
+ if (result == 0) {
+ if (lhsLength < rhsLength) {
+ return -1;
+ } else if (lhsLength > rhsLength) {
+ return +1;
+ } else {
+ return 0;
+ }
+ } else {
+ return result;
+ }
+ }
+
+ // All sentinel types are equal.
+ default:
+ return 0;
+ }
+}
+
+bool operator == (const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ return CompareRowValues(lhs, rhs) == 0;
+}
+
+bool operator != (const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ return CompareRowValues(lhs, rhs) != 0;
+}
+
+bool operator <= (const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ return CompareRowValues(lhs, rhs) <= 0;
+}
+
+bool operator < (const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ return CompareRowValues(lhs, rhs) < 0;
+}
+
+bool operator >= (const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ return CompareRowValues(lhs, rhs) >= 0;
+}
+
+bool operator > (const TUnversionedValue& lhs, const TUnversionedValue& rhs)
+{
+ return CompareRowValues(lhs, rhs) > 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int CompareValueRanges(TUnversionedValueRange lhs, TUnversionedValueRange rhs)
+{
+ auto* lhsCurrent = lhs.begin();
+ auto* rhsCurrent = rhs.begin();
+ while (lhsCurrent != lhs.end() && rhsCurrent != rhs.end()) {
+ int result = CompareRowValues(*lhsCurrent++, *rhsCurrent++);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return std::ssize(lhs) - std::ssize(rhs);
+}
+
+int CompareRows(TUnversionedRow lhs, TUnversionedRow rhs, ui32 prefixLength)
+{
+ if (!lhs && !rhs) {
+ return 0;
+ }
+
+ if (lhs && !rhs) {
+ return +1;
+ }
+
+ if (!lhs && rhs) {
+ return -1;
+ }
+
+ return CompareValueRanges(
+ lhs.FirstNElements(std::min(lhs.GetCount(), prefixLength)),
+ rhs.FirstNElements(std::min(rhs.GetCount(), prefixLength)));
+}
+
+bool operator == (TUnversionedRow lhs, TUnversionedRow rhs)
+{
+ return CompareRows(lhs, rhs) == 0;
+}
+
+bool operator != (TUnversionedRow lhs, TUnversionedRow rhs)
+{
+ return CompareRows(lhs, rhs) != 0;
+}
+
+bool operator <= (TUnversionedRow lhs, TUnversionedRow rhs)
+{
+ return CompareRows(lhs, rhs) <= 0;
+}
+
+bool operator < (TUnversionedRow lhs, TUnversionedRow rhs)
+{
+ return CompareRows(lhs, rhs) < 0;
+}
+
+bool operator >= (TUnversionedRow lhs, TUnversionedRow rhs)
+{
+ return CompareRows(lhs, rhs) >= 0;
+}
+
+bool operator > (TUnversionedRow lhs, TUnversionedRow rhs)
+{
+ return CompareRows(lhs, rhs) > 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (TUnversionedRow lhs, const TUnversionedOwningRow& rhs)
+{
+ return CompareRows(lhs, rhs) == 0;
+}
+
+bool operator != (TUnversionedRow lhs, const TUnversionedOwningRow& rhs)
+{
+ return CompareRows(lhs, rhs) != 0;
+}
+
+bool operator <= (TUnversionedRow lhs, const TUnversionedOwningRow& rhs)
+{
+ return CompareRows(lhs, rhs) <= 0;
+}
+
+bool operator < (TUnversionedRow lhs, const TUnversionedOwningRow& rhs)
+{
+ return CompareRows(lhs, rhs) < 0;
+}
+
+bool operator >= (TUnversionedRow lhs, const TUnversionedOwningRow& rhs)
+{
+ return CompareRows(lhs, rhs) >= 0;
+}
+
+bool operator > (TUnversionedRow lhs, const TUnversionedOwningRow& rhs)
+{
+ return CompareRows(lhs, rhs) > 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Needed by NYT::FarmFingerprint below.
+// TODO(babenko): consider refactoring.
+TFingerprint FarmFingerprint(const TUnversionedValue& value)
+{
+ return GetFarmFingerprint(value);
+}
+
+TFingerprint GetFarmFingerprint(TUnversionedValueRange range)
+{
+ return NYT::FarmFingerprint<TUnversionedValue>(range.begin(), range.end());
+}
+
+TFingerprint GetFarmFingerprint(TUnversionedRow row)
+{
+ return GetFarmFingerprint(row.Elements());
+}
+
+size_t GetUnversionedRowByteSize(ui32 valueCount)
+{
+ return sizeof(TUnversionedRowHeader) + sizeof(TUnversionedValue) * valueCount;
+}
+
+size_t GetDataWeight(TUnversionedRow row)
+{
+ if (!row) {
+ return 0;
+ }
+
+ size_t result = 1;
+ for (const auto& value : row) {
+ result += GetDataWeight(value);
+ }
+ return result;
+}
+
+size_t GetDataWeight(TRange<TUnversionedRow> rows)
+{
+ size_t result = 0;
+ for (auto row : rows) {
+ result += GetDataWeight(row);
+ }
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TMutableUnversionedRow TMutableUnversionedRow::Allocate(TChunkedMemoryPool* pool, ui32 valueCount)
+{
+ size_t byteSize = GetUnversionedRowByteSize(valueCount);
+ return Create(pool->AllocateAligned(byteSize), valueCount);
+}
+
+TMutableUnversionedRow TMutableUnversionedRow::Create(void* buffer, ui32 valueCount)
+{
+ auto* header = reinterpret_cast<TUnversionedRowHeader*>(buffer);
+ header->Count = valueCount;
+ header->Capacity = valueCount;
+ return TMutableUnversionedRow(header);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+class TYsonAnyValidator
+ : public IYsonConsumer
+{
+public:
+ void OnStringScalar(TStringBuf /*value*/) override
+ { }
+
+ void OnInt64Scalar(i64 /*value*/) override
+ { }
+
+ void OnUint64Scalar(ui64 /*value*/) override
+ { }
+
+ void OnDoubleScalar(double /*value*/) override
+ { }
+
+ void OnBooleanScalar(bool /*value*/) override
+ { }
+
+ void OnEntity() override
+ { }
+
+ void OnBeginList() override
+ {
+ ++Depth_;
+ }
+
+ void OnListItem() override
+ { }
+
+ void OnEndList() override
+ {
+ --Depth_;
+ }
+
+ void OnBeginMap() override
+ {
+ ++Depth_;
+ }
+
+ void OnKeyedItem(TStringBuf /*key*/) override
+ { }
+
+ void OnEndMap() override
+ {
+ --Depth_;
+ }
+
+ void OnBeginAttributes() override
+ {
+ if (Depth_ == 0) {
+ THROW_ERROR_EXCEPTION("Table values cannot have top-level attributes");
+ }
+ }
+
+ void OnEndAttributes() override
+ { }
+
+ void OnRaw(TStringBuf /*yson*/, EYsonType /*type*/) override
+ { }
+
+private:
+ int Depth_ = 0;
+};
+
+void ValidateAnyValue(TStringBuf yson)
+{
+ TYsonAnyValidator validator;
+ ParseYsonStringBuffer(yson, EYsonType::Node, &validator);
+}
+
+void ValidateDynamicValue(const TUnversionedValue& value, bool isKey)
+{
+ switch (value.Type) {
+ case EValueType::String:
+ if (value.Length > MaxStringValueLength) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::StringLikeValueLengthLimitExceeded,
+ "Value of type %Qlv is too long for dynamic data: length %v, limit %v",
+ value.Type,
+ value.Length,
+ MaxStringValueLength);
+ }
+ break;
+
+ case EValueType::Any:
+ if (value.Length > MaxAnyValueLength) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::StringLikeValueLengthLimitExceeded,
+ "Value of type %Qlv is too long for dynamic data: length %v, limit %v",
+ value.Type,
+ value.Length,
+ MaxAnyValueLength);
+ }
+ ValidateAnyValue(value.AsStringBuf());
+ break;
+
+ case EValueType::Double:
+ if (isKey && std::isnan(value.Data.Double)) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::KeyCannotBeNan,
+ "Key of type \"double\" cannot be NaN");
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void ValidateClientRow(
+ TUnversionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ const TNameTablePtr& nameTable,
+ bool isKey,
+ bool allowMissingKeyColumns = false,
+ std::optional<int> tabletIndexColumnId = std::nullopt)
+{
+ if (!row) {
+ THROW_ERROR_EXCEPTION("Row cannot be null");
+ }
+
+ ValidateRowValueCount(row.GetCount());
+ ValidateKeyColumnCount(schema.GetKeyColumnCount());
+
+ bool keyColumnSeen[MaxKeyColumnCount]{};
+ bool haveDataColumns = false;
+
+ for (const auto& value : row) {
+ int mappedId = ApplyIdMapping(value, &idMapping);
+ if (mappedId < 0 || mappedId >= std::ssize(schema.Columns())) {
+ int size = nameTable->GetSize();
+ if (value.Id < 0 || value.Id >= size) {
+ THROW_ERROR_EXCEPTION("Expected value id in range [0:%v] but got %v",
+ size - 1,
+ value.Id);
+ }
+
+ THROW_ERROR_EXCEPTION("Unexpected column %Qv", nameTable->GetName(value.Id));
+ }
+
+ const auto& column = schema.Columns()[mappedId];
+ ValidateValueType(value, schema, mappedId, /*typeAnyAcceptsAllValues*/false);
+
+ if (Any(value.Flags & EValueFlags::Aggregate) && !column.Aggregate()) {
+ THROW_ERROR_EXCEPTION(
+ "\"aggregate\" flag is set for value in column %v which is not aggregating",
+ column.GetDiagnosticNameString());
+ }
+
+ if (mappedId < schema.GetKeyColumnCount()) {
+ if (keyColumnSeen[mappedId]) {
+ THROW_ERROR_EXCEPTION("Duplicate key column %v",
+ column.GetDiagnosticNameString());
+ }
+ keyColumnSeen[mappedId] = true;
+ ValidateKeyValue(value);
+ } else if (isKey) {
+ THROW_ERROR_EXCEPTION("Non-key column %v in a key",
+ column.GetDiagnosticNameString());
+ } else {
+ haveDataColumns = true;
+ ValidateDataValue(value);
+ }
+ }
+
+ if (!isKey && !haveDataColumns) {
+ THROW_ERROR_EXCEPTION("At least one non-key column must be specified");
+ }
+
+ if (tabletIndexColumnId) {
+ YT_VERIFY(std::ssize(idMapping) > *tabletIndexColumnId);
+ auto mappedId = idMapping[*tabletIndexColumnId];
+ YT_VERIFY(mappedId >= 0);
+ keyColumnSeen[mappedId] = true;
+ }
+
+ if (!allowMissingKeyColumns) {
+ for (int index = 0; index < schema.GetKeyColumnCount(); ++index) {
+ if (!keyColumnSeen[index] && !schema.Columns()[index].Expression()) {
+ THROW_ERROR_EXCEPTION("Missing key column %v",
+ schema.Columns()[index].GetDiagnosticNameString());
+ }
+ }
+ }
+
+ auto dataWeight = GetDataWeight(row);
+ if (dataWeight >= MaxClientVersionedRowDataWeight) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::RowWeightLimitExceeded,
+ "Row is too large: data weight %v, limit %v",
+ dataWeight,
+ MaxClientVersionedRowDataWeight);
+ }
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString SerializeToString(TUnversionedRow row)
+{
+ return row
+ ? SerializeToString(row.Elements())
+ : SerializedNullRow;
+}
+
+TString SerializeToString(TUnversionedValueRange range)
+{
+ int size = 2 * MaxVarUint32Size; // header size
+ for (const auto& value : range) {
+ size += EstimateRowValueSize(value);
+ }
+
+ TString buffer;
+ buffer.resize(size);
+
+ char* current = const_cast<char*>(buffer.data());
+ current += WriteVarUint32(current, 0); // format version
+ current += WriteVarUint32(current, range.size());
+
+ for (const auto& value : range) {
+ current += WriteRowValue(current, value);
+ }
+
+ buffer.resize(current - buffer.data());
+
+ return buffer;
+}
+
+TUnversionedOwningRow DeserializeFromString(TString&& data, std::optional<int> nullPaddingWidth = std::nullopt)
+{
+ if (data == SerializedNullRow) {
+ return TUnversionedOwningRow();
+ }
+ auto dataRef = TSharedRef::FromString(std::move(data));
+
+ const char* current = dataRef.begin();
+
+ ui32 version;
+ current += ReadVarUint32(current, &version);
+ YT_VERIFY(version == 0);
+
+ ui32 valueCount;
+ current += ReadVarUint32(current, &valueCount);
+
+ // TODO(max42): YT-14049.
+ int nullCount = nullPaddingWidth ? std::max<int>(0, *nullPaddingWidth - static_cast<int>(valueCount)) : 0;
+
+ size_t fixedSize = GetUnversionedRowByteSize(valueCount + nullCount);
+ auto rowData = TSharedMutableRef::Allocate<TOwningRowTag>(fixedSize, {.InitializeStorage = false});
+ auto* header = reinterpret_cast<TUnversionedRowHeader*>(rowData.Begin());
+
+ header->Count = static_cast<i32>(valueCount + nullCount);
+ header->Capacity = static_cast<i32>(valueCount + nullCount);
+
+ auto* values = reinterpret_cast<TUnversionedValue*>(header + 1);
+ for (int index = 0; index < static_cast<int>(valueCount); ++index) {
+ auto* value = values + index;
+ current += ReadRowValue(current, value);
+ }
+ for (int index = valueCount; index < static_cast<int>(valueCount + nullCount); ++index) {
+ values[index] = MakeUnversionedNullValue(index);
+ }
+
+ return TUnversionedOwningRow(std::move(rowData), std::move(dataRef));
+}
+
+TUnversionedRow DeserializeFromString(const TString& data, const TRowBufferPtr& rowBuffer)
+{
+ if (data == SerializedNullRow) {
+ return TUnversionedRow();
+ }
+
+ const char* current = data.data();
+
+ ui32 version;
+ current += ReadVarUint32(current, &version);
+ YT_VERIFY(version == 0);
+
+ ui32 valueCount;
+ current += ReadVarUint32(current, &valueCount);
+
+ auto row = rowBuffer->AllocateUnversioned(valueCount);
+
+ auto* values = row.begin();
+ for (int index = 0; index < static_cast<int>(valueCount); ++index) {
+ auto* value = values + index;
+ current += ReadRowValue(current, value);
+ rowBuffer->CaptureValue(value);
+ }
+
+ return row;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TUnversionedRow::Save(TSaveContext& context) const
+{
+ NYT::Save(context, SerializeToString(*this));
+}
+
+void TUnversionedRow::Load(TLoadContext& context)
+{
+ *this = DeserializeFromString(NYT::Load<TString>(context), context.GetRowBuffer());
+}
+
+void ValidateValueType(
+ const TUnversionedValue& value,
+ const TTableSchema& schema,
+ int schemaId,
+ bool typeAnyAcceptsAllValues,
+ bool ignoreRequired,
+ bool validateAnyIsValidYson)
+{
+ ValidateValueType(
+ value,
+ schema.Columns()[schemaId],
+ typeAnyAcceptsAllValues,
+ ignoreRequired,
+ validateAnyIsValidYson);
+}
+
+[[noreturn]] static void ThrowInvalidColumnType(EValueType expected, EValueType actual)
+{
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::SchemaViolation,
+ "Invalid type: expected %Qlv, actual %Qlv",
+ expected,
+ actual);
+}
+
+static inline void ValidateColumnType(EValueType expected, const TUnversionedValue& value)
+{
+ if (value.Type != expected) {
+ ThrowInvalidColumnType(expected, value.Type);
+ }
+}
+
+template <ESimpleLogicalValueType logicalType>
+Y_FORCE_INLINE auto GetValue(const TUnversionedValue& value)
+{
+ constexpr auto physicalType = GetPhysicalType(logicalType);
+ ValidateColumnType(physicalType, value);
+ if constexpr (physicalType == EValueType::Int64) {
+ return value.Data.Int64;
+ } else if constexpr (physicalType == EValueType::Uint64) {
+ return value.Data.Uint64;
+ } else if constexpr (physicalType == EValueType::Double) {
+ return value.Data.Double;
+ } else if constexpr (physicalType == EValueType::Boolean) {
+ return value.Data.Boolean;
+ } else {
+ static_assert(physicalType == EValueType::String || physicalType == EValueType::Any);
+ return value.AsStringBuf();
+ }
+}
+
+static const TLogicalTypePtr& UnwrapTaggedAndOptional(const TLogicalTypePtr& type)
+{
+ const TLogicalTypePtr* current = &type;
+ while ((*current)->GetMetatype() == ELogicalMetatype::Tagged) {
+ current = &(*current)->UncheckedAsTaggedTypeRef().GetElement();
+ }
+
+ if ((*current)->GetMetatype() != ELogicalMetatype::Optional) {
+ return *current;
+ }
+
+ const auto& optionalType = (*current)->UncheckedAsOptionalTypeRef();
+ if (optionalType.IsElementNullable()) {
+ return *current;
+ }
+
+ current = &optionalType.GetElement();
+
+ while ((*current)->GetMetatype() == ELogicalMetatype::Tagged) {
+ current = &(*current)->UncheckedAsTaggedTypeRef().GetElement();
+ }
+
+ return *current;
+}
+
+void ValidateValueType(
+ const TUnversionedValue& value,
+ const TColumnSchema& columnSchema,
+ bool typeAnyAcceptsAllValues,
+ bool ignoreRequired,
+ bool validateAnyIsValidYson)
+{
+ if (value.Type == EValueType::Null) {
+ if (columnSchema.Required()) {
+ if (ignoreRequired) {
+ return;
+ }
+
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::SchemaViolation,
+ "Required column %v cannot have %Qlv value",
+ columnSchema.GetDiagnosticNameString(),
+ value.Type);
+ } else {
+ return;
+ }
+ }
+
+ try {
+ auto v1Type = columnSchema.CastToV1Type();
+ switch (v1Type) {
+ case ESimpleLogicalValueType::Null:
+ case ESimpleLogicalValueType::Void:
+ // this case should be handled before
+ ValidateColumnType(EValueType::Null, value);
+ return;
+ case ESimpleLogicalValueType::Any:
+ if (columnSchema.IsOfV1Type()) {
+ if (!typeAnyAcceptsAllValues) {
+ ValidateColumnType(EValueType::Any, value);
+ } else if (!IsAnyColumnCompatibleType(value.Type)) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::SchemaViolation,
+ "Cannot write value of type %Qlv into type any column",
+ value.Type);
+ }
+ if (IsAnyOrComposite(value.Type) && validateAnyIsValidYson) {
+ ValidateAnyValue(value.AsStringBuf());
+ }
+ } else {
+ ValidateColumnType(EValueType::Composite, value);
+ ValidateComplexLogicalType(value.AsStringBuf(), columnSchema.LogicalType());
+ }
+ return;
+ case ESimpleLogicalValueType::String:
+ if (columnSchema.IsOfV1Type()) {
+ ValidateSimpleLogicalType<ESimpleLogicalValueType::String>(GetValue<ESimpleLogicalValueType::String>(value));
+ } else {
+ ValidateColumnType(EValueType::String, value);
+ auto type = UnwrapTaggedAndOptional(columnSchema.LogicalType());
+ YT_VERIFY(type->GetMetatype() == ELogicalMetatype::Decimal);
+ NDecimal::TDecimal::ValidateBinaryValue(
+ value.AsStringBuf(),
+ type->UncheckedAsDecimalTypeRef().GetPrecision(),
+ type->UncheckedAsDecimalTypeRef().GetScale());
+ }
+ return;
+#define CASE(x) \
+ case x: \
+ ValidateSimpleLogicalType<x>(GetValue<x>(value)); \
+ return;
+
+ CASE(ESimpleLogicalValueType::Int64)
+ CASE(ESimpleLogicalValueType::Uint64)
+ CASE(ESimpleLogicalValueType::Double)
+ CASE(ESimpleLogicalValueType::Boolean)
+
+ CASE(ESimpleLogicalValueType::Float)
+
+ CASE(ESimpleLogicalValueType::Int8)
+ CASE(ESimpleLogicalValueType::Int16)
+ CASE(ESimpleLogicalValueType::Int32)
+
+ CASE(ESimpleLogicalValueType::Uint8)
+ CASE(ESimpleLogicalValueType::Uint16)
+ CASE(ESimpleLogicalValueType::Uint32)
+
+ CASE(ESimpleLogicalValueType::Utf8)
+ CASE(ESimpleLogicalValueType::Date)
+ CASE(ESimpleLogicalValueType::Datetime)
+ CASE(ESimpleLogicalValueType::Timestamp)
+ CASE(ESimpleLogicalValueType::Interval)
+ CASE(ESimpleLogicalValueType::Json)
+ CASE(ESimpleLogicalValueType::Uuid)
+#undef CASE
+ }
+ YT_ABORT();
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::SchemaViolation,
+ "Error validating column %v",
+ columnSchema.GetDiagnosticNameString())
+ << ex;
+ }
+}
+
+void ValidateStaticValue(const TUnversionedValue& value)
+{
+ ValidateDataValueType(value.Type);
+ if (IsStringLikeType(value.Type)) {
+ if (value.Length > MaxRowWeightLimit) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::StringLikeValueLengthLimitExceeded,
+ "Value of type %Qlv is too long for static data: length %v, limit %v",
+ value.Type,
+ value.Length,
+ MaxRowWeightLimit);
+ }
+ }
+}
+
+void ValidateDataValue(const TUnversionedValue& value)
+{
+ if (auto remainingFlags = value.Flags & ~EValueFlags::Aggregate; Any(remainingFlags)) {
+ THROW_ERROR_EXCEPTION(
+ "Value has unsupported flag(s) %Qlv",
+ remainingFlags);
+ }
+ ValidateDataValueType(value.Type);
+ ValidateDynamicValue(value, /*isKey*/ false);
+}
+
+void ValidateKeyValue(const TUnversionedValue& value)
+{
+ if (value.Flags != EValueFlags::None) {
+ THROW_ERROR_EXCEPTION(
+ "Key value has unsupported flag(s) %Qlv",
+ value.Flags);
+ }
+ ValidateKeyValueType(value.Type);
+ ValidateDynamicValue(value, /*isKey*/ true);
+}
+
+void ValidateRowValueCount(int count)
+{
+ if (count < 0) {
+ THROW_ERROR_EXCEPTION("Negative number of values in row");
+ }
+ if (count > MaxValuesPerRow) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::TooManyValuesInRow,
+ "Too many values in row: actual %v, limit %v",
+ count,
+ MaxValuesPerRow);
+ }
+}
+
+void ValidateKeyColumnCount(int count)
+{
+ if (count < 0) {
+ THROW_ERROR_EXCEPTION("Negative number of key columns");
+ }
+ if (count > MaxKeyColumnCount) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::TooManyColumnsInKey,
+ "Too many columns in key: actual %v, limit %v",
+ count,
+ MaxKeyColumnCount);
+ }
+}
+
+void ValidateRowCount(int count)
+{
+ if (count < 0) {
+ THROW_ERROR_EXCEPTION("Negative number of rows in rowset");
+ }
+ if (count > MaxRowsPerRowset) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::TooManyRowsInRowset,
+ "Too many rows in rowset: actual %v, limit %v",
+ count,
+ MaxRowsPerRowset);
+ }
+}
+
+void ValidateClientDataRow(
+ TUnversionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ const TNameTablePtr& nameTable,
+ std::optional<int> tabletIndexColumnId,
+ bool allowMissingKeyColumns)
+{
+ ValidateClientRow(row, schema, idMapping, nameTable, false, allowMissingKeyColumns, tabletIndexColumnId);
+}
+
+void ValidateDuplicateAndRequiredValueColumns(
+ TUnversionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer)
+{
+ auto& columnSeen = *columnPresenceBuffer;
+ YT_VERIFY(std::ssize(columnSeen) >= schema.GetColumnCount());
+ std::fill(columnSeen.begin(), columnSeen.end(), 0);
+
+ for (const auto& value : row) {
+ int mappedId = ApplyIdMapping(value, &idMapping);
+ if (mappedId < 0) {
+ continue;
+ }
+ const auto& column = schema.Columns()[mappedId];
+
+ if (columnSeen[mappedId]) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::DuplicateColumnInSchema,
+ "Duplicate column %v in table schema",
+ column.GetDiagnosticNameString());
+ }
+ columnSeen[mappedId] = true;
+ }
+
+ for (int index = schema.GetKeyColumnCount(); index < schema.GetColumnCount(); ++index) {
+ if (!columnSeen[index] && schema.Columns()[index].Required()) {
+ THROW_ERROR_EXCEPTION(
+ NTableClient::EErrorCode::MissingRequiredColumnInSchema,
+ "Missing required column %v in table schema",
+ schema.Columns()[index].GetDiagnosticNameString());
+ }
+ }
+}
+
+void ValidateClientKey(TLegacyKey key)
+{
+ for (const auto& value : key) {
+ ValidateKeyValue(value);
+ }
+}
+
+void ValidateClientKey(
+ TLegacyKey key,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ const TNameTablePtr& nameTable)
+{
+ ValidateClientRow(key, schema, idMapping, nameTable, true);
+}
+
+void ValidateReadTimestamp(TTimestamp timestamp)
+{
+ if (timestamp != SyncLastCommittedTimestamp &&
+ timestamp != AsyncLastCommittedTimestamp &&
+ (timestamp < MinTimestamp || timestamp > MaxTimestamp))
+ {
+ THROW_ERROR_EXCEPTION("Invalid read timestamp %x", timestamp);
+ }
+}
+
+void ValidateGetInSyncReplicasTimestamp(TTimestamp timestamp)
+{
+ if (timestamp != SyncLastCommittedTimestamp &&
+ (timestamp < MinTimestamp || timestamp > MaxTimestamp))
+ {
+ THROW_ERROR_EXCEPTION("Invalid GetInSyncReplicas timestamp %x", timestamp);
+ }
+}
+
+void ValidateWriteTimestamp(TTimestamp timestamp)
+{
+ if (timestamp < MinTimestamp || timestamp > MaxTimestamp) {
+ THROW_ERROR_EXCEPTION("Invalid write timestamp %x", timestamp);
+ }
+}
+
+int ApplyIdMapping(
+ const TUnversionedValue& value,
+ const TNameTableToSchemaIdMapping* idMapping)
+{
+ auto valueId = value.Id;
+ if (idMapping) {
+ const auto& idMapping_ = *idMapping;
+ if (valueId >= idMapping_.size()) {
+ THROW_ERROR_EXCEPTION("Invalid column id during remapping: expected in range [0, %v), got %v",
+ idMapping_.size(),
+ valueId);
+ }
+ return idMapping_[valueId];
+ } else {
+ return valueId;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLegacyOwningKey GetKeySuccessorImpl(TLegacyKey key, ui32 prefixLength, EValueType sentinelType)
+{
+ auto length = std::min(prefixLength, key.GetCount());
+ TUnversionedOwningRowBuilder builder(length + 1);
+ for (int index = 0; index < static_cast<int>(length); ++index) {
+ builder.AddValue(key[index]);
+ }
+ builder.AddValue(MakeUnversionedSentinelValue(sentinelType));
+ return builder.FinishRow();
+}
+
+TLegacyKey GetKeySuccessorImpl(TLegacyKey key, ui32 prefixLength, EValueType sentinelType, const TRowBufferPtr& rowBuffer)
+{
+ auto length = std::min(prefixLength, key.GetCount());
+ auto result = rowBuffer->AllocateUnversioned(length + 1);
+ for (int index = 0; index < static_cast<int>(length); ++index) {
+ result[index] = rowBuffer->CaptureValue(key[index]);
+ }
+ result[length] = MakeUnversionedSentinelValue(sentinelType);
+ return result;
+}
+
+TLegacyOwningKey GetKeySuccessor(TLegacyKey key)
+{
+ return GetKeySuccessorImpl(
+ key,
+ key.GetCount(),
+ EValueType::Min);
+}
+
+TLegacyKey GetKeySuccessor(TLegacyKey key, const TRowBufferPtr& rowBuffer)
+{
+ return GetKeySuccessorImpl(
+ key,
+ key.GetCount(),
+ EValueType::Min,
+ rowBuffer);
+}
+
+TLegacyOwningKey GetKeyPrefixSuccessor(TLegacyKey key, ui32 prefixLength)
+{
+ return GetKeySuccessorImpl(
+ key,
+ prefixLength,
+ EValueType::Max);
+}
+
+TLegacyKey GetKeyPrefixSuccessor(TLegacyKey key, ui32 prefixLength, const TRowBufferPtr& rowBuffer)
+{
+ return GetKeySuccessorImpl(
+ key,
+ prefixLength,
+ EValueType::Max,
+ rowBuffer);
+}
+
+TLegacyOwningKey GetKeyPrefix(TLegacyKey key, ui32 prefixLength)
+{
+ return TLegacyOwningKey(key.FirstNElements(std::min(key.GetCount(), prefixLength)));
+}
+
+TLegacyKey GetKeyPrefix(TLegacyKey key, ui32 prefixLength, const TRowBufferPtr& rowBuffer)
+{
+ return rowBuffer->CaptureRow(key.FirstNElements(std::min(key.GetCount(), prefixLength)));
+}
+
+TLegacyKey GetStrictKey(TLegacyKey key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType)
+{
+ if (key.GetCount() > keyColumnCount) {
+ return GetKeyPrefix(key, keyColumnCount, rowBuffer);
+ } else {
+ return WidenKey(key, keyColumnCount, rowBuffer, sentinelType);
+ }
+}
+
+TLegacyKey GetStrictKeySuccessor(TLegacyKey key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType)
+{
+ if (key.GetCount() >= keyColumnCount) {
+ return GetKeyPrefixSuccessor(key, keyColumnCount, rowBuffer);
+ } else {
+ return WidenKeySuccessor(key, keyColumnCount, rowBuffer, sentinelType);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TLegacyOwningKey MakeSentinelKey(EValueType type)
+{
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedSentinelValue(type));
+ return builder.FinishRow();
+}
+
+static const TLegacyOwningKey CachedMinKey = MakeSentinelKey(EValueType::Min);
+static const TLegacyOwningKey CachedMaxKey = MakeSentinelKey(EValueType::Max);
+
+const TLegacyOwningKey MinKey()
+{
+ return CachedMinKey;
+}
+
+const TLegacyOwningKey MaxKey()
+{
+ return CachedMaxKey;
+}
+
+static TLegacyOwningKey MakeEmptyKey()
+{
+ TUnversionedOwningRowBuilder builder;
+ return builder.FinishRow();
+}
+
+static const TLegacyOwningKey CachedEmptyKey = MakeEmptyKey();
+
+const TLegacyOwningKey EmptyKey()
+{
+ return CachedEmptyKey;
+}
+
+const TLegacyOwningKey& ChooseMinKey(const TLegacyOwningKey& a, const TLegacyOwningKey& b)
+{
+ int result = CompareRows(a, b);
+ return result <= 0 ? a : b;
+}
+
+const TLegacyOwningKey& ChooseMaxKey(const TLegacyOwningKey& a, const TLegacyOwningKey& b)
+{
+ int result = CompareRows(a, b);
+ return result >= 0 ? a : b;
+}
+
+void ToProto(TProtoStringType* protoRow, TUnversionedRow row)
+{
+ *protoRow = SerializeToString(row);
+}
+
+void ToProto(TProtoStringType* protoRow, const TUnversionedOwningRow& row)
+{
+ ToProto(protoRow, row.Get());
+}
+
+void ToProto(TProtoStringType* protoRow, TUnversionedValueRange range)
+{
+ *protoRow = SerializeToString(range);
+}
+
+void ToProto(TProtoStringType* protoRow, const TRange<TUnversionedOwningValue>& values)
+{
+ std::vector<TUnversionedValue> notOwningValues(values.size());
+ std::copy(values.begin(), values.end(), notOwningValues.begin());
+ ToProto(protoRow, notOwningValues);
+}
+
+void FromProto(TUnversionedOwningRow* row, const TProtoStringType& protoRow, std::optional<int> nullPaddingWidth)
+{
+ *row = DeserializeFromString(TString{protoRow}, nullPaddingWidth);
+}
+
+void FromProto(TUnversionedRow* row, const TProtoStringType& protoRow, const TRowBufferPtr& rowBuffer)
+{
+ if (protoRow == SerializedNullRow) {
+ *row = TUnversionedRow();
+ return;
+ }
+
+ const char* current = protoRow.data();
+
+ ui32 version;
+ current += ReadVarUint32(current, &version);
+ YT_VERIFY(version == 0);
+
+ ui32 valueCount;
+ current += ReadVarUint32(current, &valueCount);
+
+ auto mutableRow = rowBuffer->AllocateUnversioned(valueCount);
+ *row = mutableRow;
+
+ auto* values = mutableRow.Begin();
+ for (auto* value = values; value < values + valueCount; ++value) {
+ current += ReadRowValue(current, value);
+ rowBuffer->CaptureValue(value);
+ }
+}
+
+void FromProto(std::vector<TUnversionedOwningValue>* values, const TProtoStringType& protoRow)
+{
+ TUnversionedOwningRow row;
+ FromProto(&row, protoRow);
+ values->resize(row.GetCount());
+ std::copy(row.Begin(), row.End(), values->begin());
+}
+
+void ToBytes(TString* bytes, const TUnversionedOwningRow& row)
+{
+ *bytes = SerializeToString(row);
+}
+
+void FromBytes(TUnversionedOwningRow* row, TStringBuf bytes)
+{
+ *row = DeserializeFromString(TString(bytes));
+}
+
+void PrintTo(const TUnversionedOwningRow& key, ::std::ostream* os)
+{
+ *os << KeyToYson(key);
+}
+
+void PrintTo(const TUnversionedRow& value, ::std::ostream* os)
+{
+ *os << ToString(value);
+}
+
+TLegacyOwningKey RowToKey(
+ const TTableSchema& schema,
+ TUnversionedRow row)
+{
+ TUnversionedOwningRowBuilder builder;
+ for (int index = 0; index < schema.GetKeyColumnCount(); ++index) {
+ builder.AddValue(row[index]);
+ }
+ return builder.FinishRow();
+}
+
+namespace {
+
+template <class TRow>
+std::pair<TSharedRange<TUnversionedRow>, i64> CaptureRowsImpl(
+ TRange<TRow> rows,
+ TRefCountedTypeCookie tagCookie)
+{
+ size_t bufferSize = 0;
+ bufferSize += sizeof (TUnversionedRow) * rows.Size();
+ for (auto row : rows) {
+ bufferSize += GetUnversionedRowByteSize(row.GetCount());
+ for (const auto& value : row) {
+ if (IsStringLikeType(value.Type)) {
+ bufferSize += value.Length;
+ }
+ }
+ }
+ auto buffer = TSharedMutableRef::Allocate(bufferSize, {.InitializeStorage = false}, tagCookie);
+
+ char* alignedPtr = buffer.Begin();
+ auto allocateAligned = [&] (size_t size) {
+ auto* result = alignedPtr;
+ alignedPtr += size;
+ return result;
+ };
+
+ char* unalignedPtr = buffer.End();
+ auto allocateUnaligned = [&] (size_t size) {
+ unalignedPtr -= size;
+ return unalignedPtr;
+ };
+
+ auto* capturedRows = reinterpret_cast<TUnversionedRow*>(allocateAligned(sizeof (TUnversionedRow) * rows.Size()));
+ for (size_t index = 0; index < rows.Size(); ++index) {
+ auto row = rows[index];
+ int valueCount = row.GetCount();
+ auto* capturedHeader = reinterpret_cast<TUnversionedRowHeader*>(allocateAligned(GetUnversionedRowByteSize(valueCount)));
+ capturedHeader->Capacity = valueCount;
+ capturedHeader->Count = valueCount;
+ auto capturedRow = TMutableUnversionedRow(capturedHeader);
+ capturedRows[index] = capturedRow;
+ ::memcpy(capturedRow.Begin(), row.Begin(), sizeof (TUnversionedValue) * row.GetCount());
+ for (auto& capturedValue : capturedRow) {
+ if (IsStringLikeType(capturedValue.Type)) {
+ auto* capturedString = allocateUnaligned(capturedValue.Length);
+ ::memcpy(capturedString, capturedValue.Data.String, capturedValue.Length);
+ capturedValue.Data.String = capturedString;
+ }
+ }
+ }
+
+ YT_VERIFY(alignedPtr == unalignedPtr);
+
+ return {
+ MakeSharedRange(
+ MakeRange(capturedRows, rows.Size()),
+ std::move(buffer.ReleaseHolder())),
+ bufferSize
+ };
+}
+
+} // namespace
+
+std::pair<TSharedRange<TUnversionedRow>, i64> CaptureRows(
+ TRange<TUnversionedRow> rows,
+ TRefCountedTypeCookie tagCookie)
+{
+ return CaptureRowsImpl(rows, tagCookie);
+}
+
+std::pair<TSharedRange<TUnversionedRow>, i64> CaptureRows(
+ TRange<TUnversionedOwningRow> rows,
+ TRefCountedTypeCookie tagCookie)
+{
+ return CaptureRowsImpl(rows, tagCookie);
+}
+
+void Serialize(const TUnversionedValue& value, IYsonConsumer* consumer, bool anyAsRaw)
+{
+ if (Any(value.Flags)) {
+ consumer->OnBeginAttributes();
+ if (Any(value.Flags & EValueFlags::Aggregate)) {
+ consumer->OnKeyedItem("aggregate");
+ consumer->OnBooleanScalar(true);
+ }
+ if (Any(value.Flags & EValueFlags::Hunk)) {
+ consumer->OnKeyedItem("hunk");
+ consumer->OnBooleanScalar(true);
+ }
+ consumer->OnEndAttributes();
+ }
+ auto type = value.Type;
+ switch (type) {
+ case EValueType::Int64:
+ consumer->OnInt64Scalar(value.Data.Int64);
+ break;
+
+ case EValueType::Uint64:
+ consumer->OnUint64Scalar(value.Data.Uint64);
+ break;
+
+ case EValueType::Double:
+ consumer->OnDoubleScalar(value.Data.Double);
+ break;
+
+ case EValueType::Boolean:
+ consumer->OnBooleanScalar(value.Data.Boolean);
+ break;
+
+ case EValueType::String:
+ consumer->OnStringScalar(value.AsStringBuf());
+ break;
+
+ case EValueType::Any:
+ if (anyAsRaw) {
+ consumer->OnRaw(value.AsStringBuf(), EYsonType::Node);
+ } else {
+ ParseYsonStringBuffer(value.AsStringBuf(), EYsonType::Node, consumer);
+ }
+ break;
+
+ case EValueType::Null:
+ consumer->OnEntity();
+ break;
+
+ case EValueType::Composite:
+ consumer->OnBeginAttributes();
+ consumer->OnKeyedItem("type");
+ consumer->OnStringScalar(FormatEnum(type));
+ consumer->OnEndAttributes();
+ if (anyAsRaw) {
+ consumer->OnRaw(value.AsStringBuf(), EYsonType::Node);
+ } else {
+ ParseYsonStringBuffer(value.AsStringBuf(), EYsonType::Node, consumer);
+ }
+ break;
+
+ default:
+ consumer->OnBeginAttributes();
+ consumer->OnKeyedItem("type");
+ consumer->OnStringScalar(FormatEnum(type));
+ consumer->OnEndAttributes();
+ consumer->OnEntity();
+ break;
+ }
+}
+
+void Serialize(TLegacyKey key, IYsonConsumer* consumer)
+{
+ consumer->OnBeginList();
+ for (const auto& value : key) {
+ consumer->OnListItem();
+ Serialize(value, consumer);
+ }
+ consumer->OnEndList();
+}
+
+void Serialize(const TLegacyOwningKey& key, IYsonConsumer* consumer)
+{
+ return Serialize(key.Get(), consumer);
+}
+
+void Deserialize(TLegacyOwningKey& key, INodePtr node)
+{
+ if (node->GetType() != ENodeType::List) {
+ THROW_ERROR_EXCEPTION("Key cannot be parsed from %Qlv",
+ node->GetType());
+ }
+
+ TUnversionedOwningRowBuilder builder;
+ int id = 0;
+ for (const auto& item : node->AsList()->GetChildren()) {
+ try {
+ switch (item->GetType()) {
+ #define XX(type, cppType) \
+ case ENodeType::type: \
+ builder.AddValue(MakeUnversioned ## type ## Value(item->As ## type()->GetValue(), id)); \
+ break;
+ ITERATE_SCALAR_YTREE_NODE_TYPES(XX)
+ #undef XX
+
+ case ENodeType::Entity: {
+ auto valueType = item->Attributes().Get<EValueType>("type", EValueType::Null);
+ if (valueType != EValueType::Null && !IsSentinelType(valueType)) {
+ THROW_ERROR_EXCEPTION("Entities can only represent %Qlv and sentinel values but "
+ "not values of type %Qlv",
+ EValueType::Null,
+ valueType);
+ }
+ builder.AddValue(MakeUnversionedSentinelValue(valueType, id));
+ break;
+ }
+
+ default:
+ THROW_ERROR_EXCEPTION("Key cannot contain %Qlv values",
+ item->GetType());
+ }
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error deserializing key component #%v", id)
+ << ex;
+ }
+ ++id;
+ }
+ key = builder.FinishRow();
+}
+
+void Deserialize(TLegacyOwningKey& key, TYsonPullParserCursor* cursor)
+{
+ // TODO(levysotsky): Speed up?
+ Deserialize(key, ExtractTo<INodePtr>(cursor));
+}
+
+void TUnversionedOwningRow::Save(TStreamSaveContext& context) const
+{
+ NYT::Save(context, SerializeToString(Get()));
+}
+
+void TUnversionedOwningRow::Load(TStreamLoadContext& context)
+{
+ *this = DeserializeFromString(NYT::Load<TString>(context));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedRowBuilder::TUnversionedRowBuilder(int initialValueCapacity /*= 16*/)
+{
+ RowData_.resize(GetUnversionedRowByteSize(initialValueCapacity));
+ Reset();
+ GetHeader()->Capacity = initialValueCapacity;
+}
+
+int TUnversionedRowBuilder::AddValue(const TUnversionedValue& value)
+{
+ auto* header = GetHeader();
+ if (header->Count == header->Capacity) {
+ auto valueCapacity = 2 * std::max(1U, header->Capacity);
+ RowData_.resize(GetUnversionedRowByteSize(valueCapacity));
+ header = GetHeader();
+ header->Capacity = valueCapacity;
+ }
+
+ *GetValue(header->Count) = value;
+ return header->Count++;
+}
+
+TMutableUnversionedRow TUnversionedRowBuilder::GetRow()
+{
+ return TMutableUnversionedRow(GetHeader());
+}
+
+void TUnversionedRowBuilder::Reset()
+{
+ auto* header = GetHeader();
+ header->Count = 0;
+}
+
+TUnversionedRowHeader* TUnversionedRowBuilder::GetHeader()
+{
+ return reinterpret_cast<TUnversionedRowHeader*>(RowData_.data());
+}
+
+TUnversionedValue* TUnversionedRowBuilder::GetValue(ui32 index)
+{
+ return reinterpret_cast<TUnversionedValue*>(GetHeader() + 1) + index;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedOwningRowBuilder::TUnversionedOwningRowBuilder(int initialValueCapacity /*= 16*/)
+ : InitialValueCapacity_(initialValueCapacity)
+{
+ Reset();
+}
+
+int TUnversionedOwningRowBuilder::AddValue(const TUnversionedValue& value)
+{
+ auto* header = GetHeader();
+ if (header->Count == header->Capacity) {
+ auto valueCapacity = 2 * std::max(1U, header->Capacity);
+ RowData_.Resize(GetUnversionedRowByteSize(valueCapacity));
+ header = GetHeader();
+ header->Capacity = valueCapacity;
+ }
+
+ auto* newValue = GetValue(header->Count);
+ *newValue = value;
+
+ if (IsStringLikeType(value.Type)) {
+ const char* oldStringDataPtr = StringData_.Begin();
+ auto oldStringDataLength = StringData_.Size();
+ StringData_.Append(value.Data.String, value.Length);
+ const char* newStringDataPtr = StringData_.Begin();
+ newValue->Data.String = newStringDataPtr + oldStringDataLength;
+ if (newStringDataPtr != oldStringDataPtr) {
+ for (int index = 0; index < static_cast<int>(header->Count); ++index) {
+ auto* existingValue = GetValue(index);
+ if (IsStringLikeType(existingValue->Type)) {
+ existingValue->Data.String = newStringDataPtr + (existingValue->Data.String - oldStringDataPtr);
+ }
+ }
+ }
+ }
+
+ return header->Count++;
+}
+
+TUnversionedValue* TUnversionedOwningRowBuilder::BeginValues()
+{
+ return reinterpret_cast<TUnversionedValue*>(GetHeader() + 1);
+}
+
+TUnversionedValue* TUnversionedOwningRowBuilder::EndValues()
+{
+ return BeginValues() + GetHeader()->Count;
+}
+
+TUnversionedOwningRow TUnversionedOwningRowBuilder::FinishRow()
+{
+ auto row = TUnversionedOwningRow(
+ TSharedMutableRef::FromBlob(std::move(RowData_)),
+ TSharedRef::FromBlob(std::move(StringData_)));
+ Reset();
+ return row;
+}
+
+void TUnversionedOwningRowBuilder::Reset()
+{
+ RowData_.Resize(GetUnversionedRowByteSize(InitialValueCapacity_));
+
+ auto* header = GetHeader();
+ header->Count = 0;
+ header->Capacity = InitialValueCapacity_;
+}
+
+TUnversionedRowHeader* TUnversionedOwningRowBuilder::GetHeader()
+{
+ return reinterpret_cast<TUnversionedRowHeader*>(RowData_.Begin());
+}
+
+TUnversionedValue* TUnversionedOwningRowBuilder::GetValue(ui32 index)
+{
+ return reinterpret_cast<TUnversionedValue*>(GetHeader() + 1) + index;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TUnversionedOwningRow::Init(TUnversionedValueRange range)
+{
+ int count = std::ssize(range);
+
+ size_t fixedSize = GetUnversionedRowByteSize(count);
+ RowData_ = TSharedMutableRef::Allocate<TOwningRowTag>(fixedSize, {.InitializeStorage = false});
+ auto* header = GetHeader();
+
+ header->Count = count;
+ header->Capacity = count;
+ ::memcpy(header + 1, range.begin(), sizeof(TUnversionedValue) * range.size());
+
+ size_t variableSize = 0;
+ for (const auto& otherValue : range) {
+ if (IsStringLikeType(otherValue.Type)) {
+ variableSize += otherValue.Length;
+ }
+ }
+
+ if (variableSize > 0) {
+ TBlob stringData;
+ stringData.Resize(variableSize);
+ char* current = stringData.Begin();
+ for (int index = 0; index < count; ++index) {
+ const auto& otherValue = range[index];
+ auto& value = reinterpret_cast<TUnversionedValue*>(header + 1)[index];
+ if (IsStringLikeType(otherValue.Type)) {
+ ::memcpy(current, otherValue.Data.String, otherValue.Length);
+ value.Data.String = current;
+ current += otherValue.Length;
+ }
+ }
+ StringData_ = TSharedRef::FromBlob(std::move(stringData));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLegacyOwningKey WidenKey(const TLegacyOwningKey& key, ui32 keyColumnCount, EValueType sentinelType)
+{
+ return WidenKeyPrefix(key, key.GetCount(), keyColumnCount, sentinelType);
+}
+
+TLegacyKey WidenKey(const TLegacyKey& key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType)
+{
+ return WidenKeyPrefix(key, key.GetCount(), keyColumnCount, rowBuffer, sentinelType);
+}
+
+TLegacyOwningKey WidenKeySuccessor(const TLegacyOwningKey& key, ui32 keyColumnCount, EValueType sentinelType)
+{
+ YT_VERIFY(static_cast<int>(keyColumnCount) >= key.GetCount());
+
+ TUnversionedOwningRowBuilder builder;
+ for (int index = 0; index < key.GetCount(); ++index) {
+ builder.AddValue(key[index]);
+ }
+
+ for (ui32 index = key.GetCount(); index < keyColumnCount; ++index) {
+ builder.AddValue(MakeUnversionedSentinelValue(sentinelType));
+ }
+
+ builder.AddValue(MakeUnversionedSentinelValue(EValueType::Max));
+
+ return builder.FinishRow();
+}
+
+TLegacyKey WidenKeySuccessor(const TLegacyKey& key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType)
+{
+ YT_VERIFY(keyColumnCount >= key.GetCount());
+
+ auto wideKey = rowBuffer->AllocateUnversioned(keyColumnCount + 1);
+
+ for (ui32 index = 0; index < key.GetCount(); ++index) {
+ wideKey[index] = rowBuffer->CaptureValue(key[index]);
+ }
+
+ for (ui32 index = key.GetCount(); index < keyColumnCount; ++index) {
+ wideKey[index] = MakeUnversionedSentinelValue(sentinelType);
+ }
+
+ wideKey[keyColumnCount] = MakeUnversionedSentinelValue(EValueType::Max);
+
+ return wideKey;
+}
+
+TLegacyOwningKey WidenKeyPrefix(const TLegacyOwningKey& key, ui32 prefixLength, ui32 keyColumnCount, EValueType sentinelType)
+{
+ YT_VERIFY(static_cast<int>(prefixLength) <= key.GetCount() && prefixLength <= keyColumnCount);
+
+ if (key.GetCount() == static_cast<int>(prefixLength) && prefixLength == keyColumnCount) {
+ return key;
+ }
+
+ TUnversionedOwningRowBuilder builder;
+ for (ui32 index = 0; index < prefixLength; ++index) {
+ builder.AddValue(key[index]);
+ }
+
+ for (ui32 index = prefixLength; index < keyColumnCount; ++index) {
+ builder.AddValue(MakeUnversionedSentinelValue(sentinelType));
+ }
+
+ return builder.FinishRow();
+}
+
+TLegacyKey WidenKeyPrefix(TLegacyKey key, ui32 prefixLength, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType)
+{
+ YT_VERIFY(prefixLength <= key.GetCount() && prefixLength <= keyColumnCount);
+
+ if (key.GetCount() == prefixLength && prefixLength == keyColumnCount) {
+ return rowBuffer->CaptureRow(key);
+ }
+
+ auto wideKey = rowBuffer->AllocateUnversioned(keyColumnCount);
+
+ for (ui32 index = 0; index < prefixLength; ++index) {
+ wideKey[index] = rowBuffer->CaptureValue(key[index]);
+ }
+
+ for (ui32 index = prefixLength; index < keyColumnCount; ++index) {
+ wideKey[index] = MakeUnversionedSentinelValue(sentinelType);
+ }
+
+ return wideKey;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSharedRange<TRowRange> MakeSingletonRowRange(TLegacyKey lowerBound, TLegacyKey upperBound)
+{
+ auto rowBuffer = New<TRowBuffer>();
+ TCompactVector<TRowRange, 1> ranges(1, TRowRange(
+ rowBuffer->CaptureRow(lowerBound),
+ rowBuffer->CaptureRow(upperBound)));
+ return MakeSharedRange(
+ std::move(ranges),
+ std::move(rowBuffer));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyRef ToKeyRef(TUnversionedRow row)
+{
+ return row.Elements();
+}
+
+TUnversionedValueRange ToKeyRef(TUnversionedRow row, int prefixLength)
+{
+ return row.FirstNElements(prefixLength);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TUnversionedRow row, TStringBuf format)
+{
+ if (row) {
+ builder->AppendChar('[');
+ JoinToString(
+ builder,
+ row.Begin(),
+ row.End(),
+ [&] (TStringBuilderBase* builder, const TUnversionedValue& value) {
+ FormatValue(builder, value, format);
+ });
+ builder->AppendChar(']');
+ } else {
+ builder->AppendString("<null>");
+ }
+}
+
+void FormatValue(TStringBuilderBase* builder, TMutableUnversionedRow row, TStringBuf format)
+{
+ FormatValue(builder, TUnversionedRow(row), format);
+}
+
+void FormatValue(TStringBuilderBase* builder, const TUnversionedOwningRow& row, TStringBuf format)
+{
+ FormatValue(builder, TUnversionedRow(row), format);
+}
+
+TString ToString(TUnversionedRow row, bool valuesOnly)
+{
+ return ToStringViaBuilder(row, valuesOnly ? "k" : "");
+}
+
+TString ToString(TMutableUnversionedRow row, bool valuesOnly)
+{
+ return ToString(TUnversionedRow(row), valuesOnly);
+}
+
+TString ToString(const TUnversionedOwningRow& row, bool valuesOnly)
+{
+ return ToString(row.Get(), valuesOnly);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t TDefaultUnversionedValueRangeHash::operator()(TUnversionedValueRange range) const
+{
+ return GetFarmFingerprint(range);
+}
+
+bool TDefaultUnversionedValueRangeEqual::operator()(TUnversionedValueRange lhs, TUnversionedValueRange rhs) const
+{
+ if (lhs.size() != rhs.size()) {
+ return false;
+ }
+ for (size_t index = 0; index < lhs.size(); ++index) {
+ if (!TDefaultUnversionedValueEqual()(lhs[index], rhs[index])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t TDefaultUnversionedRowHash::operator()(TUnversionedRow row) const
+{
+ return TDefaultUnversionedValueRangeHash()(row.Elements());
+}
+
+bool TDefaultUnversionedRowEqual::operator()(TUnversionedRow lhs, TUnversionedRow rhs) const
+{
+ return lhs == rhs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t TBitwiseUnversionedValueRangeHash::operator()(TUnversionedValueRange range) const
+{
+ size_t result = 0;
+ for (const auto& value : range) {
+ HashCombine(result, TBitwiseUnversionedValueHash()(value));
+ }
+ return result;
+}
+
+bool TBitwiseUnversionedValueRangeEqual::operator()(TUnversionedValueRange lhs, TUnversionedValueRange rhs) const
+{
+ if (lhs.size() != rhs.size()) {
+ return false;
+ }
+ for (size_t index = 0; index < lhs.size(); ++index) {
+ if (!TBitwiseUnversionedValueEqual()(lhs[index], rhs[index])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t TBitwiseUnversionedRowHash::operator()(TUnversionedRow row) const
+{
+ return TBitwiseUnversionedValueRangeHash()(row.Elements());
+}
+
+bool TBitwiseUnversionedRowEqual::operator()(TUnversionedRow lhs, TUnversionedRow rhs) const
+{
+ return TBitwiseUnversionedValueRangeEqual()(lhs.Elements(), rhs.Elements());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unversioned_row.h b/yt/yt/client/table_client/unversioned_row.h
new file mode 100644
index 0000000000..7557836241
--- /dev/null
+++ b/yt/yt/client/table_client/unversioned_row.h
@@ -0,0 +1,973 @@
+#pragma once
+
+#include "public.h"
+#include "row_base.h"
+#include "unversioned_value.h"
+
+#include <yt/yt/core/logging/log.h>
+
+#include <yt/yt/core/misc/blob.h>
+#include <yt/yt/core/misc/serialize.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <yt/yt/core/concurrency/fls.h>
+
+#include <yt/yt_proto/yt/core/misc/proto/guid.pb.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+#include <library/cpp/yt/memory/chunked_memory_pool.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+extern const TString SerializedNullRow;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnversionedOwningValue
+{
+public:
+ TUnversionedOwningValue() = default;
+
+ TUnversionedOwningValue(TUnversionedOwningValue&& other) noexcept
+ {
+ std::swap(Value_, other.Value_);
+ }
+
+ TUnversionedOwningValue(const TUnversionedOwningValue& other)
+ {
+ Assign(other.Value_);
+ }
+
+ TUnversionedOwningValue(const TUnversionedValue& other)
+ {
+ Assign(other);
+ }
+
+ ~TUnversionedOwningValue()
+ {
+ Clear();
+ }
+
+ operator TUnversionedValue() const
+ {
+ return Value_;
+ }
+
+ TUnversionedOwningValue& operator=(TUnversionedOwningValue other) noexcept
+ {
+ std::swap(Value_, other.Value_);
+ return *this;
+ }
+
+ void Clear()
+ {
+ if (IsStringLikeType(Value_.Type)) {
+ delete[] Value_.Data.String;
+ }
+ Value_.Type = EValueType::TheBottom;
+ Value_.Length = 0;
+ }
+
+ //! Provides mutable access to the string data.
+ char* GetMutableString()
+ {
+ YT_VERIFY(IsStringLikeType(Value_.Type));
+ // NB: it is correct to use `const_cast` here to modify the stored string
+ // because initially it's allocated as a non-const `char*`.
+ return const_cast<char*>(Value_.Data.String);
+ }
+
+ EValueType Type() const
+ {
+ return Value_.Type;
+ }
+
+private:
+ TUnversionedValue Value_{
+ .Id = 0,
+ .Type = EValueType::TheBottom,
+ .Flags = {},
+ .Length = 0,
+ .Data = {},
+ };
+
+ void Assign(const TUnversionedValue& other)
+ {
+ Value_ = other;
+ if (IsStringLikeType(Value_.Type)) {
+ auto newString = new char[Value_.Length];
+ ::memcpy(newString, Value_.Data.String, Value_.Length);
+ Value_.Data.String = newString;
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Debug printer for Gtest unittests.
+inline void PrintTo(const TUnversionedOwningValue& value, std::ostream* os)
+{
+ PrintTo(static_cast<TUnversionedValue>(value), os);
+}
+
+inline TUnversionedValue MakeUnversionedSentinelValue(EValueType type, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeSentinelValue<TUnversionedValue>(type, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedNullValue(int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeNullValue<TUnversionedValue>(id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedInt64Value(i64 value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeInt64Value<TUnversionedValue>(value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedUint64Value(ui64 value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeUint64Value<TUnversionedValue>(value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedDoubleValue(double value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeDoubleValue<TUnversionedValue>(value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedBooleanValue(bool value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeBooleanValue<TUnversionedValue>(value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedStringLikeValue(EValueType valueType, TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeStringLikeValue<TUnversionedValue>(valueType, value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedStringValue(TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeStringValue<TUnversionedValue>(value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedAnyValue(TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeAnyValue<TUnversionedValue>(value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedCompositeValue(TStringBuf value, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeCompositeValue<TUnversionedValue>(value, id, flags);
+}
+
+inline TUnversionedValue MakeUnversionedValueHeader(EValueType type, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ return MakeSentinelValue<TUnversionedValue>(type, id, flags);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TUnversionedRowHeader
+{
+ ui32 Count;
+ ui32 Capacity;
+};
+
+static_assert(
+ sizeof(TUnversionedRowHeader) == 8,
+ "TUnversionedRowHeader has to be exactly 8 bytes.");
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline size_t GetDataWeight(EValueType type)
+{
+ switch (type) {
+ case EValueType::Null:
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ return 0;
+
+ case EValueType::Int64:
+ return sizeof(i64);
+
+ case EValueType::Uint64:
+ return sizeof(ui64);
+
+ case EValueType::Double:
+ return sizeof(double);
+
+ case EValueType::Boolean:
+ return 1;
+
+ default:
+ YT_ABORT();
+ }
+}
+
+inline size_t GetDataWeight(const TUnversionedValue& value)
+{
+ if (IsStringLikeType(value.Type)) {
+ return value.Length;
+ } else {
+ return GetDataWeight(value.Type);
+ }
+}
+
+size_t EstimateRowValueSize(const TUnversionedValue& value, bool isInlineHunkValue = false);
+size_t WriteRowValue(char* output, const TUnversionedValue& value, bool isInlineHunkValue = false);
+size_t ReadRowValue(const char* input, TUnversionedValue* value);
+
+void Save(TStreamSaveContext& context, const TUnversionedValue& value);
+void Load(TStreamLoadContext& context, TUnversionedValue& value, TChunkedMemoryPool* pool);
+
+//! Ternary comparison predicate for TUnversionedValue-s.
+//! Returns zero, positive or negative value depending on the outcome.
+//! Note that this ignores flags.
+int CompareRowValues(const TUnversionedValue& lhs, const TUnversionedValue& rhs);
+
+//! Derived comparison operators.
+//! Note that these ignore flags.
+bool operator == (const TUnversionedValue& lhs, const TUnversionedValue& rhs);
+bool operator != (const TUnversionedValue& lhs, const TUnversionedValue& rhs);
+bool operator <= (const TUnversionedValue& lhs, const TUnversionedValue& rhs);
+bool operator < (const TUnversionedValue& lhs, const TUnversionedValue& rhs);
+bool operator >= (const TUnversionedValue& lhs, const TUnversionedValue& rhs);
+bool operator > (const TUnversionedValue& lhs, const TUnversionedValue& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Ternary comparison predicate for value ranges.
+//! Note that this ignores aggregate flags.
+int CompareValueRanges(
+ TUnversionedValueRange lhs,
+ TUnversionedValueRange rhs);
+
+//! Ternary comparison predicate for TUnversionedRow-s stripped to a given number of
+//! (leading) values.
+//! Note that this ignores aggregate flags.
+int CompareRows(
+ TUnversionedRow lhs,
+ TUnversionedRow rhs,
+ ui32 prefixLength = std::numeric_limits<ui32>::max());
+
+//! Derived comparison operators.
+//! Note that these ignore aggregate flags.
+bool operator == (TUnversionedRow lhs, TUnversionedRow rhs);
+bool operator != (TUnversionedRow lhs, TUnversionedRow rhs);
+bool operator <= (TUnversionedRow lhs, TUnversionedRow rhs);
+bool operator < (TUnversionedRow lhs, TUnversionedRow rhs);
+bool operator >= (TUnversionedRow lhs, TUnversionedRow rhs);
+bool operator > (TUnversionedRow lhs, TUnversionedRow rhs);
+
+//! Computes FarmHash forever-fixed fingerprint for a range of values.
+TFingerprint GetFarmFingerprint(TUnversionedValueRange range);
+
+//! Computes FarmHash forever-fixed fingerprint for an unversioned row.
+TFingerprint GetFarmFingerprint(TUnversionedRow row);
+
+//! Returns the number of bytes needed to store an unversioned row (not including string data).
+size_t GetUnversionedRowByteSize(ui32 valueCount);
+
+//! Returns the storage-invariant data weight of a given row.
+size_t GetDataWeight(TUnversionedRow row);
+
+size_t GetDataWeight(TRange<TUnversionedRow> rows);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A row with unversioned data.
+/*!
+ * A lightweight wrapper around |TUnversionedRowHeader*|.
+ *
+ * Provides access to a sequence of unversioned values.
+ * If data is schemaful then the positions of values must exactly match their ids.
+ *
+ * Memory layout:
+ * 1) TUnversionedRowHeader
+ * 2) TUnversionedValue per each value (#TUnversionedRowHeader::Count)
+ */
+class TUnversionedRow
+{
+public:
+ TUnversionedRow() = default;
+
+ explicit TUnversionedRow(const TUnversionedRowHeader* header)
+ : Header_(header)
+ { }
+
+ explicit TUnversionedRow(TTypeErasedRow erased)
+ : Header_(reinterpret_cast<const TUnversionedRowHeader*>(erased.OpaqueHeader))
+ { }
+
+ explicit operator bool() const
+ {
+ return Header_ != nullptr;
+ }
+
+ TTypeErasedRow ToTypeErasedRow() const
+ {
+ return {Header_};
+ }
+
+ const TUnversionedRowHeader* GetHeader() const
+ {
+ return Header_;
+ }
+
+ const TUnversionedValue* Begin() const
+ {
+ return reinterpret_cast<const TUnversionedValue*>(Header_ + 1);
+ }
+
+ const TUnversionedValue* End() const
+ {
+ return Begin() + GetCount();
+ }
+
+ TUnversionedValueRange Elements() const
+ {
+ return {Begin(), End()};
+ }
+
+ TUnversionedValueRange FirstNElements(int count) const
+ {
+ YT_ASSERT(count <= static_cast<int>(GetCount()));
+ return {Begin(), Begin() + count};
+ }
+
+ const TUnversionedValue& operator[] (int index) const
+ {
+ YT_ASSERT(index >= 0 && static_cast<ui32>(index) < GetCount());
+ return Begin()[index];
+ }
+
+ ui32 GetCount() const
+ {
+ return Header_ ? Header_->Count : 0u;
+ }
+
+ // STL interop.
+ const TUnversionedValue* begin() const
+ {
+ return Begin();
+ }
+
+ const TUnversionedValue* end() const
+ {
+ return End();
+ }
+
+ void Save(TSaveContext& context) const;
+ void Load(TLoadContext& context);
+
+private:
+ const TUnversionedRowHeader* Header_ = nullptr;
+};
+
+static_assert(
+ sizeof(TUnversionedRow) == sizeof(intptr_t),
+ "TUnversionedRow size must match that of a pointer.");
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Checks that #value type is compatible with the schema column type.
+/*
+ * If #typeAnyAcceptsAllValues is false columns of type EValueType::ANY accept only values of same type.
+ * If #typeAnyAcceptsAllValues is true then columns of type EValueType::ANY accept all values.
+ *
+ * \note Function throws an error if column has `Any' type and value has `non-Any' type.
+ */
+void ValidateValueType(
+ const TUnversionedValue& value,
+ const TTableSchema& schema,
+ int schemaId,
+ bool typeAnyAcceptsAllValues,
+ bool ignoreRequired = false,
+ bool validateAnyIsValidYson = false);
+void ValidateValueType(
+ const TUnversionedValue& value,
+ const TColumnSchema& columnSchema,
+ bool typeAnyAcceptsAllValues,
+ bool ignoreRequired = false,
+ bool validateAnyIsValidYson = false);
+
+//! Checks that #value is allowed to appear in static tables' data. Throws on failure.
+void ValidateStaticValue(const TUnversionedValue& value);
+
+//! Checks that #value is allowed to appear in dynamic tables' data. Throws on failure.
+void ValidateDataValue(const TUnversionedValue& value);
+
+//! Checks that #value is allowed to appear in dynamic tables' keys. Throws on failure.
+void ValidateKeyValue(const TUnversionedValue& value);
+
+//! Checks that #count represents an allowed number of values in a row. Throws on failure.
+void ValidateRowValueCount(int count);
+
+//! Checks that #count represents an allowed number of components in a key. Throws on failure.
+void ValidateKeyColumnCount(int count);
+
+//! Checks that #count represents an allowed number of rows in a rowset. Throws on failure.
+void ValidateRowCount(int count);
+
+//! Checks that #row is a valid client-side data row. Throws on failure.
+/*!
+ * Value ids in the row are first mapped via #idMapping.
+ * The row must obey the following properties:
+ * 1. Its value count must pass #ValidateRowValueCount checks.
+ * 2. It must contain all key components (values with ids in range [0, #schema.GetKeyColumnCount() - 1]).
+ * 3. Value types must either be null or match those given in #schema.
+ * 4. For values marked with #TUnversionedValue::Aggregate flag, the corresponding columns in #schema must
+ * be aggregating.
+ * 5. Versioned values must be sorted by |id| (in ascending order) and then by |timestamp| (in descending order).
+ * 6. At least one non-key column must be specified.
+ */
+void ValidateClientDataRow(
+ TUnversionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ const TNameTablePtr& nameTable,
+ std::optional<int> tabletIndexColumnId = std::nullopt,
+ bool allowMissingKeyColumns = false);
+
+//! Checks that #row contains no duplicate non-key columns and that all required columns are present. Skip values that map to negative ids via #idMapping.
+/*! It is assumed that ValidateClientDataRow was called before. */
+void ValidateDuplicateAndRequiredValueColumns(
+ TUnversionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer);
+
+//! Checks that #key is a valid client-side key. Throws on failure.
+/*! The components must pass #ValidateKeyValue check. */
+void ValidateClientKey(TLegacyKey key);
+
+//! Checks that #key is a valid client-side key. Throws on failure.
+/*! The key must obey the following properties:
+ * 1. It cannot be null.
+ * 2. It must contain exactly #schema.GetKeyColumnCount() components.
+ * 3. Value ids must be a permutation of {0, ..., #schema.GetKeyColumnCount() - 1}.
+ * 4. Value types must either be null of match those given in schema.
+ */
+void ValidateClientKey(
+ TLegacyKey key,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ const TNameTablePtr& nameTable);
+
+//! Checks if #timestamp is sane and can be used for data.
+//! Allows timestamps in range [MinTimestamp, MaxTimestamp] plus some sentinels
+//! (SyncLastCommittedTimestamp and AsyncLastCommittedTimestamp).
+void ValidateReadTimestamp(TTimestamp timestamp);
+
+//! Checks if #timestamp is sane and can be used for replica synchronization.
+//! Allows timestamps in range [MinTimestamp, MaxTimestamp].
+void ValidateGetInSyncReplicasTimestamp(TTimestamp timestamp);
+
+//! Checks if #timestamp is sane and can be used for writing (versioned) data.
+//! Allows timestamps in range [MinTimestamp, MaxTimestamp].
+void ValidateWriteTimestamp(TTimestamp timestamp);
+
+//! An internal helper used by validators.
+int ApplyIdMapping(
+ const TUnversionedValue& value,
+ const TNameTableToSchemaIdMapping* idMapping);
+
+//! Returns the successor of |key|, i.e. the key obtained from |key|
+//! by appending a |EValueType::Min| sentinel.
+TLegacyOwningKey GetKeySuccessor(TLegacyKey key);
+TLegacyKey GetKeySuccessor(TLegacyKey key, const TRowBufferPtr& rowBuffer);
+
+//! Returns the successor of |key| trimmed to a given length, i.e. the key
+//! obtained by trimming |key| to |prefixLength| and appending
+//! a |EValueType::Max| sentinel.
+TLegacyOwningKey GetKeyPrefixSuccessor(TLegacyKey key, ui32 prefixLength);
+TLegacyKey GetKeyPrefixSuccessor(TLegacyKey key, ui32 prefixLength, const TRowBufferPtr& rowBuffer);
+
+//! Returns key of a strict length (either trimmed key or widened key)
+TLegacyKey GetStrictKey(TLegacyKey key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType = EValueType::Null);
+TLegacyKey GetStrictKeySuccessor(TLegacyKey key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType = EValueType::Null);
+
+//! If #key has more than #prefixLength values then trims it this limit.
+TLegacyOwningKey GetKeyPrefix(TLegacyKey key, ui32 prefixLength);
+TLegacyKey GetKeyPrefix(TLegacyKey key, ui32 prefixLength, const TRowBufferPtr& rowBuffer);
+
+//! Makes a new, wider key padded with given sentinel values.
+TLegacyOwningKey WidenKey(const TLegacyOwningKey& key, ui32 keyColumnCount, EValueType sentinelType = EValueType::Null);
+TLegacyKey WidenKey(const TLegacyKey& key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType = EValueType::Null);
+
+//! Makes the key wider by appending given sentinel values up to |keyColumnCount| length
+//! and then appends a |EValueType::Max| sentinel.
+TLegacyOwningKey WidenKeySuccessor(const TLegacyOwningKey& key, ui32 keyColumnCount, EValueType sentinelType = EValueType::Null);
+TLegacyKey WidenKeySuccessor(const TLegacyKey& key, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType = EValueType::Null);
+
+//! Takes prefix of a key and makes it wider.
+TLegacyOwningKey WidenKeyPrefix(const TLegacyOwningKey& key, ui32 prefixLength, ui32 keyColumnCount, EValueType sentinelType = EValueType::Null);
+TLegacyKey WidenKeyPrefix(TLegacyKey key, ui32 prefixLength, ui32 keyColumnCount, const TRowBufferPtr& rowBuffer, EValueType sentinelType = EValueType::Null);
+
+//! Returns the key with no components.
+const TLegacyOwningKey EmptyKey();
+
+//! Returns the key with a single |Min| component.
+const TLegacyOwningKey MinKey();
+
+//! Returns the key with a single |Max| component.
+const TLegacyOwningKey MaxKey();
+
+//! Compares two keys, |a| and |b|, and returns a smaller one.
+//! Ties are broken in favour of the first argument.
+const TLegacyOwningKey& ChooseMinKey(const TLegacyOwningKey& a, const TLegacyOwningKey& b);
+
+//! Compares two keys, |a| and |b|, and returns a bigger one.
+//! Ties are broken in favour of the first argument.
+const TLegacyOwningKey& ChooseMaxKey(const TLegacyOwningKey& a, const TLegacyOwningKey& b);
+
+TString SerializeToString(TUnversionedRow row);
+TString SerializeToString(TUnversionedValueRange range);
+
+void ToProto(TProtoStringType* protoRow, TUnversionedRow row);
+void ToProto(TProtoStringType* protoRow, const TUnversionedOwningRow& row);
+void ToProto(TProtoStringType* protoRow, TUnversionedValueRange range);
+void ToProto(TProtoStringType* protoRow, const TRange<TUnversionedOwningValue>& values);
+
+void FromProto(TUnversionedOwningRow* row, const TProtoStringType& protoRow, std::optional<int> nullPaddingWidth = {});
+void FromProto(TUnversionedRow* row, const TProtoStringType& protoRow, const TRowBufferPtr& rowBuffer);
+void FromProto(std::vector<TUnversionedOwningValue>* values, const TProtoStringType& protoRow);
+
+void ToBytes(TString* bytes, const TUnversionedOwningRow& row);
+
+void FromBytes(TUnversionedOwningRow* row, TStringBuf bytes);
+
+void Serialize(const TUnversionedValue& value, NYson::IYsonConsumer* consumer, bool anyAsRaw = false);
+void Serialize(TLegacyKey key, NYson::IYsonConsumer* consumer);
+void Serialize(const TLegacyOwningKey& key, NYson::IYsonConsumer* consumer);
+
+void Deserialize(TLegacyOwningKey& key, NYTree::INodePtr node);
+void Deserialize(TLegacyOwningKey& key, NYson::TYsonPullParserCursor* cursor);
+
+size_t GetYsonSize(const TUnversionedValue& value);
+size_t WriteYson(char* buffer, const TUnversionedValue& unversionedValue);
+
+//! Debug printers for Gtest unittests.
+void PrintTo(const TUnversionedOwningRow& key, ::std::ostream* os);
+void PrintTo(const TUnversionedRow& value, ::std::ostream* os);
+
+TLegacyOwningKey RowToKey(
+ const NTableClient::TTableSchema& schema,
+ TUnversionedRow row);
+
+//! Constructs a shared range of rows from a non-shared one.
+/*!
+ * The values contained in the rows are also captured.
+ * The underlying storage allocation has just the right size to contain the captured
+ * data and is marked with #tagCookie. The size of allocated space is returned via
+ * second value.
+ */
+std::pair<TSharedRange<TUnversionedRow>, i64> CaptureRows(
+ TRange<TUnversionedRow> rows,
+ TRefCountedTypeCookie tagCookie);
+
+std::pair<TSharedRange<TUnversionedRow>, i64> CaptureRows(
+ TRange<TUnversionedOwningRow> rows,
+ TRefCountedTypeCookie tagCookie);
+
+template <class TTag, class TRow>
+std::pair<TSharedRange<TUnversionedRow>, i64> CaptureRows(TRange<TRow> rows)
+{
+ return CaptureRows(rows, GetRefCountedTypeCookie<TTag>());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A variant of TUnversionedRow that enables mutating access to its content.
+class TMutableUnversionedRow
+ : public TUnversionedRow
+{
+public:
+ TMutableUnversionedRow() = default;
+
+ explicit TMutableUnversionedRow(TUnversionedRowHeader* header)
+ : TUnversionedRow(header)
+ { }
+
+ explicit TMutableUnversionedRow(TTypeErasedRow row)
+ : TUnversionedRow(reinterpret_cast<const TUnversionedRowHeader*>(row.OpaqueHeader))
+ { }
+
+ static TMutableUnversionedRow Allocate(
+ TChunkedMemoryPool* pool,
+ ui32 valueCount);
+
+ static TMutableUnversionedRow Create(
+ void* buffer,
+ ui32 valueCount);
+
+ TUnversionedRowHeader* GetHeader()
+ {
+ return const_cast<TUnversionedRowHeader*>(TUnversionedRow::GetHeader());
+ }
+
+ TUnversionedValue* Begin()
+ {
+ return reinterpret_cast<TUnversionedValue*>(GetHeader() + 1);
+ }
+
+ TUnversionedValue* End()
+ {
+ return Begin() + GetCount();
+ }
+
+ const TUnversionedValue* Begin() const
+ {
+ return reinterpret_cast<const TUnversionedValue*>(TUnversionedRow::GetHeader() + 1);
+ }
+
+ const TUnversionedValue* End() const
+ {
+ return Begin() + GetCount();
+ }
+
+ TMutableUnversionedValueRange Elements()
+ {
+ return {Begin(), End()};
+ }
+
+ TMutableUnversionedValueRange FirstNElements(int count)
+ {
+ YT_ASSERT(count <= static_cast<int>(GetCount()));
+ return {Begin(), Begin() + count};
+ }
+
+ void SetCount(ui32 count)
+ {
+ YT_ASSERT(count <= GetHeader()->Capacity);
+ GetHeader()->Count = count;
+ }
+
+ TUnversionedValue& operator[] (ui32 index)
+ {
+ YT_ASSERT(index < GetHeader()->Count);
+ return Begin()[index];
+ }
+
+ // STL interop.
+ TUnversionedValue* begin()
+ {
+ return Begin();
+ }
+
+ TUnversionedValue* end()
+ {
+ return End();
+ }
+
+ const TUnversionedValue* begin() const
+ {
+ return Begin();
+ }
+
+ const TUnversionedValue* end() const
+ {
+ return End();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! An owning variant of TUnversionedRow.
+/*!
+ * Instances of TUnversionedOwningRow are lightweight handles.
+ * Fixed part is stored in shared ref-counted blobs.
+ * Variable part is stored in shared strings.
+ */
+class TUnversionedOwningRow
+{
+public:
+ TUnversionedOwningRow() = default;
+
+ TUnversionedOwningRow(TUnversionedValueRange range)
+ {
+ Init(range);
+ }
+
+ explicit TUnversionedOwningRow(TUnversionedRow other)
+ {
+ if (other) {
+ Init(other.Elements());
+ }
+ }
+
+ TUnversionedOwningRow(const TUnversionedOwningRow& other)
+ : RowData_(other.RowData_)
+ , StringData_(other.StringData_)
+ { }
+
+ TUnversionedOwningRow(TUnversionedOwningRow&& other)
+ : RowData_(std::move(other.RowData_))
+ , StringData_(std::move(other.StringData_))
+ { }
+
+ explicit operator bool() const
+ {
+ return static_cast<bool>(RowData_);
+ }
+
+ operator TUnversionedRow() const
+ {
+ return Get();
+ }
+
+ TUnversionedRow Get() const
+ {
+ return TUnversionedRow(GetHeader());
+ }
+
+ const TUnversionedValue* Begin() const
+ {
+ const auto* header = GetHeader();
+ return header ? reinterpret_cast<const TUnversionedValue*>(header + 1) : nullptr;
+ }
+
+ const TUnversionedValue* End() const
+ {
+ return Begin() + GetCount();
+ }
+
+ TUnversionedValueRange Elements() const
+ {
+ return {Begin(), End()};
+ }
+
+ TUnversionedValueRange FirstNElements(int count) const
+ {
+ YT_ASSERT(count <= static_cast<int>(GetCount()));
+ return {Begin(), Begin() + count};
+ }
+
+ const TUnversionedValue& operator[] (int index) const
+ {
+ YT_ASSERT(index >= 0 && index < GetCount());
+ return Begin()[index];
+ }
+
+ int GetCount() const
+ {
+ const auto* header = GetHeader();
+ return header ? static_cast<int>(header->Count) : 0;
+ }
+
+ size_t GetSpaceUsed() const
+ {
+ return StringData_.GetHolder()->GetTotalByteSize().value_or(StringData_.Size()) +
+ RowData_.GetHolder()->GetTotalByteSize().value_or(RowData_.Size());
+ }
+
+
+ friend void swap(TUnversionedOwningRow& lhs, TUnversionedOwningRow& rhs)
+ {
+ using std::swap;
+
+ swap(lhs.RowData_, rhs.RowData_);
+ swap(lhs.StringData_, rhs.StringData_);
+ }
+
+ TUnversionedOwningRow& operator=(const TUnversionedOwningRow& other)
+ {
+ RowData_ = other.RowData_;
+ StringData_ = other.StringData_;
+ return *this;
+ }
+
+ TUnversionedOwningRow& operator=(TUnversionedOwningRow&& other)
+ {
+ RowData_ = std::move(other.RowData_);
+ StringData_ = std::move(other.StringData_);
+ return *this;
+ }
+
+ // STL interop.
+ const TUnversionedValue* begin() const
+ {
+ return Begin();
+ }
+
+ const TUnversionedValue* end() const
+ {
+ return End();
+ }
+
+
+ void Save(TStreamSaveContext& context) const;
+ void Load(TStreamLoadContext& context);
+
+private:
+ friend TLegacyOwningKey GetKeySuccessorImpl(const TLegacyOwningKey& key, int prefixLength, EValueType sentinelType);
+ friend TUnversionedOwningRow DeserializeFromString(TString&& data, std::optional<int> nullPaddingWidth);
+
+ friend class TUnversionedOwningRowBuilder;
+
+ TSharedMutableRef RowData_; // TRowHeader plus TValue-s
+ TSharedRef StringData_; // Holds string data
+
+
+ TUnversionedOwningRow(TSharedMutableRef&& rowData, TSharedRef&& stringData)
+ : RowData_(std::move(rowData))
+ , StringData_(std::move(stringData))
+ { }
+
+ void Init(TUnversionedValueRange range);
+
+ TUnversionedRowHeader* GetHeader()
+ {
+ return RowData_ ? reinterpret_cast<TUnversionedRowHeader*>(RowData_.Begin()) : nullptr;
+ }
+
+ const TUnversionedRowHeader* GetHeader() const
+ {
+ return RowData_ ? reinterpret_cast<const TUnversionedRowHeader*>(RowData_.Begin()) : nullptr;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A helper used for constructing TUnversionedRow instances.
+//! Only row values are kept, strings are only referenced.
+class TUnversionedRowBuilder
+{
+public:
+ static const int DefaultValueCapacity = 8;
+
+ explicit TUnversionedRowBuilder(int initialValueCapacity = DefaultValueCapacity);
+
+ int AddValue(const TUnversionedValue& value);
+ TMutableUnversionedRow GetRow();
+ void Reset();
+
+private:
+ static const int DefaultBlobCapacity =
+ sizeof(TUnversionedRowHeader) +
+ DefaultValueCapacity * sizeof(TUnversionedValue);
+
+ TCompactVector<char, DefaultBlobCapacity> RowData_;
+
+ TUnversionedRowHeader* GetHeader();
+ TUnversionedValue* GetValue(ui32 index);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TOwningRowTag
+{ };
+
+//! A helper used for constructing TUnversionedOwningRow instances.
+//! Keeps both row values and strings.
+class TUnversionedOwningRowBuilder
+{
+public:
+ static const int DefaultValueCapacity = 16;
+
+ explicit TUnversionedOwningRowBuilder(int initialValueCapacity = DefaultValueCapacity);
+
+ int AddValue(const TUnversionedValue& value);
+ TUnversionedValue* BeginValues();
+ TUnversionedValue* EndValues();
+
+ TUnversionedOwningRow FinishRow();
+
+private:
+ const int InitialValueCapacity_;
+
+ TBlob RowData_{GetRefCountedTypeCookie<TOwningRowTag>()};
+ TBlob StringData_{GetRefCountedTypeCookie<TOwningRowTag>()};
+
+ TUnversionedRowHeader* GetHeader();
+ TUnversionedValue* GetValue(ui32 index);
+ void Reset();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSharedRange<TRowRange> MakeSingletonRowRange(TLegacyKey lowerBound, TLegacyKey upperBound);
+
+TKeyRef ToKeyRef(TUnversionedRow row);
+TKeyRef ToKeyRef(TUnversionedRow row, int prefixLength);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TUnversionedRow row, TStringBuf format);
+void FormatValue(TStringBuilderBase* builder, TMutableUnversionedRow row, TStringBuf format);
+void FormatValue(TStringBuilderBase* builder, const TUnversionedOwningRow& row, TStringBuf format);
+
+TString ToString(TUnversionedRow row, bool valuesOnly = false);
+TString ToString(TMutableUnversionedRow row, bool valuesOnly = false);
+TString ToString(const TUnversionedOwningRow& row, bool valuesOnly = false);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Hash function may change in future. Use fingerprints for stability.
+struct TDefaultUnversionedValueRangeHash
+{
+ size_t operator()(TUnversionedValueRange range) const;
+};
+
+struct TDefaultUnversionedValueRangeEqual
+{
+ bool operator()(TUnversionedValueRange lhs, TUnversionedValueRange rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Hash function may change in future. Use fingerprints for stability.
+struct TDefaultUnversionedRowHash
+{
+ size_t operator()(TUnversionedRow row) const;
+};
+
+struct TDefaultUnversionedRowEqual
+{
+ bool operator()(TUnversionedRow lhs, TUnversionedRow rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBitwiseUnversionedValueRangeHash
+{
+ size_t operator()(TUnversionedValueRange range) const;
+};
+
+struct TBitwiseUnversionedValueRangeEqual
+{
+ bool operator()(TUnversionedValueRange lhs, TUnversionedValueRange rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBitwiseUnversionedRowHash
+{
+ size_t operator()(TUnversionedRow row) const;
+};
+
+struct TBitwiseUnversionedRowEqual
+{
+ bool operator()(TUnversionedRow lhs, TUnversionedRow rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+//! A hasher for TUnversionedRow.
+template <>
+struct THash<NYT::NTableClient::TUnversionedRow>
+{
+ inline size_t operator()(NYT::NTableClient::TUnversionedRow row) const
+ {
+ return NYT::NTableClient::TDefaultUnversionedRowHash()(row);
+ }
+};
diff --git a/yt/yt/client/table_client/unversioned_value.cpp b/yt/yt/client/table_client/unversioned_value.cpp
new file mode 100644
index 0000000000..9f602ed918
--- /dev/null
+++ b/yt/yt/client/table_client/unversioned_value.cpp
@@ -0,0 +1,239 @@
+#include "unversioned_value.h"
+
+#ifndef YT_COMPILING_UDF
+
+#include "unversioned_row.h"
+#include "composite_compare.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#endif
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStringBuf TUnversionedValue::AsStringBuf() const
+{
+ return TStringBuf(Data.String, Length);
+}
+
+TString TUnversionedValue::AsString() const
+{
+ return TString(Data.String, Length);
+}
+
+TFingerprint GetFarmFingerprint(const TUnversionedValue& value)
+{
+ auto type = value.Type;
+ switch (type) {
+ case EValueType::String:
+ return NYT::FarmFingerprint(value.Data.String, value.Length);
+
+ case EValueType::Int64:
+ return NYT::FarmFingerprint(std::bit_cast<ui64>(value.Data.Int64));
+
+ case EValueType::Uint64:
+ return NYT::FarmFingerprint(value.Data.Uint64);
+
+ case EValueType::Double:
+ return NYT::FarmFingerprint(std::bit_cast<ui64>(value.Data.Double));
+
+ case EValueType::Boolean:
+ return NYT::FarmFingerprint(static_cast<ui64>(value.Data.Boolean));
+
+ case EValueType::Null:
+ return NYT::FarmFingerprint(0);
+
+ case EValueType::Composite:
+ return CompositeFarmHash(NYson::TYsonStringBuf(value.AsStringBuf()));
+
+ default:
+#ifdef YT_COMPILING_UDF
+ YT_ABORT();
+#else
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::UnhashableType,
+ "Cannot hash values of type %Qlv; only scalar types are allowed for key columns",
+ type)
+ << TErrorAttribute("value", value);
+#endif
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void PrintTo(const TUnversionedValue& value, ::std::ostream* os)
+{
+ *os << ToString(value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+void AppendWithCut(TStringBuilderBase* builder, TStringBuf string)
+{
+ constexpr auto Cutoff = 128;
+ if (string.size() <= 2 * Cutoff + 3) {
+ builder->AppendString(string);
+ } else {
+ builder->AppendString(string.substr(0, Cutoff));
+ builder->AppendString("...");
+ builder->AppendString(string.substr(string.size() - Cutoff, Cutoff));
+ }
+}
+
+} // namespace
+
+void FormatValue(TStringBuilderBase* builder, const TUnversionedValue& value, TStringBuf format)
+{
+ using NTableClient::EValueFlags;
+ using NTableClient::EValueType;
+
+ bool noFlags = false;
+ for (char c : format) {
+ noFlags |= c == 'k';
+ }
+
+ if (!noFlags) {
+ if (Any(value.Flags & EValueFlags::Aggregate)) {
+ builder->AppendChar('%');
+ }
+ if (Any(value.Flags & EValueFlags::Hunk)) {
+ builder->AppendChar('&');
+ }
+ builder->AppendFormat("%v#", value.Id);
+ }
+ switch (value.Type) {
+ case EValueType::Null:
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ builder->AppendFormat("<%v>", value.Type);
+ break;
+
+ case EValueType::Int64:
+ builder->AppendFormat("%v", value.Data.Int64);
+ break;
+
+ case EValueType::Uint64:
+ builder->AppendFormat("%vu", value.Data.Uint64);
+ break;
+
+ case EValueType::Double:
+ builder->AppendFormat("%v", value.Data.Double);
+ break;
+
+ case EValueType::Boolean:
+ builder->AppendFormat("%v", value.Data.Boolean);
+ break;
+
+ case EValueType::String: {
+ builder->AppendChar('"');
+ AppendWithCut(builder, value.AsStringBuf());
+ builder->AppendChar('"');
+ break;
+ }
+
+ case EValueType::Any:
+ case EValueType::Composite: {
+ if (value.Type == EValueType::Composite) {
+ // ermolovd@ says "composites" are comparable, in contrast to "any".
+ builder->AppendString("><");
+ }
+
+ auto compositeString = ConvertToYsonString(
+ NYson::TYsonString(value.AsString()),
+ NYson::EYsonFormat::Text);
+
+ AppendWithCut(builder, compositeString.AsStringBuf());
+ break;
+ }
+ }
+}
+
+TString ToString(const TUnversionedValue& value, bool valueOnly)
+{
+ return ToStringViaBuilder(value, valueOnly ? "k" : "");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t TDefaultUnversionedValueHash::operator()(const TUnversionedValue& value) const
+{
+ return GetFarmFingerprint(value);
+}
+
+bool TDefaultUnversionedValueEqual::operator()(const TUnversionedValue& lhs, const TUnversionedValue& rhs) const
+{
+ return lhs == rhs;
+}
+
+size_t TBitwiseUnversionedValueHash::operator()(const TUnversionedValue& value) const
+{
+ size_t result = 0;
+ HashCombine(result, value.Id);
+ HashCombine(result, value.Flags);
+ HashCombine(result, value.Type);
+ switch (value.Type) {
+ case EValueType::Int64:
+ HashCombine(result, value.Data.Int64);
+ break;
+ case EValueType::Uint64:
+ HashCombine(result, value.Data.Uint64);
+ break;
+ case EValueType::Double:
+ HashCombine(result, value.Data.Double);
+ break;
+ case EValueType::Boolean:
+ HashCombine(result, value.Data.Boolean);
+ break;
+ case EValueType::String:
+ case EValueType::Any:
+ case EValueType::Composite:
+ HashCombine(result, value.AsStringBuf());
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+bool TBitwiseUnversionedValueEqual::operator()(const TUnversionedValue& lhs, const TUnversionedValue& rhs) const
+{
+ if (lhs.Id != rhs.Id) {
+ return false;
+ }
+ if (lhs.Flags != rhs.Flags) {
+ return false;
+ }
+ if (lhs.Type != rhs.Type) {
+ return false;
+ }
+ switch (lhs.Type) {
+ case EValueType::Int64:
+ return lhs.Data.Int64 == rhs.Data.Int64;
+ case EValueType::Uint64:
+ return lhs.Data.Uint64 == rhs.Data.Uint64;
+ case EValueType::Double:
+ return lhs.Data.Double == rhs.Data.Double;
+ case EValueType::Boolean:
+ return lhs.Data.Boolean == rhs.Data.Boolean;
+ case EValueType::String:
+ case EValueType::Any:
+ case EValueType::Composite:
+ if (lhs.Length != rhs.Length) {
+ return false;
+ }
+ return ::memcmp(lhs.Data.String, rhs.Data.String, lhs.Length) == 0;
+ default:
+ return true;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/unversioned_value.h b/yt/yt/client/table_client/unversioned_value.h
new file mode 100644
index 0000000000..d4fdc40c9d
--- /dev/null
+++ b/yt/yt/client/table_client/unversioned_value.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include "row_base.h"
+
+#include <library/cpp/yt/farmhash/farm_hash.h>
+
+#include <util/system/defaults.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Wire protocol readers/writer rely on this fixed layout.
+union TUnversionedValueData
+{
+ //! |Int64| value.
+ i64 Int64;
+ //! |Uint64| value.
+ ui64 Uint64;
+ //! |Double| value.
+ double Double;
+ //! |Boolean| value.
+ bool Boolean;
+ //! String value for |String| type or YSON-encoded value for |Any| type.
+ //! NB: string is not zero-terminated, so never use it as a TString.
+ //! Use #TUnversionedValue::AsStringBuf() or #TUnversionedValue::AsString() instead.
+ const char* String;
+};
+
+static_assert(
+ sizeof(TUnversionedValueData) == 8,
+ "TUnversionedValueData has to be exactly 8 bytes.");
+
+// NB: Wire protocol readers/writer rely on this fixed layout.
+struct TUnversionedValue
+{
+ //! Column id w.r.t. the name table.
+ ui16 Id;
+
+ //! Column type.
+ EValueType Type;
+
+ //! Various bit-packed flags.
+ EValueFlags Flags;
+
+ //! Length of a variable-sized value (only meaningful for string-like types).
+ ui32 Length;
+
+ //! Payload.
+ TUnversionedValueData Data;
+
+ //! Assuming #IsStringLikeType(Type), return string data as a TStringBuf.
+ TStringBuf AsStringBuf() const;
+ //! Assuming #IsStringLikeType(Type), return string data as a TString.
+ TString AsString() const;
+};
+
+static_assert(
+ sizeof(TUnversionedValue) == 16,
+ "TUnversionedValue has to be exactly 16 bytes.");
+static_assert(
+ std::is_pod_v<TUnversionedValue>,
+ "TUnversionedValue must be a POD type.");
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Computes FarmHash forever-fixed fingerprint for a given TUnversionedValue.
+TFingerprint GetFarmFingerprint(const TUnversionedValue& value);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Debug printer for Gtest unittests.
+void PrintTo(const TUnversionedValue& value, ::std::ostream* os);
+
+void FormatValue(TStringBuilderBase* builder, const TUnversionedValue& value, TStringBuf format);
+TString ToString(const TUnversionedValue& value, bool valueOnly = false);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Hash function may change in future. Use fingerprints for stability.
+struct TDefaultUnversionedValueHash
+{
+ size_t operator()(const TUnversionedValue& value) const;
+};
+
+struct TDefaultUnversionedValueEqual
+{
+ bool operator()(const TUnversionedValue& lhs, const TUnversionedValue& rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBitwiseUnversionedValueHash
+{
+ size_t operator()(const TUnversionedValue& value) const;
+};
+
+struct TBitwiseUnversionedValueEqual
+{
+ bool operator()(const TUnversionedValue& lhs, const TUnversionedValue& rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+Y_DECLARE_PODTYPE(NYT::NTableClient::TUnversionedValue);
+
+//! A hasher for TUnversionedValue.
+template <>
+struct THash<NYT::NTableClient::TUnversionedValue>
+{
+ inline size_t operator()(const NYT::NTableClient::TUnversionedValue& value) const
+ {
+ return NYT::NTableClient::TDefaultUnversionedValueHash()(value);
+ }
+};
diff --git a/yt/yt/client/table_client/unversioned_writer.h b/yt/yt/client/table_client/unversioned_writer.h
new file mode 100644
index 0000000000..3e41ee3415
--- /dev/null
+++ b/yt/yt/client/table_client/unversioned_writer.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "unversioned_row.h"
+
+#include <yt/yt/client/chunk_client/writer_base.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/misc/range.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*!
+ * Writes non-versioned, fixed-width, strictly typed rowset with given schema.
+ * Useful for: query engine.
+ */
+struct IUnversionedRowsetWriter
+ : public virtual NChunkClient::IWriterBase
+{
+ /*!
+ * Writes given rows.
+ *
+ * The returned value is |true| iff one can write next rowset immediately,
+ * otherwise one should wait for |GetReadyEvent()| future.
+ *
+ * Every row must contain exactly one value for each column in schema, in the same order.
+ */
+ virtual bool Write(TRange<TUnversionedRow> rows) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IUnversionedRowsetWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Writes a schemaless unversioned rowset.
+/*!
+ * Writes unversioned rowset with schema and variable columns.
+ * Useful for: mapreduce jobs, write command.
+ */
+struct IUnversionedWriter
+ : public IUnversionedRowsetWriter
+{
+ virtual const TNameTablePtr& GetNameTable() const = 0;
+ virtual const TTableSchemaPtr& GetSchema() const = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IUnversionedWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/validate_logical_type-inl.h b/yt/yt/client/table_client/validate_logical_type-inl.h
new file mode 100644
index 0000000000..6bcf6a2740
--- /dev/null
+++ b/yt/yt/client/table_client/validate_logical_type-inl.h
@@ -0,0 +1,209 @@
+#ifndef VALIDATE_LOGICAL_TYPE_INL_H_
+#error "Direct inclusion of this file is not allowed, include validate_logical_type.h"
+// For the sake of sane code completion.
+#include "validate_logical_type.h"
+#endif
+
+#include "logical_type.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <util/charset/utf8.h>
+
+#include <cmath>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename TNumber>
+static Y_FORCE_INLINE void ValidateNumericRange(TNumber value, TNumber min, TNumber max)
+{
+ static_assert(std::is_same_v<TNumber, i64> || std::is_same_v<TNumber, ui64> || std::is_same_v<TNumber, double>);
+ if (value < min || value > max) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::SchemaViolation,
+ "Value %v is out of allowed range [%v, %v]",
+ value,
+ min,
+ max);
+ }
+}
+
+template <ESimpleLogicalValueType type>
+static constexpr auto GetLogicalTypeMax()
+{
+ // Ints
+ if constexpr (type == ESimpleLogicalValueType::Int8) {
+ return static_cast<i64>(Max<i8>());
+ } else if constexpr (type == ESimpleLogicalValueType::Int16) {
+ return static_cast<i64>(Max<i16>());
+ } else if constexpr (type == ESimpleLogicalValueType::Int32) {
+ return static_cast<i64>(Max<i32>());
+ } else if constexpr (type == ESimpleLogicalValueType::Int64) {
+ return static_cast<i64>(Max<i64>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint8) { // Uints
+ return static_cast<ui64>(Max<ui8>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint16) {
+ return static_cast<ui64>(Max<ui16>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint32) {
+ return static_cast<ui64>(Max<ui32>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint64) {
+ return static_cast<ui64>(Max<ui64>());
+ } else if constexpr (type == ESimpleLogicalValueType::Date) { // Time types
+ return static_cast<ui64>(DateUpperBound - 1);
+ } else if constexpr (type == ESimpleLogicalValueType::Datetime) {
+ return static_cast<ui64>(DatetimeUpperBound - 1);
+ } else if constexpr (type == ESimpleLogicalValueType::Timestamp) {
+ return static_cast<ui64>(TimestampUpperBound - 1);
+ } else if constexpr (type == ESimpleLogicalValueType::Interval) {
+ return static_cast<i64>(TimestampUpperBound - 1);
+ } else if constexpr (type == ESimpleLogicalValueType::Float) {
+ return static_cast<double>(Max<float>());
+ } else {
+ // silly replacement for static_assert(false, ...);
+ static_assert(type == ESimpleLogicalValueType::Int8, "unsupported type");
+ }
+}
+
+template <ESimpleLogicalValueType type>
+static constexpr auto GetLogicalTypeMin()
+{
+ // Ints
+ if constexpr (type == ESimpleLogicalValueType::Int8) {
+ return static_cast<i64>(Min<i8>());
+ } else if constexpr (type == ESimpleLogicalValueType::Int16) {
+ return static_cast<i64>(Min<i16>());
+ } else if constexpr (type == ESimpleLogicalValueType::Int32) {
+ return static_cast<i64>(Min<i32>());
+ } else if constexpr (type == ESimpleLogicalValueType::Int64) {
+ return static_cast<i64>(Min<i64>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint8) { // Uints
+ return static_cast<ui64>(Min<ui8>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint16) {
+ return static_cast<ui64>(Min<ui16>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint32) {
+ return static_cast<ui64>(Min<ui32>());
+ } else if constexpr (type == ESimpleLogicalValueType::Uint64) {
+ return static_cast<ui64>(Min<ui64>());
+ } else if constexpr (type == ESimpleLogicalValueType::Date) { // Time types
+ return static_cast<ui64>(0);
+ } else if constexpr (type == ESimpleLogicalValueType::Datetime) {
+ return static_cast<ui64>(0);
+ } else if constexpr (type == ESimpleLogicalValueType::Timestamp) {
+ return static_cast<ui64>(0);
+ } else if constexpr (type == ESimpleLogicalValueType::Interval) {
+ return static_cast<i64>(-TimestampUpperBound + 1);
+ } else if constexpr (type == ESimpleLogicalValueType::Float) { // Floating point
+ return static_cast<double>(std::numeric_limits<float>::lowest());
+ } else {
+ // silly replacement for static_assert(false, ...);
+ static_assert(type == ESimpleLogicalValueType::Int8, "unsupported type");
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(i64 value)
+{
+ if constexpr (
+ type == ESimpleLogicalValueType::Int8 ||
+ type == ESimpleLogicalValueType::Int16 ||
+ type == ESimpleLogicalValueType::Int32 ||
+ type == ESimpleLogicalValueType::Interval)
+ {
+ NDetail::ValidateNumericRange(
+ value,
+ NDetail::GetLogicalTypeMin<type>(),
+ NDetail::GetLogicalTypeMax<type>());
+ } else {
+ static_assert(type == ESimpleLogicalValueType::Int64, "Bad logical type");
+ // Do nothing since Int64 doesn't require validation
+ }
+}
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(ui64 value)
+{
+ if constexpr (
+ type == ESimpleLogicalValueType::Uint8 ||
+ type == ESimpleLogicalValueType::Uint16 ||
+ type == ESimpleLogicalValueType::Uint32 ||
+ type == ESimpleLogicalValueType::Date ||
+ type == ESimpleLogicalValueType::Datetime ||
+ type == ESimpleLogicalValueType::Timestamp)
+ {
+ NDetail::ValidateNumericRange(
+ value,
+ NDetail::GetLogicalTypeMin<type>(),
+ NDetail::GetLogicalTypeMax<type>());
+ } else {
+ static_assert(type == ESimpleLogicalValueType::Uint64, "Bad logical type");
+ // Do nothing since Uint64 doesn't require validation
+ }
+}
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(double value)
+{
+ if constexpr (type == ESimpleLogicalValueType::Float) {
+ if (!std::isinf(value) && !std::isnan(value)) {
+ NDetail::ValidateNumericRange(
+ value,
+ NDetail::GetLogicalTypeMin<type>(),
+ NDetail::GetLogicalTypeMax<type>());
+ }
+ } else if constexpr (type == ESimpleLogicalValueType::Double) {
+ // do nothing
+ } else {
+ static_assert(type == ESimpleLogicalValueType::Double, "Bad logical type");
+ }
+}
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(bool /*value*/)
+{
+ if constexpr (type == ESimpleLogicalValueType::Boolean) {
+ // do nothing
+ } else {
+ static_assert(type == ESimpleLogicalValueType::Boolean, "Bad logical type");
+ }
+}
+
+template <ESimpleLogicalValueType type>
+void ValidateSimpleLogicalType(TStringBuf value)
+{
+ if constexpr (type == ESimpleLogicalValueType::String) {
+ // do nothing
+ } else if constexpr (type == ESimpleLogicalValueType::Utf8) {
+ if (UTF8Detect(value.data(), value.size()) == NotUTF8) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::SchemaViolation,
+ "Not a valid utf8 string");
+ }
+ } else if constexpr (type == ESimpleLogicalValueType::Uuid) {
+ if (value.size() != 16) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::SchemaViolation,
+ "Not a valid Uuid");
+ }
+ } else {
+ static_assert(type == ESimpleLogicalValueType::String, "Bad logical type");
+ }
+}
+
+template <>
+void ValidateSimpleLogicalType<ESimpleLogicalValueType::Json>(TStringBuf value);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/validate_logical_type.cpp b/yt/yt/client/table_client/validate_logical_type.cpp
new file mode 100644
index 0000000000..8dc20d94e2
--- /dev/null
+++ b/yt/yt/client/table_client/validate_logical_type.cpp
@@ -0,0 +1,635 @@
+#include "validate_logical_type.h"
+#include "logical_type.h"
+
+#include <yt/yt/library/decimal/decimal.h>
+
+#include <yt/yt/core/misc/finally.h>
+
+#include <yt/yt/core/yson/pull_parser.h>
+
+#include <util/stream/mem.h>
+#include <util/generic/adaptor.h>
+
+#include <library/cpp/json/json_reader.h>
+
+namespace NYT::NTableClient {
+
+using namespace NYson;
+using namespace NJson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <EValueType physicalType>
+Y_FORCE_INLINE constexpr EYsonItemType ExpectedYsonItemType()
+{
+ if constexpr (physicalType == EValueType::Boolean) {
+ return EYsonItemType::BooleanValue;
+ } else if constexpr (physicalType == EValueType::Int64) {
+ return EYsonItemType::Int64Value;
+ } else if constexpr (physicalType == EValueType::Uint64) {
+ return EYsonItemType::Uint64Value;
+ } else if constexpr (physicalType == EValueType::Double) {
+ return EYsonItemType::DoubleValue;
+ } else if constexpr (physicalType == EValueType::String) {
+ return EYsonItemType::StringValue;
+ } else if constexpr (physicalType == EValueType::Null) {
+ return EYsonItemType::EntityValue;
+ } else {
+ static_assert(physicalType == EValueType::Boolean, "Unexpected value type");
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TComplexLogicalTypeValidatorImpl
+{
+ class TFieldId;
+
+public:
+ TComplexLogicalTypeValidatorImpl(TYsonPullParser* parser, TComplexTypeFieldDescriptor descriptor)
+ : Cursor_(parser)
+ , RootDescriptor_(descriptor)
+ { }
+
+ void Validate()
+ {
+ return ValidateLogicalType(RootDescriptor_.GetType(), TFieldId());
+ }
+
+private:
+ void ValidateLogicalType(const TLogicalTypePtr& type, const TFieldId& fieldId)
+ {
+ switch (type->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ ValidateSimpleType(type->UncheckedAsSimpleTypeRef().GetElement(), fieldId);
+ return;
+ case ELogicalMetatype::Optional:
+ ValidateOptionalType(type->UncheckedAsOptionalTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::List:
+ ValidateListType(type->UncheckedAsListTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::Struct:
+ ValidateStructType(type->UncheckedAsStructTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::Tuple:
+ ValidateTupleType(type->UncheckedAsTupleTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::VariantStruct:
+ ValidateVariantStructType(type->UncheckedAsVariantStructTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::VariantTuple:
+ ValidateVariantTupleType(type->UncheckedAsVariantTupleTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::Dict:
+ ValidateDictType(type->UncheckedAsDictTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::Tagged:
+ ValidateTaggedType(type->UncheckedAsTaggedTypeRef(), fieldId);
+ return;
+ case ELogicalMetatype::Decimal:
+ ValidateDecimalType(type->UncheckedAsDecimalTypeRef(), fieldId);
+ return;
+ }
+ YT_ABORT();
+ }
+
+ void ThrowUnexpectedYsonToken(EYsonItemType type, const TFieldId& fieldId)
+ {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ type,
+ Cursor_.GetCurrent().GetType());
+ }
+
+ Y_FORCE_INLINE void ValidateYsonTokenType(EYsonItemType type, const TFieldId& fieldId)
+ {
+ if (Cursor_.GetCurrent().GetType() != type) {
+ ThrowUnexpectedYsonToken(type, fieldId);
+ }
+ }
+
+ template <ESimpleLogicalValueType type>
+ void ValidateSimpleType(const TFieldId& fieldId)
+ {
+ if constexpr (type == ESimpleLogicalValueType::Any) {
+ switch (Cursor_.GetCurrent().GetType()) {
+ case EYsonItemType::EntityValue:
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; unexpected entity value",
+ GetDescription(fieldId));
+ case EYsonItemType::Int64Value:
+ case EYsonItemType::BooleanValue:
+ case EYsonItemType::Uint64Value:
+ case EYsonItemType::DoubleValue:
+ case EYsonItemType::StringValue:
+ Cursor_.Next();
+ return;
+ case EYsonItemType::BeginAttributes:
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; unexpected top level attributes",
+ GetDescription(fieldId));
+
+ case EYsonItemType::BeginList:
+ case EYsonItemType::BeginMap: {
+ Cursor_.SkipComplexValue();
+ return;
+ }
+ default:
+ YT_ABORT();
+ }
+ } else {
+ static_assert(type != ESimpleLogicalValueType::Any);
+ constexpr auto expectedYsonEventType = ExpectedYsonItemType<GetPhysicalType(type)>();
+ if (Cursor_.GetCurrent().GetType() != expectedYsonEventType) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ expectedYsonEventType,
+ Cursor_.GetCurrent().GetType());
+ }
+
+ if constexpr (expectedYsonEventType == EYsonItemType::EntityValue) {
+ // nothing to check
+ } else if constexpr (expectedYsonEventType == EYsonItemType::BooleanValue) {
+ NTableClient::ValidateSimpleLogicalType<type>(Cursor_.GetCurrent().UncheckedAsBoolean());
+ } else if constexpr (expectedYsonEventType == EYsonItemType::Int64Value) {
+ NTableClient::ValidateSimpleLogicalType<type>(Cursor_.GetCurrent().UncheckedAsInt64());
+ } else if constexpr (expectedYsonEventType == EYsonItemType::Uint64Value) {
+ NTableClient::ValidateSimpleLogicalType<type>(Cursor_.GetCurrent().UncheckedAsUint64());
+ } else if constexpr (expectedYsonEventType == EYsonItemType::DoubleValue) {
+ NTableClient::ValidateSimpleLogicalType<type>(Cursor_.GetCurrent().UncheckedAsDouble());
+ } else if constexpr (expectedYsonEventType == EYsonItemType::StringValue) {
+ NTableClient::ValidateSimpleLogicalType<type>(Cursor_.GetCurrent().UncheckedAsString());
+ } else {
+ static_assert(expectedYsonEventType == EYsonItemType::EntityValue, "unexpected EYsonItemType");
+ }
+ Cursor_.Next();
+ }
+ }
+
+ Y_FORCE_INLINE void ValidateSimpleType(ESimpleLogicalValueType type, const TFieldId& fieldId)
+ {
+ switch (type) {
+#define CASE(x) \
+ case x: \
+ ValidateSimpleType<x>(fieldId); \
+ return;
+ CASE(ESimpleLogicalValueType::Null)
+ CASE(ESimpleLogicalValueType::Int64)
+ CASE(ESimpleLogicalValueType::Uint64)
+ CASE(ESimpleLogicalValueType::Double)
+ CASE(ESimpleLogicalValueType::Float)
+ CASE(ESimpleLogicalValueType::Boolean)
+ CASE(ESimpleLogicalValueType::String)
+ CASE(ESimpleLogicalValueType::Any)
+ CASE(ESimpleLogicalValueType::Json)
+ CASE(ESimpleLogicalValueType::Int8)
+ CASE(ESimpleLogicalValueType::Uint8)
+ CASE(ESimpleLogicalValueType::Int16)
+ CASE(ESimpleLogicalValueType::Uint16)
+ CASE(ESimpleLogicalValueType::Int32)
+ CASE(ESimpleLogicalValueType::Uint32)
+ CASE(ESimpleLogicalValueType::Utf8)
+ CASE(ESimpleLogicalValueType::Date)
+ CASE(ESimpleLogicalValueType::Datetime)
+ CASE(ESimpleLogicalValueType::Timestamp)
+ CASE(ESimpleLogicalValueType::Interval)
+ CASE(ESimpleLogicalValueType::Void)
+ CASE(ESimpleLogicalValueType::Uuid)
+#undef CASE
+ }
+ YT_ABORT();
+ }
+
+ void ValidateOptionalType(const TOptionalLogicalType& type, const TFieldId& fieldId)
+ {
+ if (Cursor_.GetCurrent().GetType() == EYsonItemType::EntityValue) {
+ Cursor_.Next();
+ return;
+ }
+
+ if (!type.IsElementNullable()) {
+ ValidateLogicalType(type.GetElement(), fieldId.OptionalElement());
+ return;
+ }
+
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::BeginList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::BeginList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ if (Cursor_.GetCurrent().GetType() == EYsonItemType::EndList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; empty yson",
+ GetDescription(fieldId),
+ Cursor_.GetCurrent().GetType());
+ }
+ ValidateLogicalType(type.GetElement(), fieldId.OptionalElement());
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::EndList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::EndList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ }
+
+ void ValidateListType(const TListLogicalType& type, const TFieldId& fieldId)
+ {
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::BeginList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::BeginList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ const auto& elementType = type.GetElement();
+ auto elementFieldId = fieldId.ListElement();
+ while (Cursor_.GetCurrent().GetType() != EYsonItemType::EndList) {
+ ValidateLogicalType(elementType, elementFieldId);
+ }
+ Cursor_.Next();
+ }
+
+ void ValidateStructType(const TStructLogicalType& type, const TFieldId& fieldId)
+ {
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::BeginList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::BeginList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ const auto& fields = type.GetFields();
+ for (size_t i = 0; i < fields.size(); ++i) {
+ if (Cursor_.GetCurrent().GetType() == EYsonItemType::EndList) {
+ do {
+ const auto& field = fields[i];
+ if (field.Type->GetMetatype() != ELogicalMetatype::Optional) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; struct ended before required field %Qv is set",
+ GetDescription(fieldId),
+ field.Name);
+ }
+ ++i;
+ } while (i < fields.size());
+ break;
+ }
+ const auto& field = fields[i];
+ ValidateLogicalType(field.Type, fieldId.StructField(i));
+ }
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::EndList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::EndList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ }
+
+ void ValidateTupleType(const TTupleLogicalType& type, const TFieldId& fieldId)
+ {
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::BeginList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::BeginList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ const auto& elements = type.GetElements();
+ for (size_t i = 0; i < elements.size(); ++i) {
+ if (Cursor_.GetCurrent().GetType() == EYsonItemType::EndList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; expected %Qv got %Qv",
+ GetDescription(fieldId),
+ GetDescription(fieldId.TupleElement(i)),
+ EYsonItemType::EndList);
+ }
+ ValidateLogicalType(elements[i], fieldId.TupleElement(i));
+ }
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::EndList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::EndList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ }
+
+ template <typename T>
+ Y_FORCE_INLINE void ValidateVariantTypeImpl(const T& type, const TFieldId& fieldId)
+ {
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::BeginList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::BeginList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::Int64Value) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::Int64Value,
+ Cursor_.GetCurrent().GetType());
+ }
+ const auto alternativeIndex = Cursor_.GetCurrent().UncheckedAsInt64();
+ Cursor_.Next();
+ if constexpr (std::is_same_v<T, TVariantTupleLogicalType>) {
+ const auto& elements = type.GetElements();
+ if (alternativeIndex < 0) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; variant alternative index %Qv is less than 0",
+ GetDescription(fieldId),
+ alternativeIndex);
+ }
+ if (alternativeIndex >= std::ssize(elements)) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; variant alternative index %Qv exceeds number of variant elements %Qv",
+ GetDescription(fieldId),
+ alternativeIndex,
+ elements.size());
+ }
+ ValidateLogicalType(elements[alternativeIndex], fieldId.VariantTupleElement(alternativeIndex));
+ } else {
+ static_assert(std::is_same_v<T, TVariantStructLogicalType>);
+ const auto& fields = type.GetFields();
+ if (alternativeIndex < 0) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; variant alternative index %Qv is less than 0",
+ GetDescription(fieldId),
+ alternativeIndex);
+ }
+ if (alternativeIndex >= std::ssize(fields)) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv; variant alternative index %Qv exceeds number of variant elements %Qv",
+ GetDescription(fieldId),
+ alternativeIndex,
+ fields.size());
+ }
+ ValidateLogicalType(fields[alternativeIndex].Type, fieldId.VariantStructField(alternativeIndex));
+ }
+
+ if (Cursor_.GetCurrent().GetType() != EYsonItemType::EndList) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation,
+ "Cannot parse %Qv: expected %Qlv, found %Qlv",
+ GetDescription(fieldId),
+ EYsonItemType::EndList,
+ Cursor_.GetCurrent().GetType());
+ }
+ Cursor_.Next();
+ }
+
+ void ValidateVariantTupleType(const TVariantTupleLogicalType& type, const TFieldId& fieldId)
+ {
+ ValidateVariantTypeImpl(type, fieldId);
+ }
+
+ void ValidateVariantStructType(const TVariantStructLogicalType& type, const TFieldId& fieldId)
+ {
+ ValidateVariantTypeImpl(type, fieldId);
+ }
+
+ void ValidateDictType(const TDictLogicalType& type, const TFieldId& fieldId)
+ {
+ ValidateYsonTokenType(EYsonItemType::BeginList, fieldId);
+ Cursor_.Next();
+ while (Cursor_.GetCurrent().GetType() != EYsonItemType::EndList) {
+ ValidateYsonTokenType(EYsonItemType::BeginList, fieldId);
+ Cursor_.Next();
+
+ ValidateLogicalType(type.GetKey(), fieldId.DictKey());
+ ValidateLogicalType(type.GetValue(), fieldId.DictValue());
+
+ ValidateYsonTokenType(EYsonItemType::EndList, fieldId);
+ Cursor_.Next();
+ }
+ Cursor_.Next();
+ }
+
+ Y_FORCE_INLINE void ValidateTaggedType(const TTaggedLogicalType& type, const TFieldId& fieldId)
+ {
+ ValidateLogicalType(type.GetElement(), fieldId.TaggedElement());
+ }
+
+ Y_FORCE_INLINE void ValidateDecimalType(const TDecimalLogicalType& type, const TFieldId& fieldId)
+ {
+ ValidateYsonTokenType(EYsonItemType::StringValue, fieldId);
+ try {
+ NDecimal::TDecimal::ValidateBinaryValue(
+ Cursor_.GetCurrent().UncheckedAsString(),
+ type.GetPrecision(),
+ type.GetScale());
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation, "Error validating field %Qv",
+ GetDescription(fieldId))
+ << ex;
+ }
+ Cursor_.Next();
+ }
+
+ TString GetDescription(const TFieldId& fieldId) const
+ {
+ return fieldId.GetDescriptor(RootDescriptor_).GetDescription();
+ }
+
+private:
+ class TFieldId
+ {
+ public:
+ // Root field id
+ TFieldId() = default;
+
+ TFieldId OptionalElement() const
+ {
+ return {this, 0};
+ }
+
+ TFieldId ListElement() const
+ {
+ return {this, 0};
+ }
+
+ TFieldId StructField(int i) const
+ {
+ return {this, i};
+ }
+
+ TFieldId TupleElement(int i) const
+ {
+ return {this, i};
+ }
+
+ TFieldId VariantStructField(int i) const
+ {
+ return {this, i};
+ }
+
+ TFieldId VariantTupleElement(int i) const
+ {
+ return {this, i};
+ }
+
+ TFieldId DictKey() const
+ {
+ return {this, 0};
+ }
+
+ TFieldId DictValue() const
+ {
+ return {this, 1};
+ }
+
+ TFieldId TaggedElement() const
+ {
+ return {this, 0};
+ }
+
+ TComplexTypeFieldDescriptor GetDescriptor(const TComplexTypeFieldDescriptor& root) const
+ {
+ std::vector<int> path;
+ const auto* current = this;
+ while (current->Parent_ != nullptr) {
+ path.push_back(current->SiblingIndex_);
+ current = current->Parent_;
+ }
+
+ auto descriptor = root;
+ for (const auto& childIndex : Reversed(path)) {
+ const auto& type = descriptor.GetType();
+ switch (type->GetMetatype()) {
+ case ELogicalMetatype::Simple:
+ case ELogicalMetatype::Decimal:
+ return descriptor;
+ case ELogicalMetatype::Optional:
+ descriptor = descriptor.OptionalElement();
+ continue;
+ case ELogicalMetatype::List:
+ descriptor = descriptor.ListElement();
+ continue;
+ case ELogicalMetatype::Struct:
+ descriptor = descriptor.StructField(childIndex);
+ continue;
+ case ELogicalMetatype::Tuple:
+ descriptor = descriptor.TupleElement(childIndex);
+ continue;
+ case ELogicalMetatype::VariantStruct:
+ descriptor = descriptor.VariantStructField(childIndex);
+ continue;
+ case ELogicalMetatype::VariantTuple:
+ descriptor = descriptor.VariantTupleElement(childIndex);
+ continue;
+ case ELogicalMetatype::Dict:
+ switch (childIndex) {
+ case 0:
+ descriptor = descriptor.DictKey();
+ continue;
+ case 1:
+ descriptor = descriptor.DictValue();
+ continue;
+ }
+ break;
+ case ELogicalMetatype::Tagged:
+ descriptor = descriptor.TaggedElement();
+ continue;
+ }
+ YT_ABORT();
+ }
+ return descriptor;
+ }
+
+ private:
+ TFieldId(const TFieldId* parent, int siblingIndex)
+ : Parent_(parent)
+ , SiblingIndex_(siblingIndex)
+ { }
+
+ private:
+ const TFieldId* Parent_ = nullptr;
+ int SiblingIndex_ = 0;
+ };
+
+private:
+ TYsonPullParserCursor Cursor_;
+ const TComplexTypeFieldDescriptor RootDescriptor_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateComplexLogicalType(TStringBuf ysonData, const TLogicalTypePtr& type)
+{
+ TMemoryInput in(ysonData);
+ TYsonPullParser parser(&in, EYsonType::Node);
+ TComplexLogicalTypeValidatorImpl validator(&parser, TComplexTypeFieldDescriptor(type));
+ validator.Validate();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TValidateJsonCallbacks
+ : public TJsonCallbacks
+{
+public:
+ TValidateJsonCallbacks()
+ : TJsonCallbacks(/* throwOnError */ true)
+ {}
+
+ bool OnDouble(double value) final
+ {
+ if (Y_UNLIKELY(std::isinf(value))) {
+ ythrow TJsonException() << "infinite values are not allowed";
+ }
+ return true;
+ }
+
+ bool OnEnd() final
+ {
+ if (Finished_) {
+ ythrow TJsonException() << "JSON value is already finished";
+ }
+ Finished_ = true;
+ return true;
+ }
+
+private:
+ bool Finished_ = false;
+};
+
+template <>
+void ValidateSimpleLogicalType<ESimpleLogicalValueType::Json>(TStringBuf value)
+{
+ TMemoryInput input(value);
+ TValidateJsonCallbacks callbacks;
+ try {
+ auto ok = ReadJson(&input, &callbacks);
+ // We expect all the errors to be thrown.
+ YT_VERIFY(ok);
+ } catch (const TJsonException& ex) {
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation, "Invalid JSON: %s", ex.AsStrBuf());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/validate_logical_type.h b/yt/yt/client/table_client/validate_logical_type.h
new file mode 100644
index 0000000000..add327a812
--- /dev/null
+++ b/yt/yt/client/table_client/validate_logical_type.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "public.h"
+#include "logical_type.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr ui64 DateUpperBound = 49673ull;
+constexpr ui64 DatetimeUpperBound = DateUpperBound * 86400ull;
+constexpr ui64 TimestampUpperBound = DatetimeUpperBound * 1000000ull;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(i64 value);
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(ui64 value);
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(double value);
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(bool value);
+
+template <ESimpleLogicalValueType type>
+Y_FORCE_INLINE void ValidateSimpleLogicalType(TStringBuf value);
+
+// Validates complex logical type yson representation.
+void ValidateComplexLogicalType(TStringBuf ysonData, const TLogicalTypePtr& type);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
+#define VALIDATE_LOGICAL_TYPE_INL_H_
+#include "validate_logical_type-inl.h"
+#undef VALIDATE_LOGICAL_TYPE_INL_H_
diff --git a/yt/yt/client/table_client/value_consumer.cpp b/yt/yt/client/table_client/value_consumer.cpp
new file mode 100644
index 0000000000..ea097ea2e8
--- /dev/null
+++ b/yt/yt/client/table_client/value_consumer.cpp
@@ -0,0 +1,388 @@
+#include "value_consumer.h"
+#include "helpers.h"
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+
+#include <yt/yt/core/concurrency/scheduler.h>
+
+#include <util/string/cast.h>
+
+namespace NYT::NTableClient {
+
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool ConvertToBooleanValue(TStringBuf stringValue)
+{
+ if (stringValue == "true") {
+ return true;
+ } else if (stringValue == "false") {
+ return false;
+ } else {
+ THROW_ERROR_EXCEPTION("Unable to convert value to boolean")
+ << TErrorAttribute("value", stringValue);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TValueConsumerBase::TValueConsumerBase(
+ TTableSchemaPtr schema,
+ TTypeConversionConfigPtr typeConversionConfig)
+ : Schema_(std::move(schema))
+ , TypeConversionConfig_(std::move(typeConversionConfig))
+{ }
+
+void TValueConsumerBase::InitializeIdToTypeMapping()
+{
+ const auto& nameTable = GetNameTable();
+ for (const auto& column : Schema_->Columns()) {
+ int id = nameTable->GetIdOrRegisterName(column.Name());
+ if (id >= static_cast<int>(NameTableIdToType_.size())) {
+ NameTableIdToType_.resize(id + 1, EValueType::Any);
+ }
+ NameTableIdToType_[id] = column.GetWireType();
+ }
+}
+
+template <typename T>
+void TValueConsumerBase::ProcessIntegralValue(const TUnversionedValue& value, EValueType columnType)
+{
+ auto integralValue = FromUnversionedValue<T>(value);
+ if (TypeConversionConfig_->EnableAllToStringConversion && columnType == EValueType::String) {
+ char buf[64];
+ char* end = buf + 64;
+ char* start = WriteDecIntToBufferBackwards(end, integralValue);
+ OnMyValue(MakeUnversionedStringValue(TStringBuf(start, end), value.Id));
+ } else if (TypeConversionConfig_->EnableIntegralToDoubleConversion && columnType == EValueType::Double) {
+ OnMyValue(MakeUnversionedDoubleValue(static_cast<double>(integralValue), value.Id));
+ } else {
+ OnMyValue(value);
+ }
+}
+
+void TValueConsumerBase::ProcessInt64Value(const TUnversionedValue& value, EValueType columnType)
+{
+ if (TypeConversionConfig_->EnableIntegralTypeConversion && columnType == EValueType::Uint64) {
+ i64 integralValue = value.Data.Int64;
+ if (integralValue < 0) {
+ ThrowConversionException(
+ value,
+ columnType,
+ TError("Unable to convert negative int64 to uint64")
+ << TErrorAttribute("value", integralValue));
+ } else {
+ OnMyValue(MakeUnversionedUint64Value(static_cast<ui64>(integralValue), value.Id));
+ }
+ } else {
+ ProcessIntegralValue<i64>(value, columnType);
+ }
+}
+
+void TValueConsumerBase::ProcessUint64Value(const TUnversionedValue& value, EValueType columnType)
+{
+ if (TypeConversionConfig_->EnableIntegralTypeConversion && columnType == EValueType::Int64) {
+ ui64 integralValue = value.Data.Uint64;
+ if (integralValue > std::numeric_limits<i64>::max()) {
+ ThrowConversionException(
+ value,
+ columnType,
+ TError("Unable to convert uint64 to int64 as it leads to an overflow")
+ << TErrorAttribute("value", integralValue));
+ } else {
+ OnMyValue(MakeUnversionedInt64Value(static_cast<i64>(integralValue), value.Id));
+ }
+ } else {
+ ProcessIntegralValue<ui64>(value, columnType);
+ }
+}
+
+void TValueConsumerBase::ProcessBooleanValue(const TUnversionedValue& value, EValueType columnType)
+{
+ if (TypeConversionConfig_->EnableAllToStringConversion && columnType == EValueType::String) {
+ TStringBuf stringValue = value.Data.Boolean ? "true" : "false";
+ OnMyValue(MakeUnversionedStringValue(stringValue, value.Id));
+ } else {
+ OnMyValue(value);
+ }
+}
+
+void TValueConsumerBase::ProcessDoubleValue(const TUnversionedValue& value, EValueType columnType)
+{
+ if (TypeConversionConfig_->EnableAllToStringConversion && columnType == EValueType::String) {
+ char buf[64];
+ auto length = FloatToString(value.Data.Double, buf, sizeof(buf));
+ TStringBuf stringValue(buf, buf + length);
+ OnMyValue(MakeUnversionedStringValue(stringValue, value.Id));
+ } else {
+ OnMyValue(value);
+ }
+}
+
+void TValueConsumerBase::ProcessStringValue(const TUnversionedValue& value, EValueType columnType)
+{
+ if (TypeConversionConfig_->EnableStringToAllConversion) {
+ TUnversionedValue convertedValue;
+ TStringBuf stringValue(value.Data.String, value.Length);
+ try {
+ switch (columnType) {
+ case EValueType::Int64:
+ case EValueType::Uint64: {
+ auto adjustedStringValue = stringValue;
+ if (!stringValue.empty() && stringValue.back() == 'u') {
+ adjustedStringValue = TStringBuf(value.Data.String, value.Length - 1);
+ }
+ if (columnType == EValueType::Int64) {
+ convertedValue = MakeUnversionedInt64Value(FromString<i64>(adjustedStringValue), value.Id);
+ } else {
+ convertedValue = MakeUnversionedUint64Value(FromString<ui64>(adjustedStringValue), value.Id);
+ }
+ break;
+ }
+ case EValueType::Double:
+ convertedValue = MakeUnversionedDoubleValue(FromString<double>(stringValue), value.Id);
+ break;
+ case EValueType::Boolean:
+ convertedValue = MakeUnversionedBooleanValue(ConvertToBooleanValue(stringValue), value.Id);
+ break;
+ default:
+ convertedValue = value;
+ break;
+ }
+ } catch (const std::exception& ex) {
+ ThrowConversionException(value, columnType, TError(ex));
+ }
+ OnMyValue(convertedValue);
+ } else {
+ OnMyValue(value);
+ }
+}
+
+void TValueConsumerBase::OnValue(const TUnversionedValue& value)
+{
+ EValueType columnType;
+ if (NameTableIdToType_.size() <= value.Id) {
+ columnType = EValueType::Any;
+ } else {
+ columnType = NameTableIdToType_[value.Id];
+ }
+
+ switch (value.Type) {
+ case EValueType::Int64:
+ ProcessInt64Value(value, columnType);
+ break;
+ case EValueType::Uint64:
+ ProcessUint64Value(value, columnType);
+ break;
+ case EValueType::Boolean:
+ ProcessBooleanValue(value, columnType);
+ break;
+ case EValueType::Double:
+ ProcessDoubleValue(value, columnType);
+ break;
+ case EValueType::String:
+ ProcessStringValue(value, columnType);
+ break;
+ default:
+ OnMyValue(value);
+ break;
+ }
+}
+
+const TTableSchemaPtr& TValueConsumerBase::GetSchema() const
+{
+ return Schema_;
+}
+
+void TValueConsumerBase::ThrowConversionException(const TUnversionedValue& value, EValueType columnType, const TError& ex)
+{
+ THROW_ERROR_EXCEPTION(EErrorCode::SchemaViolation, "Error while performing type conversion")
+ << ex
+ << TErrorAttribute("column", GetNameTable()->GetName(value.Id))
+ << TErrorAttribute("value_type", value.Type)
+ << TErrorAttribute("column_type", columnType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TBuildingValueConsumer::TBuildingValueConsumer(
+ TTableSchemaPtr schema,
+ NLogging::TLogger logger,
+ bool convertNullToEntity,
+ TTypeConversionConfigPtr typeConversionConfig)
+ : TValueConsumerBase(std::move(schema), std::move(typeConversionConfig))
+ , Logger(std::move(logger))
+ , NameTable_(TNameTable::FromSchema(*Schema_))
+ , ConvertNullToEntity_(convertNullToEntity)
+ , WrittenFlags_(NameTable_->GetSize())
+{
+ InitializeIdToTypeMapping();
+}
+
+std::vector<TUnversionedRow> TBuildingValueConsumer::GetRows() const
+{
+ std::vector<TUnversionedRow> result;
+ result.reserve(Rows_.size());
+ for (const auto& row : Rows_) {
+ result.push_back(row);
+ }
+ return result;
+}
+
+void TBuildingValueConsumer::SetAggregate(bool value)
+{
+ Aggregate_ = value;
+}
+
+void TBuildingValueConsumer::SetTreatMissingAsNull(bool value)
+{
+ TreatMissingAsNull_ = value;
+}
+
+void TBuildingValueConsumer::SetAllowMissingKeyColumns(bool value)
+{
+ AllowMissingKeyColumns_ = value;
+}
+
+const TNameTablePtr& TBuildingValueConsumer::GetNameTable() const
+{
+ return NameTable_;
+}
+
+bool TBuildingValueConsumer::GetAllowUnknownColumns() const
+{
+ return false;
+}
+
+void TBuildingValueConsumer::OnBeginRow()
+{
+ // Do nothing.
+}
+
+void TBuildingValueConsumer::OnMyValue(const TUnversionedValue& value)
+{
+ if (value.Id >= Schema_->GetColumnCount()) {
+ return;
+ }
+ auto valueCopy = value;
+ const auto& columnSchema = Schema_->Columns()[valueCopy.Id];
+ if (columnSchema.Aggregate() && Aggregate_) {
+ valueCopy.Flags |= EValueFlags::Aggregate;
+ }
+ if (columnSchema.IsOfV1Type(ESimpleLogicalValueType::Any) &&
+ valueCopy.Type != EValueType::Any &&
+ (valueCopy.Type != EValueType::Null || ConvertNullToEntity_))
+ {
+ if (valueCopy.Type == EValueType::Null && LogNullToEntity_) {
+ YT_LOG_DEBUG("Detected conversion of null to YSON entity");
+ LogNullToEntity_ = false;
+ }
+ Builder_.AddValue(EncodeUnversionedAnyValue(valueCopy, &MemoryPool_));
+ MemoryPool_.Clear();
+ } else {
+ Builder_.AddValue(valueCopy);
+ }
+ WrittenFlags_[valueCopy.Id] = true;
+}
+
+void TBuildingValueConsumer::OnEndRow()
+{
+ for (int id = 0; id < std::ssize(WrittenFlags_); ++id) {
+ if (WrittenFlags_[id]) {
+ WrittenFlags_[id] = false;
+ } else if ((TreatMissingAsNull_ || id < Schema_->GetKeyColumnCount()) &&
+ !(AllowMissingKeyColumns_ && id < Schema_->GetKeyColumnCount()) &&
+ !Schema_->Columns()[id].Expression())
+ {
+ auto flags = EValueFlags::None;
+ if (Schema_->Columns()[id].Aggregate() && Aggregate_) {
+ flags |= EValueFlags::Aggregate;
+ }
+ Builder_.AddValue(MakeUnversionedSentinelValue(EValueType::Null, id, flags));
+ }
+ }
+ Rows_.emplace_back(Builder_.FinishRow());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWritingValueConsumerBufferTag
+{ };
+
+TWritingValueConsumer::TWritingValueConsumer(
+ IUnversionedWriterPtr writer,
+ TTypeConversionConfigPtr typeConversionConfig,
+ i64 maxRowBufferSize)
+ : TValueConsumerBase(writer->GetSchema(), std::move(typeConversionConfig))
+ , Writer_(std::move(writer))
+ , MaxRowBufferSize_(maxRowBufferSize)
+ , RowBuffer_(New<TRowBuffer>(TWritingValueConsumerBufferTag()))
+{
+ YT_VERIFY(Writer_);
+ InitializeIdToTypeMapping();
+}
+
+TFuture<void> TWritingValueConsumer::Flush()
+{
+ if (RowBuffer_->GetSize() == 0) {
+ return VoidFuture;
+ }
+
+ // We could have multiple value consumers writing into the same Writer.
+ // It means, that there could be multiple subscribers waiting to flush data on the same ready event.
+ // To make writing safe, we must double check that the writer is really ready, before flushing rows.
+
+ return
+ BIND([writer = Writer_, rowBuffer = RowBuffer_, rows = std::move(Rows_)] {
+ while (!writer->GetReadyEvent().IsSet() || !writer->GetReadyEvent().Get().IsOK()) {
+ WaitFor(writer->GetReadyEvent())
+ .ThrowOnError();
+ }
+
+ writer->Write(rows);
+ rowBuffer->Clear();
+ return writer->GetReadyEvent();
+ })
+ .AsyncVia(GetCurrentInvoker())
+ .Run();
+}
+
+const TNameTablePtr& TWritingValueConsumer::GetNameTable() const
+{
+ return Writer_->GetNameTable();
+}
+
+bool TWritingValueConsumer::GetAllowUnknownColumns() const
+{
+ return true;
+}
+
+void TWritingValueConsumer::OnBeginRow()
+{
+ YT_ASSERT(Values_.empty());
+}
+
+void TWritingValueConsumer::OnMyValue(const TUnversionedValue& value)
+{
+ Values_.push_back(RowBuffer_->CaptureValue(value));
+}
+
+void TWritingValueConsumer::OnEndRow()
+{
+ auto row = RowBuffer_->CaptureRow(MakeRange(Values_), false);
+ Values_.clear();
+ Rows_.push_back(row);
+
+ if (RowBuffer_->GetSize() >= MaxRowBufferSize_) {
+ auto error = WaitFor(Flush());
+ THROW_ERROR_EXCEPTION_IF_FAILED(error, "Table writer failed")
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/value_consumer.h b/yt/yt/client/table_client/value_consumer.h
new file mode 100644
index 0000000000..3ec3be5fd0
--- /dev/null
+++ b/yt/yt/client/table_client/value_consumer.h
@@ -0,0 +1,155 @@
+#pragma once
+
+#include "public.h"
+#include "config.h"
+#include "unversioned_row.h"
+
+#include <yt/yt/core/misc/blob_output.h>
+
+#include <yt/yt/core/yson/writer.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IValueConsumer
+{
+ virtual ~IValueConsumer() = default;
+
+ virtual const TNameTablePtr& GetNameTable() const = 0;
+ virtual const TTableSchemaPtr& GetSchema() const = 0;
+
+ virtual bool GetAllowUnknownColumns() const = 0;
+
+ virtual void OnBeginRow() = 0;
+ virtual void OnValue(const TUnversionedValue& value) = 0;
+ virtual void OnEndRow() = 0;
+};
+
+struct IFlushableValueConsumer
+ : public virtual IValueConsumer
+{
+ virtual TFuture<void> Flush() = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TValueConsumerBase
+ : public virtual IValueConsumer
+{
+public:
+ TValueConsumerBase(
+ TTableSchemaPtr schema,
+ TTypeConversionConfigPtr typeConversionConfig);
+
+ void OnValue(const TUnversionedValue& value) override;
+ const TTableSchemaPtr& GetSchema() const override;
+
+protected:
+ const TTableSchemaPtr Schema_;
+
+ virtual void OnMyValue(const TUnversionedValue& value) = 0;
+
+ // This should be done in a separate base class method because we can't do
+ // it in a constructor (it depends on a derived type GetNameTable() implementation that
+ // can't be called from a parent class).
+ void InitializeIdToTypeMapping();
+
+private:
+ const TTypeConversionConfigPtr TypeConversionConfig_;
+
+ std::vector<EValueType> NameTableIdToType_;
+
+ // This template method is private and only used in value_consumer.cpp with T = i64/ui64,
+ // so it is not necessary to implement it in value_consumer-inl.h.
+ template <typename T>
+ void ProcessIntegralValue(const TUnversionedValue& value, EValueType columnType);
+
+ void ProcessInt64Value(const TUnversionedValue& value, EValueType columnType);
+ void ProcessUint64Value(const TUnversionedValue& value, EValueType columnType);
+ void ProcessBooleanValue(const TUnversionedValue& value, EValueType columnType);
+ void ProcessDoubleValue(const TUnversionedValue& value, EValueType columnType);
+ void ProcessStringValue(const TUnversionedValue& value, EValueType columnType);
+
+ void ThrowConversionException(const TUnversionedValue& value, EValueType columnType, const TError& error);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBuildingValueConsumer
+ : public TValueConsumerBase
+{
+public:
+ TBuildingValueConsumer(
+ TTableSchemaPtr schema,
+ NLogging::TLogger logger,
+ bool convertNullToEntity,
+ TTypeConversionConfigPtr typeConversionConfig = New<TTypeConversionConfig>());
+
+ std::vector<TUnversionedRow> GetRows() const;
+
+ const TNameTablePtr& GetNameTable() const override;
+
+ void SetAggregate(bool value);
+ void SetTreatMissingAsNull(bool value);
+ void SetAllowMissingKeyColumns(bool value);
+
+private:
+ bool GetAllowUnknownColumns() const override;
+
+ void OnBeginRow() override;
+ void OnMyValue(const TUnversionedValue& value) override;
+ void OnEndRow() override;
+
+private:
+ const NLogging::TLogger Logger;
+ const TNameTablePtr NameTable_;
+ const bool ConvertNullToEntity_;
+
+ TUnversionedOwningRowBuilder Builder_;
+ std::vector<TUnversionedOwningRow> Rows_;
+ std::vector<bool> WrittenFlags_;
+ TChunkedMemoryPool MemoryPool_;
+
+ bool Aggregate_ = false;
+ bool TreatMissingAsNull_ = false;
+ bool LogNullToEntity_ = true;
+ bool AllowMissingKeyColumns_ = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWritingValueConsumer
+ : public TValueConsumerBase
+ , public IFlushableValueConsumer
+{
+public:
+ explicit TWritingValueConsumer(
+ IUnversionedWriterPtr writer,
+ TTypeConversionConfigPtr typeConversionConfig = New<TTypeConversionConfig>(),
+ i64 maxRowBufferSize = 1_MB);
+
+ TFuture<void> Flush() override;
+ const TNameTablePtr& GetNameTable() const override;
+
+ bool GetAllowUnknownColumns() const override;
+
+ void OnBeginRow() override;
+ void OnMyValue(const TUnversionedValue& value) override;
+ void OnEndRow() override;
+
+private:
+ const IUnversionedWriterPtr Writer_;
+ const i64 MaxRowBufferSize_;
+
+ const TRowBufferPtr RowBuffer_;
+
+ std::vector<TUnversionedRow> Rows_;
+ TCompactVector<TUnversionedValue, TypicalColumnCount> Values_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/versioned_reader.cpp b/yt/yt/client/table_client/versioned_reader.cpp
new file mode 100644
index 0000000000..297855051d
--- /dev/null
+++ b/yt/yt/client/table_client/versioned_reader.cpp
@@ -0,0 +1,82 @@
+#include "versioned_reader.h"
+
+#include <yt/yt_proto/yt/client/chunk_client/proto/data_statistics.pb.h>
+
+namespace NYT::NTableClient {
+
+using namespace NChunkClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEmptyVersionedReader
+ : public IVersionedReader
+{
+public:
+ explicit TEmptyVersionedReader(int rowCount)
+ : RowCount_(rowCount)
+ { }
+
+ TFuture<void> Open() override
+ {
+ return VoidFuture;
+ }
+
+ IVersionedRowBatchPtr Read(const TRowBatchReadOptions& options) override
+ {
+ if (RowCount_ == 0) {
+ return nullptr;
+ }
+
+ std::vector<TVersionedRow> rows;
+ int rowCount = std::min<i64>(options.MaxRowsPerRead, RowCount_);
+ rows.reserve(rowCount);
+ for (int index = 0; index < rowCount; ++index) {
+ rows.push_back(TVersionedRow());
+ }
+
+ RowCount_ -= rowCount;
+
+ return CreateBatchFromVersionedRows(MakeSharedRange(std::move(rows)));
+ }
+
+ TFuture<void> GetReadyEvent() const override
+ {
+ return VoidFuture;
+ }
+
+ NChunkClient::NProto::TDataStatistics GetDataStatistics() const override
+ {
+ return NChunkClient::NProto::TDataStatistics();
+ }
+
+ TCodecStatistics GetDecompressionStatistics() const override
+ {
+ return NChunkClient::TCodecStatistics();
+ }
+
+ bool IsFetchingCompleted() const override
+ {
+ return true;
+ }
+
+ std::vector<TChunkId> GetFailedChunkIds() const override
+ {
+ return std::vector<TChunkId>();
+ }
+
+private:
+ int RowCount_;
+};
+
+DEFINE_REFCOUNTED_TYPE(TEmptyVersionedReader)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IVersionedReaderPtr CreateEmptyVersionedReader(int rowCount)
+{
+ return New<TEmptyVersionedReader>(rowCount);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/versioned_reader.h b/yt/yt/client/table_client/versioned_reader.h
new file mode 100644
index 0000000000..968a34f3b8
--- /dev/null
+++ b/yt/yt/client/table_client/versioned_reader.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "config.h"
+#include "versioned_row.h"
+#include "row_batch.h"
+
+#include <yt/yt/client/chunk_client/reader_base.h>
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Reads a schemaful versioned rowset.
+/*!
+ * Versioned rowset implies that it is:
+ * 1. Schemaful.
+ * 2. Sorted.
+ * 3. No two rows share the same key.
+ *
+ * Useful for: merging and compactions.
+ */
+struct IVersionedReader
+ : public virtual NChunkClient::IReaderBase
+{
+ virtual TFuture<void> Open() = 0;
+
+ //! Tries to read more rows from the reader.
+ /*!
+ * Depending on implementation, rows may come in two different flavors.
+ * (A) Rows containing no more than one versioned value for each cell,
+ * no more than one write timestamp (the last one), and no more than one
+ * delete timestamp (for merging).
+ * (B) Rows containing all available versions and two lists of timestamps: write and delete
+ * (for compactions).
+ *
+ * Value ids correspond to column indexes in schema.
+ * The returned rows are canonically sorted (see TVersionedRow).
+ *
+ * If |nullptr| is returned then the end of the rowset is reached.
+ * If non-null value is returned but the batch is empty then no more data is available
+ * at the moment. The caller must wait for the asynchronous flag provided by #GetReadyEvent
+ * to become set. The latter may indicate an error occurred while fetching more data.
+ *
+ * In Case A above row timestamps have the following meaning:
+ * 1. If the row is found and is known to be deleted then only the deletion
+ * timestamp is provided.
+ * 2. If the row is found and is known to exist then the last write
+ * timestamp and the last deleted timestamp (if exists) are provided.
+ */
+ virtual IVersionedRowBatchPtr Read(const TRowBatchReadOptions& options = {}) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IVersionedReader)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IVersionedReaderPtr CreateEmptyVersionedReader(int rowCount = 0);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/versioned_row.cpp b/yt/yt/client/table_client/versioned_row.cpp
new file mode 100644
index 0000000000..17fd1e41ba
--- /dev/null
+++ b/yt/yt/client/table_client/versioned_row.cpp
@@ -0,0 +1,602 @@
+#include "versioned_row.h"
+
+#include "name_table.h"
+#include "row_buffer.h"
+#include "schema.h"
+
+#include <library/cpp/yt/coding/varint.h>
+
+#include <numeric>
+
+namespace NYT::NTableClient {
+
+using namespace NTransactionClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t GetByteSize(const TVersionedValue& value)
+{
+ return EstimateRowValueSize(static_cast<TUnversionedValue>(value)) + MaxVarInt64Size;
+}
+
+size_t GetDataWeight(const TVersionedValue& value)
+{
+ return GetDataWeight(static_cast<TUnversionedValue>(value)) + sizeof(TTimestamp);
+}
+
+size_t ReadValue(const char* input, TVersionedValue* value)
+{
+ int result = ReadRowValue(input, static_cast<TUnversionedValue*>(value));
+ result += ReadVarUint64(input + result, &value->Timestamp);
+ return result;
+}
+
+size_t WriteValue(char* output, const TVersionedValue& value)
+{
+ int result = WriteRowValue(output, static_cast<TUnversionedValue>(value));
+ result += WriteVarUint64(output + result, value.Timestamp);
+ return result;
+}
+
+void Save(TStreamSaveContext& context, const TVersionedValue& value)
+{
+ NYT::Save(context, value.Timestamp);
+ NTableClient::Save(context, static_cast<const TUnversionedValue&>(value));
+}
+
+void Load(TStreamLoadContext& context, TVersionedValue& value, TChunkedMemoryPool* pool)
+{
+ NYT::Load(context, value.Timestamp);
+ NTableClient::Load(context, static_cast<TUnversionedValue&>(value), pool);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t GetVersionedRowByteSize(
+ int keyCount,
+ int valueCount,
+ int writeTimestampCount,
+ int deleteTimestampCount)
+{
+ return
+ sizeof(TVersionedRowHeader) +
+ sizeof(TUnversionedValue) * keyCount +
+ sizeof(TVersionedValue) * valueCount +
+ sizeof(TTimestamp) * writeTimestampCount +
+ sizeof(TTimestamp) * deleteTimestampCount;
+}
+
+size_t GetDataWeight(TVersionedRow row)
+{
+ if (!row) {
+ return 0;
+ }
+
+ size_t result = 0;
+ result += std::accumulate(
+ row.BeginValues(),
+ row.EndValues(),
+ 0ll,
+ [] (size_t x, const TVersionedValue& value) {
+ return GetDataWeight(value) + x;
+ });
+
+ result += std::accumulate(
+ row.BeginKeys(),
+ row.EndKeys(),
+ 0ll,
+ [] (size_t x, const TUnversionedValue& value) {
+ return GetDataWeight(value) + x;
+ });
+
+ result += row.GetWriteTimestampCount() * sizeof(TTimestamp);
+ result += row.GetDeleteTimestampCount() * sizeof(TTimestamp);
+
+ return result;
+}
+
+void ValidateClientDataRow(
+ TVersionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ const TNameTablePtr& nameTable,
+ bool allowMissingKeyColumns)
+{
+ int keyCount = row.GetKeyCount();
+ ValidateKeyColumnCount(keyCount);
+ ValidateRowValueCount(row.GetValueCount());
+
+ if (!allowMissingKeyColumns) {
+ if (keyCount != schema.GetKeyColumnCount()) {
+ THROW_ERROR_EXCEPTION("Invalid key count: expected %v, got %v",
+ schema.GetKeyColumnCount(),
+ keyCount);
+ }
+
+ if (nameTable->GetSize() < keyCount) {
+ THROW_ERROR_EXCEPTION("Name table size is too small to contain all keys: expected >=%v, got %v",
+ row.GetKeyCount(),
+ nameTable->GetSize());
+ }
+
+ for (int index = 0; index < keyCount; ++index) {
+ const auto& expectedName = schema.Columns()[index].Name();
+ auto actualName = nameTable->GetName(index);
+ if (expectedName != actualName) {
+ THROW_ERROR_EXCEPTION("Invalid key column %v in name table: expected %Qv, got %Qv",
+ index,
+ expectedName,
+ actualName);
+ }
+ ValidateValueType(row.Keys()[index], schema, index, /*typeAnyAcceptsAllValues*/ false);
+ ValidateKeyValue(row.Keys()[index]);
+ }
+ } else {
+ for (int index = 0; index < keyCount; ++index) {
+ auto name = nameTable->GetName(index);
+ int columnIndex = schema.GetColumnIndexOrThrow(name);
+ if (columnIndex >= schema.GetColumnCount()) {
+ THROW_ERROR_EXCEPTION("Invalid key column %Qv: appears as a key column in row but actually is a data column in table",
+ name);
+ }
+ ValidateValueType(row.Keys()[index], schema, columnIndex, /*typeAnyAcceptsAllValues*/ false);
+ ValidateKeyValue(row.Keys()[index]);
+ }
+ }
+
+ auto validateTimestamps = [&] (const TTimestamp* begin, const TTimestamp* end) {
+ for (const auto* current = begin; current != end; ++current) {
+ ValidateWriteTimestamp(*current);
+ if (current != begin && *current >= *(current - 1)) {
+ THROW_ERROR_EXCEPTION("Timestamps are not monotonically decreasing: %v >= %v",
+ *current,
+ *(current - 1));
+ }
+ }
+ };
+ validateTimestamps(row.BeginWriteTimestamps(), row.EndWriteTimestamps());
+ validateTimestamps(row.BeginDeleteTimestamps(), row.EndDeleteTimestamps());
+
+ for (const auto* current = row.BeginValues(); current != row.EndValues(); ++current) {
+ if (current != row.BeginValues()) {
+ auto* prev = current - 1;
+ if (current->Id < prev->Id) {
+ THROW_ERROR_EXCEPTION("Value ids must be non-decreasing: %v < %v",
+ current->Id,
+ prev->Id);
+ }
+ if (current->Id == prev->Id && current->Timestamp >= prev->Timestamp) {
+ THROW_ERROR_EXCEPTION("Value timestamps must be decreasing: %v >= %v",
+ current->Timestamp,
+ prev->Timestamp);
+ }
+ }
+
+ const auto& value = *current;
+ int mappedId = ApplyIdMapping(value, &idMapping);
+
+ if (mappedId < 0 || mappedId >= std::ssize(schema.Columns())) {
+ int size = nameTable->GetSize();
+ if (value.Id < 0 || value.Id >= size) {
+ THROW_ERROR_EXCEPTION("Expected value id in range [0:%v] but got %v",
+ size - 1,
+ value.Id);
+ }
+
+ THROW_ERROR_EXCEPTION("Unexpected column %Qv", nameTable->GetName(value.Id));
+ }
+
+ if (mappedId < keyCount) {
+ THROW_ERROR_EXCEPTION("Key component %v appears in value part",
+ schema.Columns()[mappedId].GetDiagnosticNameString());
+ }
+
+ const auto& column = schema.Columns()[mappedId];
+ ValidateValueType(value, schema, mappedId, /*typeAnyAcceptsAllValues*/ false);
+
+ if (Any(value.Flags & EValueFlags::Aggregate) && !column.Aggregate()) {
+ THROW_ERROR_EXCEPTION(
+ "\"aggregate\" flag is set for value in non-aggregating column %v",
+ column.GetDiagnosticNameString());
+ }
+
+ if (mappedId < schema.GetKeyColumnCount()) {
+ THROW_ERROR_EXCEPTION("Key column %v in values",
+ column.GetDiagnosticNameString());
+ }
+
+ ValidateDataValue(value);
+ }
+
+ auto dataWeight = GetDataWeight(row);
+ if (dataWeight >= MaxClientVersionedRowDataWeight) {
+ THROW_ERROR_EXCEPTION("Row is too large: data weight %v, limit %v",
+ dataWeight,
+ MaxClientVersionedRowDataWeight);
+ }
+}
+
+void ValidateDuplicateAndRequiredValueColumns(
+ TVersionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer,
+ const TTimestamp* writeTimestamps,
+ int writeTimestampCount)
+{
+ if (writeTimestampCount == 0) {
+ return;
+ }
+
+ auto& columnSeen = *columnPresenceBuffer;
+ YT_VERIFY(std::ssize(columnSeen) >= schema.GetColumnCount());
+ std::fill(columnSeen.begin(), columnSeen.end(), 0);
+
+ for (const auto *valueGroupBeginIt = row.BeginValues(), *valueGroupEndIt = valueGroupBeginIt;
+ valueGroupBeginIt != row.EndValues();
+ valueGroupBeginIt = valueGroupEndIt)
+ {
+ while (valueGroupEndIt != row.EndValues() && valueGroupBeginIt->Id == valueGroupEndIt->Id) {
+ ++valueGroupEndIt;
+ }
+
+ int mappedId = ApplyIdMapping(*valueGroupBeginIt, &idMapping);
+ if (mappedId < 0) {
+ continue;
+ }
+ const auto& column = schema.Columns()[mappedId];
+
+ if (columnSeen[mappedId]) {
+ THROW_ERROR_EXCEPTION("Duplicate value group %v in versioned row",
+ column.GetDiagnosticNameString());
+ }
+ columnSeen[mappedId] = true;
+
+ if (column.Required()) {
+ auto mismatch = std::mismatch(
+ writeTimestamps,
+ writeTimestamps + writeTimestampCount,
+ valueGroupBeginIt,
+ valueGroupEndIt,
+ [] (TTimestamp expected, const TVersionedValue& actual) {
+ return expected == actual.Timestamp;
+ }
+ );
+ if (mismatch.first == writeTimestamps + writeTimestampCount) {
+ if (mismatch.second != valueGroupEndIt) {
+ THROW_ERROR_EXCEPTION(
+ "Row-wise write timestamps do not contain write timestamp %v for column %v",
+ *mismatch.second,
+ column.GetDiagnosticNameString());
+ }
+ } else {
+ THROW_ERROR_EXCEPTION(
+ "Required column %v does not contain value for timestamp %Qv",
+ column.GetDiagnosticNameString(),
+ *mismatch.first);
+ }
+ }
+ }
+
+ for (int index = schema.GetKeyColumnCount(); index < schema.GetColumnCount(); ++index) {
+ if (!columnSeen[index] && schema.Columns()[index].Required()) {
+ THROW_ERROR_EXCEPTION("Missing values for required column %v",
+ schema.Columns()[index].GetDiagnosticNameString());
+ }
+ }
+}
+
+TLegacyOwningKey ToOwningKey(TVersionedRow row)
+{
+ TUnversionedOwningRowBuilder builder;
+ for (const auto& value : row.Keys()) {
+ builder.AddValue(value);
+ }
+ return builder.FinishRow();
+}
+
+TKeyRef ToKeyRef(TVersionedRow row)
+{
+ return row.Keys();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TVersionedRowBuilder::TVersionedRowBuilder(TRowBufferPtr buffer, bool compaction)
+ : Buffer_(std::move(buffer))
+ , Compaction_(compaction)
+{ }
+
+void TVersionedRowBuilder::AddKey(const TUnversionedValue& value)
+{
+ Keys_.push_back(Buffer_->CaptureValue(value));
+}
+
+void TVersionedRowBuilder::AddValue(const TVersionedValue& value)
+{
+ WriteTimestamps_.push_back(value.Timestamp);
+ Values_.push_back(Buffer_->CaptureValue(value));
+}
+
+void TVersionedRowBuilder::AddDeleteTimestamp(TTimestamp timestamp)
+{
+ DeleteTimestamps_.push_back(timestamp);
+}
+
+void TVersionedRowBuilder::AddWriteTimestamp(TTimestamp timestamp)
+{
+ WriteTimestamps_.push_back(timestamp);
+}
+
+TMutableVersionedRow TVersionedRowBuilder::FinishRow()
+{
+ std::sort(
+ Values_.begin(),
+ Values_.end(),
+ [] (const TVersionedValue& lhs, const TVersionedValue& rhs) -> bool {
+ if (lhs.Id < rhs.Id) {
+ return true;
+ }
+ if (lhs.Id > rhs.Id) {
+ return false;
+ }
+ if (lhs.Timestamp < rhs.Timestamp) {
+ return false;
+ }
+ if (lhs.Timestamp > rhs.Timestamp) {
+ return true;
+ }
+ return false;
+ });
+
+ std::sort(WriteTimestamps_.begin(), WriteTimestamps_.end(), std::greater<TTimestamp>());
+
+ if (Compaction_) {
+ WriteTimestamps_.erase(
+ std::unique(WriteTimestamps_.begin(), WriteTimestamps_.end()),
+ WriteTimestamps_.end());
+ } else if (!WriteTimestamps_.empty()) {
+ WriteTimestamps_.erase(WriteTimestamps_.begin() + 1, WriteTimestamps_.end());
+ }
+
+ std::sort(DeleteTimestamps_.begin(), DeleteTimestamps_.end(), std::greater<TTimestamp>());
+ DeleteTimestamps_.erase(
+ std::unique(DeleteTimestamps_.begin(), DeleteTimestamps_.end()),
+ DeleteTimestamps_.end());
+
+ auto row = Buffer_->AllocateVersioned(
+ Keys_.size(),
+ Values_.size(),
+ WriteTimestamps_.size(),
+ DeleteTimestamps_.size());
+
+ memcpy(row.BeginKeys(), Keys_.data(), sizeof (TUnversionedValue) * Keys_.size());
+ memcpy(row.BeginValues(), Values_.data(), sizeof (TVersionedValue)* Values_.size());
+ memcpy(row.BeginWriteTimestamps(), WriteTimestamps_.data(), sizeof (TTimestamp) * WriteTimestamps_.size());
+ memcpy(row.BeginDeleteTimestamps(), DeleteTimestamps_.data(), sizeof (TTimestamp) * DeleteTimestamps_.size());
+
+ Keys_.clear();
+ Values_.clear();
+ WriteTimestamps_.clear();
+ DeleteTimestamps_.clear();
+
+ return row;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TVersionedOwningRow::TVersionedOwningRow(TVersionedRow other)
+{
+ if (!other) {
+ return;
+ }
+
+ size_t fixedSize = GetVersionedRowByteSize(
+ other.GetKeyCount(),
+ other.GetValueCount(),
+ other.GetWriteTimestampCount(),
+ other.GetDeleteTimestampCount());
+
+ size_t variableSize = 0;
+ auto adjustVariableSize = [&] (const TUnversionedValue& value) {
+ if (IsStringLikeType(value.Type)) {
+ variableSize += value.Length;
+ }
+ };
+
+ for (const auto& value : other.Keys()) {
+ adjustVariableSize(value);
+ }
+ for (const auto& value : other.Values()) {
+ adjustVariableSize(value);
+ }
+
+ Data_ = TSharedMutableRef::Allocate(fixedSize + variableSize, {.InitializeStorage = false});
+
+ ::memcpy(GetMutableHeader(), other.GetHeader(), fixedSize);
+
+ if (variableSize > 0) {
+ char* current = Data_.Begin() + fixedSize;
+ auto captureValue = [&] (TUnversionedValue* value) {
+ if (IsStringLikeType(value->Type)) {
+ ::memcpy(current, value->Data.String, value->Length);
+ value->Data.String = current;
+ current += value->Length;
+ }
+ };
+
+ for (int index = 0; index < other.GetKeyCount(); ++index) {
+ captureValue(BeginMutableKeys() + index);
+ }
+ for (int index = 0; index < other.GetValueCount(); ++index) {
+ captureValue(BeginMutableValues() + index);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TVersionedValue& value, TStringBuf /*format*/)
+{
+ Format(
+ builder,
+ "%v@%x",
+ static_cast<TUnversionedValue>(value),
+ value.Timestamp);
+}
+
+void FormatValue(TStringBuilderBase* builder, TVersionedRow row, TStringBuf /*format*/)
+{
+ if (!row) {
+ builder->AppendString("<null>");
+ return;
+ }
+
+ builder->AppendChar('[');
+ JoinToString(
+ builder,
+ row.BeginKeys(),
+ row.EndKeys(),
+ [&] (TStringBuilderBase* builder, const TUnversionedValue& value) {
+ FormatValue(builder, value, "k");
+ });
+ builder->AppendString(" | ");
+ JoinToString(builder, row.BeginValues(), row.EndValues(), TDefaultFormatter{});
+ builder->AppendString(" | ");
+ JoinToString(
+ builder,
+ row.BeginWriteTimestamps(),
+ row.EndWriteTimestamps(),
+ [] (TStringBuilderBase* builder, TTimestamp timestamp) {
+ builder->AppendFormat("%x", timestamp);
+ });
+ builder->AppendString(" | ");
+ JoinToString(
+ builder,
+ row.BeginDeleteTimestamps(),
+ row.EndDeleteTimestamps(),
+ [] (TStringBuilderBase* builder, TTimestamp timestamp) {
+ builder->AppendFormat("%x", timestamp);
+ });
+ builder->AppendChar(']');
+}
+
+void FormatValue(TStringBuilderBase* builder, TMutableVersionedRow row, TStringBuf /*format*/)
+{
+ FormatValue(builder, TVersionedRow(row), {});
+}
+
+void FormatValue(TStringBuilderBase* builder, TVersionedOwningRow row, TStringBuf /*format*/)
+{
+ FormatValue(builder, TVersionedRow(row), {});
+}
+
+TString ToString(const TVersionedValue& value)
+{
+ return ToStringViaBuilder(value);
+}
+
+TString ToString(TVersionedRow row)
+{
+ return ToStringViaBuilder(row);
+}
+
+TString ToString(TMutableVersionedRow row)
+{
+ return ToString(TVersionedRow(row));
+}
+
+TString ToString(const TVersionedOwningRow& row)
+{
+ return ToString(row.Get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t TBitwiseVersionedValueHash::operator()(const TVersionedValue& value) const
+{
+ size_t result = 0;
+ HashCombine(result, value.Timestamp);
+ HashCombine(result, TBitwiseUnversionedValueHash()(value));
+ return result;
+}
+
+bool TBitwiseVersionedValueEqual::operator()(const TVersionedValue& lhs, const TVersionedValue& rhs) const
+{
+ return lhs.Timestamp == rhs.Timestamp && TBitwiseUnversionedValueEqual()(lhs, rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t TBitwiseVersionedRowHash::operator()(TVersionedRow row) const
+{
+ size_t result = 0;
+ for (const auto& value : row.Keys()) {
+ HashCombine(result, TBitwiseUnversionedValueHash()(value));
+ }
+ for (const auto& value : row.Values()) {
+ HashCombine(result, TBitwiseVersionedValueHash()(value));
+ }
+ // Write timestamps are omitted since these are inferred from values.
+ for (auto timestamp : row.DeleteTimestamps()) {
+ HashCombine(result, timestamp);
+ }
+ return result;
+}
+
+bool TBitwiseVersionedRowEqual::operator()(TVersionedRow lhs, TVersionedRow rhs) const
+{
+ if (!lhs && !rhs) {
+ return true;
+ }
+
+ if (!lhs || !rhs) {
+ return false;
+ }
+
+ if (lhs.GetKeyCount() != rhs.GetKeyCount()) {
+ return false;
+ }
+
+ for (int index = 0; index < lhs.GetKeyCount(); ++index) {
+ if (!TBitwiseUnversionedValueEqual()(lhs.Keys()[index], lhs.Keys()[index])) {
+ return false;
+ }
+ }
+
+ if (lhs.GetValueCount() != rhs.GetValueCount()) {
+ return false;
+ }
+
+ for (int i = 0; i < lhs.GetValueCount(); ++i) {
+ if (!TBitwiseVersionedValueEqual()(lhs.Values()[i], rhs.Values()[i])) {
+ return false;
+ }
+ }
+
+ if (lhs.GetWriteTimestampCount() != rhs.GetWriteTimestampCount()) {
+ return false;
+ }
+
+ for (int i = 0; i < lhs.GetWriteTimestampCount(); ++i) {
+ if (lhs.WriteTimestamps()[i] != rhs.WriteTimestamps()[i]) {
+ return false;
+ }
+ }
+
+ if (lhs.GetDeleteTimestampCount() != rhs.GetDeleteTimestampCount()) {
+ return false;
+ }
+
+ for (int i = 0; i < lhs.GetDeleteTimestampCount(); ++i) {
+ if (lhs.DeleteTimestamps()[i] != rhs.DeleteTimestamps()[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/versioned_row.h b/yt/yt/client/table_client/versioned_row.h
new file mode 100644
index 0000000000..515697e531
--- /dev/null
+++ b/yt/yt/client/table_client/versioned_row.h
@@ -0,0 +1,666 @@
+#pragma once
+
+#include "public.h"
+#include "unversioned_row.h"
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TVersionedValue
+ : public TUnversionedValue
+{
+ TTimestamp Timestamp;
+};
+
+static_assert(
+ sizeof(TVersionedValue) == 24,
+ "TVersionedValue has to be exactly 24 bytes.");
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline TVersionedValue MakeVersionedValue(const TUnversionedValue& value, TTimestamp timestamp)
+{
+ TVersionedValue result;
+ static_cast<TUnversionedValue&>(result) = value;
+ result.Timestamp = timestamp;
+ return result;
+}
+
+inline TVersionedValue MakeVersionedSentinelValue(EValueType type, TTimestamp timestamp, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ auto result = MakeSentinelValue<TVersionedValue>(type, id, flags);
+ result.Timestamp = timestamp;
+ return result;
+}
+
+inline TVersionedValue MakeVersionedInt64Value(i64 value, TTimestamp timestamp, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ auto result = MakeInt64Value<TVersionedValue>(value, id, flags);
+ result.Timestamp = timestamp;
+ return result;
+}
+
+inline TVersionedValue MakeVersionedUint64Value(ui64 value, TTimestamp timestamp, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ auto result = MakeUint64Value<TVersionedValue>(value, id, flags);
+ result.Timestamp = timestamp;
+ return result;
+}
+
+inline TVersionedValue MakeVersionedDoubleValue(double value, TTimestamp timestamp, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ auto result = MakeDoubleValue<TVersionedValue>(value, id, flags);
+ result.Timestamp = timestamp;
+ return result;
+}
+
+inline TVersionedValue MakeVersionedBooleanValue(bool value, TTimestamp timestamp, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ auto result = MakeBooleanValue<TVersionedValue>(value, id, flags);
+ result.Timestamp = timestamp;
+ return result;
+}
+
+inline TVersionedValue MakeVersionedStringValue(TStringBuf value, TTimestamp timestamp, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ auto result = MakeStringValue<TVersionedValue>(value, id, flags);
+ result.Timestamp = timestamp;
+ return result;
+}
+
+inline TVersionedValue MakeVersionedAnyValue(TStringBuf value, TTimestamp timestamp, int id = 0, EValueFlags flags = EValueFlags::None)
+{
+ auto result = MakeAnyValue<TVersionedValue>(value, id, flags);
+ result.Timestamp = timestamp;
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TVersionedRowHeader
+{
+ ui32 ValueCount;
+ ui32 KeyCount;
+ ui32 WriteTimestampCount;
+ ui32 DeleteTimestampCount;
+};
+
+static_assert(
+ sizeof(TVersionedRowHeader) == 16,
+ "TVersionedRowHeader has to be exactly 16 bytes.");
+
+////////////////////////////////////////////////////////////////////////////////
+
+size_t GetByteSize(const TVersionedValue& value);
+size_t GetDataWeight(const TVersionedValue& value);
+
+size_t ReadValue(const char* input, TVersionedValue* value);
+size_t WriteValue(char* output, const TVersionedValue& value);
+
+void Save(TStreamSaveContext& context, const TVersionedValue& value);
+void Load(TStreamLoadContext& context, TVersionedValue& value, TChunkedMemoryPool* pool);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Returns the number of bytes needed to store a versioned row (not including string data).
+size_t GetVersionedRowByteSize(
+ int keyCount,
+ int valueCount,
+ int writeTimestampCount,
+ int deleteTimestampCount);
+
+//! A row with versioned data.
+/*!
+ * A lightweight wrapper around |TVersionedRowHeader*|.
+ *
+ * Provides access to the following parts:
+ * 1) write timestamps, sorted in descending order;
+ * at most one if a specific revision is requested;
+ * 2) delete timestamps, sorted in descending order;
+ * at most one if a specific revision is requested;
+ * 3) unversioned keys;
+ * 4) versioned values, sorted by |id| (in ascending order) and then by |timestamp| (in descending order);
+ * note that no position-to-id matching is ever assumed.
+ *
+ * The order of values described in 4) is typically referred to as "canonical".
+ *
+ * Memory layout:
+ * 1) TVersionedRowHeader
+ * 2) TTimestamp per each write timestamp (#TVersionedRowHeader::WriteTimestampCount)
+ * 3) TTimestamp per each delete timestamp (#TVersionedRowHeader::DeleteTimestampCount)
+ * 4) TUnversionedValue per each key (#TVersionedRowHeader::KeyCount)
+ * 5) TVersionedValue per each value (#TVersionedRowHeader::ValueCount)
+ */
+class TVersionedRow
+{
+public:
+ TVersionedRow() = default;
+
+ explicit TVersionedRow(const TVersionedRowHeader* header)
+ : Header_(header)
+ { }
+
+ explicit TVersionedRow(TTypeErasedRow erased)
+ : Header_(reinterpret_cast<const TVersionedRowHeader*>(erased.OpaqueHeader))
+ { }
+
+ explicit operator bool()
+ {
+ return Header_ != nullptr;
+ }
+
+ TTypeErasedRow ToTypeErasedRow() const
+ {
+ return {Header_};
+ }
+
+ const TVersionedRowHeader* GetHeader() const
+ {
+ return Header_;
+ }
+
+ const TTimestamp* BeginWriteTimestamps() const
+ {
+ return reinterpret_cast<const TTimestamp*>(Header_ + 1);
+ }
+
+ const TTimestamp* EndWriteTimestamps() const
+ {
+ return BeginWriteTimestamps() + GetWriteTimestampCount();
+ }
+
+ TTimestampRange WriteTimestamps() const
+ {
+ return {BeginWriteTimestamps(), EndWriteTimestamps()};
+ }
+
+ const TTimestamp* BeginDeleteTimestamps() const
+ {
+ return EndWriteTimestamps();
+ }
+
+ const TTimestamp* EndDeleteTimestamps() const
+ {
+ return BeginDeleteTimestamps() + GetDeleteTimestampCount();
+ }
+
+ TTimestampRange DeleteTimestamps() const
+ {
+ return {BeginDeleteTimestamps(), EndDeleteTimestamps()};
+ }
+
+ const TUnversionedValue* BeginKeys() const
+ {
+ return reinterpret_cast<const TUnversionedValue*>(EndDeleteTimestamps());
+ }
+
+ const TUnversionedValue* EndKeys() const
+ {
+ return BeginKeys() + GetKeyCount();
+ }
+
+ TUnversionedValueRange Keys() const
+ {
+ return {BeginKeys(), EndKeys()};
+ }
+
+ const TVersionedValue* BeginValues() const
+ {
+ return reinterpret_cast<const TVersionedValue*>(EndKeys());
+ }
+
+ const TVersionedValue* EndValues() const
+ {
+ return BeginValues() + GetValueCount();
+ }
+
+ TVersionedValueRange Values() const
+ {
+ return {BeginValues(), EndValues()};
+ }
+
+ int GetKeyCount() const
+ {
+ return Header_->KeyCount;
+ }
+
+ int GetValueCount() const
+ {
+ return Header_->ValueCount;
+ }
+
+ int GetWriteTimestampCount() const
+ {
+ return Header_->WriteTimestampCount;
+ }
+
+ int GetDeleteTimestampCount() const
+ {
+ return Header_->DeleteTimestampCount;
+ }
+
+ const char* BeginMemory() const
+ {
+ return reinterpret_cast<const char*>(Header_);
+ }
+
+ const char* EndMemory() const
+ {
+ return BeginMemory() + GetVersionedRowByteSize(
+ GetKeyCount(),
+ GetValueCount(),
+ GetWriteTimestampCount(),
+ GetDeleteTimestampCount());
+ }
+
+private:
+ const TVersionedRowHeader* Header_ = nullptr;
+};
+
+static_assert(
+ sizeof(TVersionedRow) == sizeof(intptr_t),
+ "TVersionedRow size must match that of a pointer.");
+
+size_t GetDataWeight(TVersionedRow row);
+
+//! Checks that #row is a valid client-side versioned data row. Throws on failure.
+/*!
+ * Value ids in the row are first mapped via #idMapping.
+ * The row must obey the following properties:
+ * 1. Its value count must pass #ValidateRowValueCount checks.
+ * 2. Its key count must match the number of keys in #schema.
+ * 3. Name table must contain all key columns in the same order as in the schema.
+ * 4. Write and delete timestamps must pass #ValidateWriteTimestamp test and must be decreasing.
+ * 5. Value part must not contain key components.
+ * 6. Value types must either be null or match those given in #schema.
+ * 7. For values marked with #TUnversionedValue::Aggregate flag, the corresponding columns in #schema must
+ * be aggregating.
+ */
+void ValidateClientDataRow(
+ TVersionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ const TNameTablePtr& nameTable,
+ bool allowMissingKeyColumns = false);
+
+//! Checks that #row contains no duplicate value columns and that each required column
+//! contains a value for each known write timestamp.
+//! Skips values that map to negative ids with via #idMapping.
+/*! It is assumed that ValidateClientDataRow was called before. */
+void ValidateDuplicateAndRequiredValueColumns(
+ TVersionedRow row,
+ const TTableSchema& schema,
+ const TNameTableToSchemaIdMapping& idMapping,
+ std::vector<bool>* columnPresenceBuffer,
+ const TTimestamp* writeTimestamps,
+ int writeTimestampCount);
+
+TLegacyOwningKey ToOwningKey(TVersionedRow row);
+TKeyRef ToKeyRef(TVersionedRow row);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A variant of TVersionedRow that enables mutating access to its content.
+class TMutableVersionedRow
+ : public TVersionedRow
+{
+public:
+ TMutableVersionedRow() = default;
+
+ explicit TMutableVersionedRow(TVersionedRowHeader* header)
+ : TVersionedRow(header)
+ { }
+
+ explicit TMutableVersionedRow(TTypeErasedRow row)
+ : TVersionedRow(reinterpret_cast<const TVersionedRowHeader*>(row.OpaqueHeader))
+ { }
+
+ static TMutableVersionedRow Allocate(
+ TChunkedMemoryPool* pool,
+ int keyCount,
+ int valueCount,
+ int writeTimestampCount,
+ int deleteTimestampCount)
+ {
+ size_t byteSize = GetVersionedRowByteSize(
+ keyCount,
+ valueCount,
+ writeTimestampCount,
+ deleteTimestampCount);
+ auto* header = reinterpret_cast<TVersionedRowHeader*>(pool->AllocateAligned(byteSize));
+ header->KeyCount = keyCount;
+ header->ValueCount = valueCount;
+ header->WriteTimestampCount = writeTimestampCount;
+ header->DeleteTimestampCount = deleteTimestampCount;
+ return TMutableVersionedRow(header);
+ }
+
+ TVersionedRowHeader* GetHeader()
+ {
+ return const_cast<TVersionedRowHeader*>(TVersionedRow::GetHeader());
+ }
+
+ TTimestamp* BeginWriteTimestamps()
+ {
+ return reinterpret_cast<TTimestamp*>(GetHeader() + 1);
+ }
+
+ TTimestamp* EndWriteTimestamps()
+ {
+ return BeginWriteTimestamps() + GetWriteTimestampCount();
+ }
+
+ TMutableTimestampRange WriteTimestamps()
+ {
+ return {BeginWriteTimestamps(), EndWriteTimestamps()};
+ }
+
+ TTimestamp* BeginDeleteTimestamps()
+ {
+ return EndWriteTimestamps();
+ }
+
+ TTimestamp* EndDeleteTimestamps()
+ {
+ return BeginDeleteTimestamps() + GetDeleteTimestampCount();
+ }
+
+ TMutableTimestampRange DeleteTimestamps()
+ {
+ return {BeginDeleteTimestamps(), EndDeleteTimestamps()};
+ }
+
+ TUnversionedValue* BeginKeys()
+ {
+ return reinterpret_cast<TUnversionedValue*>(EndDeleteTimestamps());
+ }
+
+ TUnversionedValue* EndKeys()
+ {
+ return BeginKeys() + GetKeyCount();
+ }
+
+ TMutableUnversionedValueRange Keys()
+ {
+ return {BeginKeys(), EndKeys()};
+ }
+
+ TVersionedValue* BeginValues()
+ {
+ return reinterpret_cast<TVersionedValue*>(EndKeys());
+ }
+
+ TVersionedValue* EndValues()
+ {
+ return BeginValues() + GetValueCount();
+ }
+
+ TMutableVersionedValueRange Values()
+ {
+ return {BeginValues(), EndValues()};
+ }
+
+ void SetKeyCount(int count)
+ {
+ GetHeader()->KeyCount = count;
+ }
+
+ void SetValueCount(int count)
+ {
+ GetHeader()->ValueCount = count;
+ }
+
+ void SetWriteTimestampCount(int count)
+ {
+ GetHeader()->WriteTimestampCount = count;
+ }
+
+ void SetDeleteTimestampCount(int count)
+ {
+ GetHeader()->DeleteTimestampCount = count;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A helper used for constructing TVersionedRow instances.
+/*!
+ * Not very efficient, only useful in tests.
+ * The resulting row is canonically ordered.
+ */
+class TVersionedRowBuilder
+{
+public:
+ /*!
+ * \param compaction - if unset, builder creates only one, latest write timestamp.
+ */
+ explicit TVersionedRowBuilder(TRowBufferPtr buffer, bool compaction = true);
+
+ void AddKey(const TUnversionedValue& value);
+ void AddValue(const TVersionedValue& value);
+ void AddDeleteTimestamp(TTimestamp timestamp);
+
+ // Sometimes versioned row have write timestamps without corresponding values,
+ // when reading with column filter.
+ void AddWriteTimestamp(TTimestamp timestamp);
+
+ TMutableVersionedRow FinishRow();
+
+private:
+ const TRowBufferPtr Buffer_;
+ const bool Compaction_;
+
+ std::vector<TUnversionedValue> Keys_;
+ std::vector<TVersionedValue> Values_;
+ std::vector<TTimestamp> WriteTimestamps_;
+ std::vector<TTimestamp> DeleteTimestamps_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! An owning variant of TVersionedRow.
+/*!
+ * Instances of TVersionedOwningRow are lightweight handles.
+ * All data is stored in shared ref-counted blobs.
+ */
+class TVersionedOwningRow
+{
+public:
+ TVersionedOwningRow() = default;
+
+ explicit TVersionedOwningRow(TVersionedRow other);
+
+ TVersionedOwningRow(const TVersionedOwningRow& other)
+ : Data_(other.Data_)
+ { }
+
+ TVersionedOwningRow(TVersionedOwningRow&& other)
+ : Data_(std::move(other.Data_))
+ { }
+
+ explicit operator bool() const
+ {
+ return static_cast<bool>(Data_);
+ }
+
+ TVersionedRow Get() const
+ {
+ return TVersionedRow(GetHeader());
+ }
+
+ operator TVersionedRow() const
+ {
+ return Get();
+ }
+
+ const TTimestamp* BeginWriteTimestamps() const
+ {
+ return reinterpret_cast<const TTimestamp*>(GetHeader() + 1);
+ }
+
+ const TTimestamp* EndWriteTimestamps() const
+ {
+ return BeginWriteTimestamps() + GetWriteTimestampCount();
+ }
+
+ TTimestampRange WriteTimestamps() const
+ {
+ return {BeginWriteTimestamps(), EndWriteTimestamps()};
+ }
+
+ const TTimestamp* BeginDeleteTimestamps() const
+ {
+ return EndWriteTimestamps();
+ }
+
+ const TTimestamp* EndDeleteTimestamps() const
+ {
+ return BeginDeleteTimestamps() + GetDeleteTimestampCount();
+ }
+
+ TTimestampRange DeleteTimestamps() const
+ {
+ return {BeginDeleteTimestamps(), EndDeleteTimestamps()};
+ }
+
+ const TUnversionedValue* BeginKeys() const
+ {
+ return reinterpret_cast<const TUnversionedValue*>(EndDeleteTimestamps());
+ }
+
+ const TUnversionedValue* EndKeys() const
+ {
+ return BeginKeys() + GetKeyCount();
+ }
+
+ TUnversionedValueRange Keys() const
+ {
+ return {BeginKeys(), EndKeys()};
+ }
+
+ const TVersionedValue* BeginValues() const
+ {
+ return reinterpret_cast<const TVersionedValue*>(EndKeys());
+ }
+
+ const TVersionedValue* EndValues() const
+ {
+ return BeginValues() + GetValueCount();
+ }
+
+ TVersionedValueRange Values() const
+ {
+ return {BeginValues(), EndValues()};
+ }
+
+ int GetKeyCount() const
+ {
+ return GetHeader()->KeyCount;
+ }
+
+ int GetValueCount() const
+ {
+ return GetHeader()->ValueCount;
+ }
+
+ int GetWriteTimestampCount() const
+ {
+ return GetHeader()->WriteTimestampCount;
+ }
+
+ int GetDeleteTimestampCount() const
+ {
+ return GetHeader()->DeleteTimestampCount;
+ }
+
+ size_t GetByteSize() const
+ {
+ return Data_.Size();
+ }
+
+
+ friend void swap(TVersionedOwningRow& lhs, TVersionedOwningRow& rhs)
+ {
+ using std::swap;
+ swap(lhs.Data_, rhs.Data_);
+ }
+
+ TVersionedOwningRow& operator=(const TVersionedOwningRow& other)
+ {
+ Data_ = other.Data_;
+ return *this;
+ }
+
+ TVersionedOwningRow& operator=(TVersionedOwningRow&& other)
+ {
+ Data_ = std::move(other.Data_);
+ return *this;
+ }
+
+private:
+ TSharedMutableRef Data_;
+
+
+ const TVersionedRowHeader* GetHeader() const
+ {
+ return Data_ ? reinterpret_cast<const TVersionedRowHeader*>(Data_.Begin()) : nullptr;
+ }
+
+ TVersionedRowHeader* GetMutableHeader()
+ {
+ return Data_ ? reinterpret_cast<TVersionedRowHeader*>(Data_.Begin()) : nullptr;
+ }
+
+ TUnversionedValue* BeginMutableKeys()
+ {
+ return const_cast<TUnversionedValue*>(BeginKeys());
+ }
+
+ TVersionedValue* BeginMutableValues()
+ {
+ return const_cast<TVersionedValue*>(BeginValues());
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TVersionedValue& value, TStringBuf format);
+void FormatValue(TStringBuilderBase* builder, TVersionedRow row, TStringBuf format);
+void FormatValue(TStringBuilderBase* builder, TMutableVersionedRow row, TStringBuf format);
+void FormatValue(TStringBuilderBase* builder, const TVersionedOwningRow& row, TStringBuf format);
+
+TString ToString(const TVersionedValue& value);
+TString ToString(TVersionedRow row);
+TString ToString(TMutableVersionedRow row);
+TString ToString(const TVersionedOwningRow& row);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBitwiseVersionedValueHash
+{
+ size_t operator()(const TVersionedValue& value) const;
+};
+
+struct TBitwiseVersionedValueEqual
+{
+ bool operator()(const TVersionedValue& lhs, const TVersionedValue& rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBitwiseVersionedRowHash
+{
+ size_t operator()(TVersionedRow row) const;
+};
+
+struct TBitwiseVersionedRowEqual
+{
+ bool operator()(TVersionedRow lhs, TVersionedRow rhs) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/versioned_writer.h b/yt/yt/client/table_client/versioned_writer.h
new file mode 100644
index 0000000000..7c4c3980ea
--- /dev/null
+++ b/yt/yt/client/table_client/versioned_writer.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "versioned_row.h"
+
+#include <yt/yt/client/chunk_client/writer_base.h>
+
+#include <yt/yt/core/misc/range.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Writes a schemaful versioned rowset.
+/*!
+ * Writes versioned rowset with given schema.
+ * Useful for: compactions.
+ */
+struct IVersionedWriter
+ : public virtual NChunkClient::IWriterBase
+{
+ //! Enqueues more rows into the writer.
+ /*!
+ * Value ids must correspond to column indexes in schema.
+ * The rows must be canonically sorted (see TVersionedRow).
+ *
+ * If |false| is returned then the writer is overflowed (but the data is nevertheless accepted)
+ * The caller must wait for asynchronous flag provided by #GetReadyEvent to become set.
+ * The latter may indicate an error occurred while fetching more data.
+ */
+ virtual bool Write(TRange<TVersionedRow> rows) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IVersionedWriter)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/wire_protocol.cpp b/yt/yt/client/table_client/wire_protocol.cpp
new file mode 100644
index 0000000000..904969d78a
--- /dev/null
+++ b/yt/yt/client/table_client/wire_protocol.cpp
@@ -0,0 +1,1291 @@
+#include "wire_protocol.h"
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_reader.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <yt/yt/core/misc/bitmap.h>
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/protobuf_helpers.h>
+#include <yt/yt/core/misc/serialize.h>
+
+#include <yt/yt/core/compression/codec.h>
+
+#include <library/cpp/yt/misc/variant.h>
+
+#include <library/cpp/yt/memory/chunked_output_stream.h>
+#include <library/cpp/yt/memory/chunked_memory_pool.h>
+
+#include <util/system/sanitizers.h>
+
+#include <google/protobuf/io/coded_stream.h>
+
+namespace NYT::NTableClient {
+
+using NYT::ToProto;
+using NYT::FromProto;
+
+using NChunkClient::NProto::TDataStatistics;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const static NLogging::TLogger Logger("WireProtocol");
+
+struct TWireProtocolWriterTag
+{ };
+
+struct TWireProtocolReaderTag
+{ };
+
+static constexpr size_t ReaderBufferChunkSize = 4096;
+
+static constexpr size_t WriterInitialBufferCapacity = 1024;
+static constexpr size_t PreallocateBlockSize = 4096;
+
+static constexpr ui64 MinusOne = static_cast<ui64>(-1);
+
+static_assert(sizeof(i64) == SerializationAlignment, "Wrong serialization alignment");
+static_assert(sizeof(double) == SerializationAlignment, "Wrong serialization alignment");
+static_assert(sizeof(TUnversionedValue) == 16, "sizeof(TUnversionedValue) != 16");
+static_assert(sizeof(TUnversionedValueData) == 8, "sizeof(TUnversionedValueData) == 8");
+static_assert(sizeof(TUnversionedRowHeader) == 8, "sizeof(TUnversionedRowHeader) != 8");
+static_assert(sizeof(TVersionedValue) == 24, "sizeof(TVersionedValue) != 24");
+static_assert(sizeof(TVersionedRowHeader) == 16, "sizeof(TVersionedRowHeader) != 16");
+
+////////////////////////////////////////////////////////////////////////////////
+
+EWireProtocolCommand GetWireProtocolCommand(const TWireProtocolWriteCommand& command)
+{
+ EWireProtocolCommand result = EWireProtocolCommand::LookupRows;
+ Visit(command,
+ [&] (const TWriteRowCommand&) { result = EWireProtocolCommand::WriteRow; },
+ [&] (const TDeleteRowCommand&) { result = EWireProtocolCommand::DeleteRow; },
+ [&] (const TVersionedWriteRowCommand&) { result = EWireProtocolCommand::VersionedWriteRow; },
+ [&] (const TWriteAndLockRowCommand&) { result = EWireProtocolCommand::WriteAndLockRow; },
+ [&] (auto) { YT_ABORT(); });
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWireProtocolWriter
+ : public IWireProtocolWriter
+{
+public:
+ TWireProtocolWriter()
+ {
+ EnsureCapacity(WriterInitialBufferCapacity);
+ }
+
+ size_t GetByteSize() const override
+ {
+ return Stream_.GetSize();
+ }
+
+ void WriteCommand(EWireProtocolCommand command) override
+ {
+ WriteUint64(static_cast<unsigned int>(command));
+ }
+
+ void WriteLegacyLockBitmap(TLegacyLockBitmap lockBitmap) override
+ {
+ WriteUint64(lockBitmap);
+ }
+
+ void WriteLockMask(TLockMask lockMask) override
+ {
+ auto size = lockMask.GetSize();
+ YT_VERIFY(size <= TLockMask::MaxSize);
+ auto wordCount = DivCeil(size, TLockMask::LocksPerWord);
+
+ WriteUint16(size);
+
+ auto bitmap = lockMask.GetBitmap();
+ YT_VERIFY(std::ssize(bitmap) >= wordCount);
+ for (int index = 0; index < wordCount; ++index) {
+ WriteUint64(bitmap[index]);
+ }
+ }
+
+ void WriteTableSchema(const TTableSchema& schema) override
+ {
+ WriteMessage(ToProto<NTableClient::NProto::TTableSchemaExt>(schema));
+ }
+
+ void WriteMessage(const ::google::protobuf::MessageLite& message) override
+ {
+ size_t size = static_cast<size_t>(message.ByteSizeLong());
+ WriteUint64(size);
+ EnsureAlignedUpCapacity(size);
+ YT_VERIFY(message.SerializePartialToArray(Current_, size));
+ memset(Current_ + size, 0, AlignUpSpace(size, SerializationAlignment));
+
+ NSan::CheckMemIsInitialized(Current_, AlignUp<size_t>(size, SerializationAlignment));
+ Current_ += AlignUp<size_t>(size, SerializationAlignment);
+ }
+
+ void WriteInt64(i64 value) override
+ {
+ WriteUint64(static_cast<ui64>(value));
+ }
+
+ size_t WriteSchemafulRow(
+ TUnversionedRow row,
+ const TNameTableToSchemaIdMapping* idMapping) override
+ {
+ size_t bytes = EstimateSchemafulRowByteSize(row);
+ EnsureCapacity(bytes);
+
+ if (!row) {
+ UnsafeWriteUint64(MinusOne);
+ return bytes;
+ }
+
+ UnsafeWriteUint64(row.GetCount());
+ UnsafeWriteSchemafulValueRange(row.Elements(), idMapping);
+ return bytes;
+ }
+
+ size_t WriteUnversionedRow(
+ TUnversionedRow row,
+ const TNameTableToSchemaIdMapping* idMapping) override
+ {
+ size_t bytes = EstimateUnversionedRowByteSize(row);
+ EnsureCapacity(bytes);
+
+ if (!row) {
+ UnsafeWriteUint64(MinusOne);
+ return bytes;
+ }
+
+ UnsafeWriteUint64(row.GetCount());
+ UnsafeWriteUnversionedValueRange(row.Elements(), idMapping);
+ return bytes;
+ }
+
+ size_t WriteVersionedRow(TVersionedRow row) override
+ {
+ size_t bytes = EstimateVersionedRowByteSize(row);
+ EnsureCapacity(bytes);
+
+ if (!row) {
+ UnsafeWriteUint64(MinusOne);
+ return bytes;
+ }
+
+ UnsafeWriteRaw(row.GetHeader(), sizeof(TVersionedRowHeader));
+ UnsafeWriteRaw(row.BeginWriteTimestamps(), sizeof(TTimestamp) * row.GetWriteTimestampCount());
+ UnsafeWriteRaw(row.BeginDeleteTimestamps(), sizeof(TTimestamp) * row.GetDeleteTimestampCount());
+
+ UnsafeWriteSchemafulValueRange(row.Keys(), nullptr);
+ UnsafeWriteVersionedValueRange(row.Values());
+ return bytes;
+ }
+
+ void WriteUnversionedValueRange(
+ TUnversionedValueRange valueRange,
+ const TNameTableToSchemaIdMapping* idMapping) override
+ {
+ size_t bytes = AlignUp<size_t>(8, SerializationAlignment); // -1 or value count
+ bytes += EstimateUnversionedValueRangeByteSize(valueRange);
+ EnsureCapacity(bytes);
+
+ UnsafeWriteUint64(valueRange.Size());
+ UnsafeWriteUnversionedValueRange(valueRange, idMapping);
+ }
+
+ void WriteUnversionedRowset(
+ TRange<TUnversionedRow> rowset,
+ const TNameTableToSchemaIdMapping* idMapping) override
+ {
+ WriteRowCount(rowset);
+ for (auto row : rowset) {
+ WriteUnversionedRow(row, idMapping);
+ }
+ }
+
+ void WriteSchemafulRowset(
+ TRange<TUnversionedRow> rowset,
+ const TNameTableToSchemaIdMapping* idMapping) override
+ {
+ WriteRowCount(rowset);
+ for (auto row : rowset) {
+ WriteSchemafulRow(row, idMapping);
+ }
+ }
+
+ void WriteVersionedRowset(TRange<TVersionedRow> rowset) override
+ {
+ WriteRowCount(rowset);
+ for (auto row : rowset) {
+ WriteVersionedRow(row);
+ }
+ }
+
+ std::vector<TSharedRef> Finish() override
+ {
+ FlushPreallocated();
+ return Stream_.Finish();
+ }
+
+private:
+ TChunkedOutputStream Stream_{GetRefCountedTypeCookie<TWireProtocolWriterTag>()};
+
+ char EmptyBuf_[0];
+ char* BeginPreallocated_ = EmptyBuf_;
+ char* EndPreallocated_ = EmptyBuf_;
+ char* Current_ = EmptyBuf_;
+
+ std::vector<TUnversionedValue> PooledValues_;
+
+ void FlushPreallocated()
+ {
+ if (!Current_) {
+ return;
+ }
+
+ YT_VERIFY(Current_ <= EndPreallocated_);
+ Stream_.Advance(Current_ - BeginPreallocated_);
+ BeginPreallocated_ = EndPreallocated_ = Current_ = EmptyBuf_;
+ }
+
+ void EnsureCapacity(size_t more)
+ {
+ if (Y_LIKELY(Current_ + more < EndPreallocated_)) {
+ return;
+ }
+
+ FlushPreallocated();
+
+ size_t size = std::max(PreallocateBlockSize, more);
+ Current_ = BeginPreallocated_ = Stream_.Preallocate(size);
+ EndPreallocated_ = BeginPreallocated_ + size;
+ }
+
+ void EnsureAlignedUpCapacity(size_t more)
+ {
+ EnsureCapacity(AlignUp<size_t>(more, SerializationAlignment));
+ }
+
+ void UnsafeWriteRaw(const void* buffer, size_t size)
+ {
+ NSan::CheckMemIsInitialized(buffer, size);
+
+ memcpy(Current_, buffer, size);
+ memset(Current_ + size, 0, AlignUp<size_t>(size, SerializationAlignment) - size);
+
+ NSan::CheckMemIsInitialized(Current_, AlignUp<size_t>(size, SerializationAlignment));
+ Current_ += AlignUp<size_t>(size, SerializationAlignment);
+ YT_ASSERT(Current_ <= EndPreallocated_);
+ }
+
+ template <class T>
+ void UnsafeWritePod(const T& value)
+ {
+ NSan::CheckMemIsInitialized(&value, sizeof(T));
+
+ static_assert(!std::is_reference<T>::value, "T must not be a reference");
+ static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
+ // Do not use #UnsafeWriteRaw here to allow compiler to optimize memcpy & AlignUp.
+ // Both of them are constexprs.
+ memcpy(Current_, &value, sizeof(T));
+ memset(Current_ + sizeof(T), 0, AlignUpSpace(sizeof(T), SerializationAlignment));
+
+ NSan::CheckMemIsInitialized(Current_, AlignUp<size_t>(sizeof(T), SerializationAlignment));
+ Current_ += AlignUp<size_t>(sizeof(T), SerializationAlignment);
+ YT_ASSERT(Current_ <= EndPreallocated_);
+ }
+
+ void WriteUint16(ui16 value)
+ {
+ EnsureCapacity(AlignUp<size_t>(sizeof(ui16), SerializationAlignment));
+ UnsafeWritePod(value);
+ }
+
+ void WriteUint64(ui64 value)
+ {
+ EnsureCapacity(AlignUp<size_t>(sizeof(ui64), SerializationAlignment));
+ UnsafeWritePod(value);
+ }
+
+ void UnsafeWriteUint64(ui64 value)
+ {
+ UnsafeWritePod(value);
+ }
+
+ template <class TRow>
+ void WriteRowCount(TRange<TRow> rowset)
+ {
+ size_t rowCount = rowset.Size();
+ ValidateRowCount(rowCount);
+ WriteUint64(rowCount);
+ }
+
+ void UnsafeWriteSchemafulValue(const TUnversionedValue& value)
+ {
+ // Write data in-place.
+ if (IsStringLikeType(value.Type)) {
+ UnsafeWritePod<ui64>(value.Length);
+ UnsafeWriteRaw(value.Data.String, value.Length);
+ } else if (IsValueType(value.Type)) {
+ UnsafeWritePod(value.Data);
+ }
+ }
+
+ void UnsafeWriteUnversionedValue(const TUnversionedValue& value)
+ {
+ // Write header (id, type, flags, length).
+ UnsafeWritePod(*reinterpret_cast<const ui64*>(&value));
+ // Write data in-place.
+ if (IsStringLikeType(value.Type)) {
+ UnsafeWriteRaw(value.Data.String, value.Length);
+ } else if (IsValueType(value.Type)) {
+ UnsafeWritePod(value.Data);
+ }
+ }
+
+ void UnsafeWriteVersionedValue(const TVersionedValue& value)
+ {
+ // Write header (id, type, flags, length).
+ const ui64* rawValue = reinterpret_cast<const ui64*>(&value);
+ UnsafeWritePod<ui64>(rawValue[0]);
+ // Write data in-place.
+ if (IsStringLikeType(value.Type)) {
+ UnsafeWriteRaw(value.Data.String, value.Length);
+ } else if (IsValueType(value.Type)) {
+ UnsafeWritePod(value.Data);
+ }
+ // Write timestamp.
+ UnsafeWritePod<ui64>(value.Timestamp);
+ }
+
+ TUnversionedValueRange RemapValues(
+ TUnversionedValueRange values,
+ const TNameTableToSchemaIdMapping* idMapping)
+ {
+ auto valueCount = values.Size();
+ PooledValues_.resize(valueCount);
+ for (size_t index = 0; index < valueCount; ++index){
+ const auto& srcValue = values[index];
+ auto& dstValue = PooledValues_[index];
+ dstValue = srcValue;
+ dstValue.Id = static_cast<ui16>((*idMapping)[srcValue.Id]);
+ }
+
+ std::sort(
+ PooledValues_.begin(),
+ PooledValues_.end(),
+ [] (const TUnversionedValue& lhs, const TUnversionedValue& rhs) -> bool {
+ return lhs.Id < rhs.Id;
+ });
+
+ return MakeRange(PooledValues_);
+ }
+
+ void UnsafeWriteNullBitmap(TUnversionedValueRange values)
+ {
+ // TODO(lukyan): Allocate space and write directly.
+ auto nullBitmap = TBitmapOutput(values.Size());
+ for (int index = 0; index < std::ssize(values); ++index) {
+ nullBitmap.Append(values[index].Type == EValueType::Null);
+ }
+ UnsafeWriteRaw(nullBitmap.GetData(), nullBitmap.GetByteSize());
+ }
+
+ void UnsafeWriteSchemafulValueRange(
+ TUnversionedValueRange values,
+ const TNameTableToSchemaIdMapping* idMapping)
+ {
+ if (idMapping) {
+ values = RemapValues(values, idMapping);
+ }
+ UnsafeWriteNullBitmap(values);
+ for (const auto& value : values) {
+ UnsafeWriteSchemafulValue(value);
+ }
+ }
+
+ void UnsafeWriteUnversionedValueRange(
+ TUnversionedValueRange values,
+ const TNameTableToSchemaIdMapping* idMapping)
+ {
+ if (idMapping) {
+ values = RemapValues(values, idMapping);
+ }
+ for (const auto& value : values) {
+ UnsafeWriteUnversionedValue(value);
+ }
+ }
+
+ void UnsafeWriteVersionedValueRange(
+ TRange<TVersionedValue> values)
+ {
+ for (const auto& value : values) {
+ UnsafeWriteVersionedValue(value);
+ }
+ }
+
+ size_t EstimateSchemafulValueRangeByteSize(TUnversionedValueRange values)
+ {
+ size_t bytes = 0;
+ bytes += AlignUp<size_t>(NBitmapDetail::GetByteSize(values.Size()), SerializationAlignment); // null bitmap
+ for (const auto& value : values) {
+ if (IsStringLikeType(value.Type)) {
+ bytes += AlignUp<size_t>(8 + value.Length, SerializationAlignment);
+ } else if (value.Type != EValueType::Null) {
+ bytes += AlignUp<size_t>(8, SerializationAlignment);
+ }
+ }
+ return bytes;
+ }
+
+ size_t EstimateUnversionedValueRangeByteSize(TUnversionedValueRange values)
+ {
+ size_t bytes = 0;
+ for (const auto& value : values) {
+ bytes += AlignUp<size_t>(8, SerializationAlignment);
+ if (IsStringLikeType(value.Type)) {
+ bytes += AlignUp<size_t>(value.Length, SerializationAlignment);
+ } else if (value.Type != EValueType::Null) {
+ bytes += AlignUp<size_t>(8, SerializationAlignment);
+ }
+ }
+ return bytes;
+ }
+
+ size_t EstimateVersionedValueRangeByteSize(TRange<TVersionedValue> values)
+ {
+ size_t bytes = 0;
+ for (const auto& value : values) {
+ bytes += AlignUp<size_t>(16, SerializationAlignment);
+ if (IsStringLikeType(value.Type)) {
+ bytes += AlignUp<size_t>(value.Length, SerializationAlignment);
+ } else if (value.Type != EValueType::Null) {
+ bytes += AlignUp<size_t>(8, SerializationAlignment);
+ }
+ }
+ return bytes;
+ }
+
+ size_t EstimateSchemafulRowByteSize(TUnversionedRow row)
+ {
+ size_t bytes = AlignUp<size_t>(8, SerializationAlignment); // -1 or value count
+ if (row) {
+ bytes += EstimateSchemafulValueRangeByteSize(row.Elements());
+ }
+ return bytes;
+ }
+
+ size_t EstimateUnversionedRowByteSize(TUnversionedRow row)
+ {
+ size_t bytes = AlignUp<size_t>(8, SerializationAlignment); // -1 or value count
+ if (row) {
+ bytes += EstimateUnversionedValueRangeByteSize(row.Elements());
+ }
+ return bytes;
+ }
+
+ size_t EstimateVersionedRowByteSize(TVersionedRow row)
+ {
+ size_t bytes = AlignUp<size_t>(8, SerializationAlignment); // -1 or value count
+ if (row) {
+ bytes += AlignUp<size_t>(8, SerializationAlignment); // -1 or value count
+ bytes += AlignUp<size_t>(sizeof(TTimestamp) * (
+ row.GetWriteTimestampCount() +
+ row.GetDeleteTimestampCount()), // timestamps
+ SerializationAlignment);
+ bytes += EstimateSchemafulValueRangeByteSize(row.Keys());
+ bytes += EstimateVersionedValueRangeByteSize(row.Values());
+ }
+ return bytes;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IWireProtocolWriter> CreateWireProtocolWriter()
+{
+ return std::make_unique<TWireProtocolWriter>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWireProtocolReader
+ : public IWireProtocolReader
+{
+public:
+ explicit TWireProtocolReader(TSharedRef data, TRowBufferPtr rowBuffer)
+ : RowBuffer_(rowBuffer ? std::move(rowBuffer) : New<TRowBuffer>(TWireProtocolReaderTag(), ReaderBufferChunkSize))
+ , Data_(std::move(data))
+ , Current_(Data_.Begin())
+ { }
+
+ const TRowBufferPtr& GetRowBuffer() const override
+ {
+ return RowBuffer_;
+ }
+
+ bool IsFinished() const override
+ {
+ return Current_ == Data_.End();
+ }
+
+ TIterator GetBegin() const override
+ {
+ return Data_.Begin();
+ }
+
+ TIterator GetEnd() const override
+ {
+ return Data_.End();
+ }
+
+ TIterator GetCurrent() const override
+ {
+ return Current_;
+ }
+
+ void SetCurrent(TIterator current) override
+ {
+ Current_ = current;
+ }
+
+ TSharedRef Slice(TIterator begin, TIterator end) override
+ {
+ return Data_.Slice(begin, end);
+ }
+
+ EWireProtocolCommand ReadCommand() override
+ {
+ return EWireProtocolCommand(ReadUint64());
+ }
+
+ TLegacyLockBitmap ReadLegacyLockBitmap() override
+ {
+ return ReadUint64();
+ }
+
+ TLockMask ReadLockMask() override
+ {
+ auto size = ReadUint16();
+ auto wordCount = DivCeil<int>(size, TLockMask::LocksPerWord);
+
+ TLockBitmap bitmap;
+ bitmap.reserve(wordCount);
+ for (int index = 0; index < wordCount; ++index) {
+ bitmap.push_back(ReadUint64());
+ }
+
+ return TLockMask(bitmap, size);
+ }
+
+ TTableSchema ReadTableSchema() override
+ {
+ NTableClient::NProto::TTableSchemaExt protoSchema;
+ ReadMessage(&protoSchema);
+ return FromProto<TTableSchema>(protoSchema);
+ }
+
+ void ReadMessage(::google::protobuf::MessageLite* message) override
+ {
+ size_t size = ReadUint64();
+ ::google::protobuf::io::CodedInputStream chunkStream(
+ reinterpret_cast<const ui8*>(Current_),
+ static_cast<int>(size));
+ Y_PROTOBUF_SUPPRESS_NODISCARD message->ParsePartialFromCodedStream(&chunkStream);
+ Current_ += AlignUp<size_t>(size, SerializationAlignment);
+ }
+
+ i64 ReadInt64() override
+ {
+ return static_cast<i64>(ReadUint64());
+ }
+
+ TUnversionedRow ReadSchemafulRow(const TSchemaData& schemaData, bool captureValues) override
+ {
+ auto valueCount = ReadUint64();
+ if (valueCount == MinusOne) {
+ return TUnversionedRow();
+ }
+ ValidateRowValueCount(valueCount);
+ auto row = RowBuffer_->AllocateUnversioned(valueCount);
+ DoReadSchemafulValueRange(schemaData, captureValues, row.Begin(), valueCount);
+ return row;
+ }
+
+ TUnversionedRow ReadUnversionedRow(bool captureValues, const TIdMapping* idMapping) override
+ {
+ auto valueCount = ReadUint64();
+ if (valueCount == MinusOne) {
+ return TUnversionedRow();
+ }
+ ValidateRowValueCount(valueCount);
+ auto row = RowBuffer_->AllocateUnversioned(valueCount);
+ DoReadUnversionedValueRange(captureValues, row.Begin(), valueCount, idMapping);
+ return row;
+ }
+
+ TVersionedRow ReadVersionedRow(const TSchemaData& schemaData, bool captureValues, const TIdMapping* valueIdMapping) override
+ {
+ union
+ {
+ ui64 Parts[2];
+ TVersionedRowHeader Value;
+ } header;
+
+ header.Parts[0] = ReadUint64();
+ if (header.Parts[0] == MinusOne) {
+ return TVersionedRow();
+ }
+ header.Parts[1] = ReadUint64();
+
+ ValidateKeyColumnCount(header.Value.KeyCount);
+ ValidateVersionedRowTimestampCount(header.Value);
+
+ auto row = TMutableVersionedRow::Allocate(
+ RowBuffer_->GetPool(),
+ header.Value.KeyCount,
+ header.Value.ValueCount,
+ header.Value.WriteTimestampCount,
+ header.Value.DeleteTimestampCount);
+
+ ReadRaw(row.BeginWriteTimestamps(), sizeof(TTimestamp) * row.GetWriteTimestampCount());
+ ReadRaw(row.BeginDeleteTimestamps(), sizeof(TTimestamp) * row.GetDeleteTimestampCount());
+
+ DoReadSchemafulValueRange(schemaData, captureValues, row.BeginKeys(), header.Value.KeyCount);
+ DoReadVersionedValueRange(captureValues, row.BeginValues(), header.Value.ValueCount, valueIdMapping);
+
+ ValidateVersionedRowDataWeight(row);
+
+ return row;
+ }
+
+ TSharedRange<TUnversionedRow> ReadSchemafulRowset(const TSchemaData& schemaData, bool captureValues) override
+ {
+ int rowCount = DoReadRowCount();
+ auto* rows = RowBuffer_->GetPool()->AllocateUninitialized<TUnversionedRow>(rowCount);
+ for (int index = 0; index < rowCount; ++index) {
+ rows[index] = ReadSchemafulRow(schemaData, captureValues);
+ }
+
+ auto range = TRange<TUnversionedRow>(rows, rows + rowCount);
+ return captureValues ? MakeSharedRange(range, RowBuffer_) : MakeSharedRange(range, RowBuffer_, Data_);
+ }
+
+ TSharedRange<TUnversionedRow> ReadUnversionedRowset(bool captureValues, const TIdMapping* idMapping) override
+ {
+ int rowCount = DoReadRowCount();
+ auto* rows = RowBuffer_->GetPool()->AllocateUninitialized<TUnversionedRow>(rowCount);
+ for (int index = 0; index < rowCount; ++index) {
+ rows[index] = ReadUnversionedRow(captureValues, idMapping);
+ }
+ auto range = TRange<TUnversionedRow>(rows, rows + rowCount);
+ return captureValues ? MakeSharedRange(range, RowBuffer_) : MakeSharedRange(range, RowBuffer_, Data_);
+ }
+
+ TSharedRange<TVersionedRow> ReadVersionedRowset(
+ const TSchemaData& schemaData,
+ bool captureValues,
+ const TIdMapping* valueIdMapping) override
+ {
+ int rowCount = DoReadRowCount();
+ auto* rows = RowBuffer_->GetPool()->AllocateUninitialized<TVersionedRow>(rowCount);
+ for (int index = 0; index < rowCount; ++index) {
+ rows[index] = ReadVersionedRow(schemaData, captureValues, valueIdMapping);
+ }
+ auto range = TRange<TVersionedRow>(rows, rows + rowCount);
+ return captureValues ? MakeSharedRange(range, RowBuffer_) : MakeSharedRange(range, RowBuffer_, Data_);
+ }
+
+ TWireProtocolWriteCommand ReadWriteCommand(
+ const TSchemaData& schemaData,
+ bool captureValues,
+ bool versionedWriteIsUnversioned) override
+ {
+ auto command = ReadCommand();
+ switch (command) {
+ case EWireProtocolCommand::WriteRow: {
+ auto row = ReadUnversionedRow(captureValues, /*idMapping*/ nullptr);
+ return TWriteRowCommand{
+ .Row = row
+ };
+ }
+ case EWireProtocolCommand::DeleteRow: {
+ auto row = ReadUnversionedRow(captureValues, /*idMapping*/ nullptr);
+ return TDeleteRowCommand{
+ .Row = row
+ };
+ }
+ case EWireProtocolCommand::VersionedWriteRow: {
+ if (versionedWriteIsUnversioned) {
+ auto unversionedRow = ReadUnversionedRow(captureValues, /*idMapping*/ nullptr);
+ return TVersionedWriteRowCommand{
+ .UnversionedRow = unversionedRow
+ };
+ } else {
+ auto versionedRow = ReadVersionedRow(schemaData, captureValues, /*valueIdMapping*/ nullptr);
+ return TVersionedWriteRowCommand{
+ .VersionedRow = versionedRow
+ };
+ }
+ }
+ // COMPAT(gritukan)
+ case EWireProtocolCommand::ReadLockWriteRow: {
+ TLockMask lockMask;
+ TLegacyLockMask legacyLocks(ReadLegacyLockBitmap());
+ for (int index = 0; index < TLegacyLockMask::MaxCount; ++index) {
+ lockMask.Set(index, legacyLocks.Get(index));
+ }
+
+ auto row = ReadUnversionedRow(captureValues, /*idMapping*/ nullptr);
+ return TWriteAndLockRowCommand{
+ .Row = row,
+ .LockMask = std::move(lockMask)
+ };
+ }
+ case EWireProtocolCommand::WriteAndLockRow: {
+ auto row = ReadUnversionedRow(captureValues, /*idMapping*/ nullptr);
+ auto lockMask = ReadLockMask();
+ return TWriteAndLockRowCommand{
+ .Row = row,
+ .LockMask = std::move(lockMask)
+ };
+ }
+ default:
+ YT_LOG_FATAL("Unknown write command (Command: %v)",
+ command);
+ }
+ }
+
+private:
+ const TRowBufferPtr RowBuffer_;
+
+ TSharedRef Data_;
+ TIterator Current_;
+
+ void ValidateSizeAvailable(size_t size)
+ {
+ if (Current_ + size > Data_.End()) {
+ THROW_ERROR_EXCEPTION("Premature end of stream while reading %v bytes", size);
+ }
+ }
+
+ void ReadRaw(void* buffer, size_t size)
+ {
+ ValidateSizeAvailable(size);
+
+ memcpy(buffer, Current_, size);
+ Current_ += size;
+ Current_ += AlignUpSpace(size, SerializationAlignment);
+ }
+
+ const char* PeekRaw(size_t size)
+ {
+ ValidateSizeAvailable(size);
+
+ auto result = Current_;
+ Current_ += size;
+ Current_ += AlignUpSpace(size, SerializationAlignment);
+ return result;
+ }
+
+ template <class T>
+ void ReadPod(T* value)
+ {
+ ValidateSizeAvailable(sizeof(T));
+
+ memcpy(value, Current_, sizeof(T));
+ Current_ += sizeof(T);
+ Current_ += AlignUpSpace(sizeof(T), SerializationAlignment);
+ }
+
+ ui64 ReadUint64()
+ {
+ ui64 value;
+ ReadPod(&value);
+ return value;
+ }
+
+ ui32 ReadUint32()
+ {
+ ui64 result = ReadUint64();
+ if (result > std::numeric_limits<ui32>::max()) {
+ THROW_ERROR_EXCEPTION("Value is out of range to fit into uint32");
+ }
+ return static_cast<ui32>(result);
+ }
+
+ ui16 ReadUint16()
+ {
+ ui64 result = ReadUint64();
+ if (result > std::numeric_limits<ui16>::max()) {
+ THROW_ERROR_EXCEPTION("Value is out of range to fit into uint16");
+ }
+ return static_cast<ui16>(result);
+ }
+
+ i32 ReadInt32()
+ {
+ i64 result = ReadInt64();
+ if (result < std::numeric_limits<i32>::min() || result > std::numeric_limits<i32>::max()) {
+ THROW_ERROR_EXCEPTION("Value is out of range to fit into int32");
+ }
+ return static_cast<i32>(result);
+ }
+
+ void DoReadStringData(EValueType type, ui32 length, const char** result, bool captureValues)
+ {
+ ui32 limit = 0;
+ if (type == EValueType::String) {
+ limit = MaxStringValueLength;
+ }
+ if (type == EValueType::Any) {
+ limit = MaxAnyValueLength;
+ }
+ if (type == EValueType::Composite) {
+ limit = MaxCompositeValueLength;
+ }
+ if (length > limit) {
+ THROW_ERROR_EXCEPTION("Value of type %Qlv is too long: length %v, limit %v",
+ type,
+ length,
+ limit);
+ }
+ if (captureValues) {
+ char* tmp = RowBuffer_->GetPool()->AllocateUnaligned(length);
+ ReadRaw(tmp, length);
+ *result = tmp;
+ } else {
+ *result = PeekRaw(length);
+ }
+ }
+
+ int DoReadRowCount()
+ {
+ int rowCount = ReadInt32();
+ ValidateRowCount(rowCount);
+ return rowCount;
+ }
+
+ void DoReadSchemafulValue(
+ ui32 schemaData,
+ bool null,
+ bool captureValues,
+ TUnversionedValue* value)
+ {
+ ui64* rawValue = reinterpret_cast<ui64*>(value);
+ rawValue[0] = schemaData;
+ if (null) {
+ value->Type = EValueType::Null;
+ // NB: Don't leave the second half uninitialized.
+ rawValue[1] = 0;
+ } else if (IsStringLikeType(value->Type)) {
+ value->Length = ReadUint32();
+ DoReadStringData(value->Type, value->Length, &value->Data.String, captureValues);
+ } else if (IsValueType(value->Type)) {
+ rawValue[1] = ReadUint64();
+ }
+ }
+
+ void DoReadUnversionedValue(bool captureValues, TUnversionedValue* value)
+ {
+ ui64* rawValue = reinterpret_cast<ui64*>(value);
+ rawValue[0] = ReadUint64();
+ if (IsStringLikeType(value->Type)) {
+ DoReadStringData(value->Type, value->Length, &value->Data.String, captureValues);
+ } else if (IsValueType(value->Type)) {
+ rawValue[1] = ReadUint64();
+ }
+ }
+
+ void DoReadVersionedValue(bool captureValues, TVersionedValue* value)
+ {
+ ui64* rawValue = reinterpret_cast<ui64*>(value);
+ rawValue[0] = ReadUint64();
+ if (IsStringLikeType(value->Type)) {
+ DoReadStringData(value->Type, value->Length, &value->Data.String, captureValues);
+ } else if (IsValueType(value->Type)) {
+ rawValue[1] = ReadUint64();
+ }
+ value->Timestamp = ReadUint64();
+ }
+
+ void DoReadSchemafulValueRange(
+ const TSchemaData& schemaData,
+ bool captureValues,
+ TUnversionedValue* values,
+ ui32 valueCount)
+ {
+ auto bitmapPtr = PeekRaw(NBitmapDetail::GetByteSize(valueCount));
+ TBitmap nullBitmap(bitmapPtr);
+ for (size_t index = 0; index < valueCount; ++index) {
+ DoReadSchemafulValue(schemaData[index], nullBitmap[index], captureValues, &values[index]);
+ }
+ }
+
+ void DoApplyIdMapping(ui16* id, int index, const TIdMapping* idMapping)
+ {
+ if (*id >= idMapping->size()) {
+ THROW_ERROR_EXCEPTION("Value with index %v has id %v which is out of range [0, %v)",
+ index,
+ *id,
+ idMapping->size());
+ }
+ int mappedId = (*idMapping)[*id];
+ if (mappedId == -1) {
+ THROW_ERROR_EXCEPTION("Id mapping for value with index %v contains unexpected value %Qv",
+ index,
+ -1);
+ }
+ *id = mappedId;
+ }
+
+ void DoReadUnversionedValueRange(
+ bool captureValues,
+ TUnversionedValue* values,
+ ui32 valueCount,
+ const TIdMapping* idMapping)
+ {
+ for (size_t index = 0; index < valueCount; ++index) {
+ DoReadUnversionedValue(captureValues, &values[index]);
+ if (idMapping) {
+ DoApplyIdMapping(&values[index].Id, index, idMapping);
+ }
+ }
+ }
+
+ void DoReadVersionedValueRange(
+ bool captureValues,
+ TVersionedValue* values,
+ ui32 valueCount,
+ const TIdMapping* valueIdMapping)
+ {
+ for (size_t index = 0; index < valueCount; ++index) {
+ DoReadVersionedValue(captureValues, &values[index]);
+ if (valueIdMapping) {
+ DoApplyIdMapping(&values[index].Id, index, valueIdMapping);
+ }
+ }
+ }
+
+ void ValidateVersionedRowTimestampCount(const TVersionedRowHeader& rowHeader)
+ {
+ if (rowHeader.WriteTimestampCount > MaxTimestampCountPerRow) {
+ THROW_ERROR_EXCEPTION("Too many write timestamps in a versioned row");
+ }
+ if (rowHeader.DeleteTimestampCount > MaxTimestampCountPerRow) {
+ THROW_ERROR_EXCEPTION("Too many delete timestamps in a versioned row");
+ }
+ }
+
+ void ValidateVersionedRowDataWeight(TVersionedRow row)
+ {
+ auto dataWeight = GetDataWeight(row);
+ if (dataWeight > MaxServerVersionedRowDataWeight) {
+ THROW_ERROR_EXCEPTION("Versioned row data weight is too large: %v > %v",
+ dataWeight,
+ MaxServerVersionedRowDataWeight)
+ << TErrorAttribute("key", ToOwningKey(row));
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+auto IWireProtocolReader::GetSchemaData(
+ const TTableSchema& schema,
+ const TColumnFilter& filter) -> TSchemaData
+{
+ TSchemaData schemaData;
+ auto addColumn = [&] (int id) {
+ auto value = MakeUnversionedValueHeader(schema.Columns()[id].GetWireType(), id);
+ schemaData.push_back(*reinterpret_cast<ui32*>(&value));
+ };
+ if (!filter.IsUniversal()) {
+ schemaData.reserve(std::ssize(filter.GetIndexes()));
+ for (int id : filter.GetIndexes()) {
+ addColumn(id);
+ }
+ } else {
+ schemaData.reserve(schema.GetColumnCount());
+ for (int id = 0; id < schema.GetColumnCount(); ++id) {
+ addColumn(id);
+ }
+ }
+ return schemaData;
+}
+
+auto IWireProtocolReader::GetSchemaData(const TTableSchema& schema) -> TSchemaData
+{
+ TSchemaData schemaData;
+ schemaData.reserve(schema.GetColumnCount());
+ for (int id = 0; id < schema.GetColumnCount(); ++id) {
+ auto value = MakeUnversionedValueHeader(schema.Columns()[id].GetWireType(), id);
+ schemaData.push_back(*reinterpret_cast<ui32*>(&value));
+ }
+ return schemaData;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IWireProtocolReader> CreateWireProtocolReader(TSharedRef data, TRowBufferPtr rowBuffer)
+{
+ return std::make_unique<TWireProtocolReader>(std::move(data), std::move(rowBuffer));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWireProtocolRowsetReader
+ : public IWireProtocolRowsetReader
+{
+public:
+ TWireProtocolRowsetReader(
+ const std::vector<TSharedRef>& compressedBlocks,
+ NCompression::ECodec codecId,
+ TTableSchemaPtr schema,
+ bool schemaful,
+ const NLogging::TLogger& logger)
+ : CompressedBlocks_(compressedBlocks)
+ , Codec_(NCompression::GetCodec(codecId))
+ , Schema_(std::move(schema))
+ , Schemaful_(schemaful)
+ , Logger(logger.WithTag("ReaderId: %v", TGuid::Create()))
+ {
+ YT_LOG_DEBUG("Wire protocol rowset reader created (BlockCount: %v, TotalCompressedSize: %v)",
+ CompressedBlocks_.size(),
+ GetByteSize(CompressedBlocks_));
+ }
+
+ IUnversionedRowBatchPtr Read(const TRowBatchReadOptions& /*options*/) override
+ {
+ if (Finished_) {
+ return nullptr;
+ }
+
+ if (BlockIndex_ >= std::ssize(CompressedBlocks_)) {
+ Finished_ = true;
+ YT_LOG_DEBUG("Wire protocol rowset reader finished");
+ return nullptr;
+ }
+
+ const auto& compressedBlock = CompressedBlocks_[BlockIndex_];
+ YT_LOG_DEBUG("Started decompressing rowset reader block (BlockIndex: %v, CompressedSize: %v)",
+ BlockIndex_,
+ compressedBlock.Size());
+ auto uncompressedBlock = Codec_->Decompress(compressedBlock);
+ YT_LOG_DEBUG("Finished decompressing rowset reader block (BlockIndex: %v, UncompressedSize: %v)",
+ BlockIndex_,
+ uncompressedBlock.Size());
+
+ auto rowBuffer = New<TRowBuffer>(TWireProtocolReaderTag(), ReaderBufferChunkSize);
+ WireReader_ = CreateWireProtocolReader(uncompressedBlock, std::move(rowBuffer));
+
+ if (!SchemaChecked_) {
+ auto actualSchema = WireReader_->ReadTableSchema();
+
+ //
+ // NB this comparison is compat for YT-10668
+ // This could be replaced with simple `operator==', once all nodes and proxies are updated to have new schema
+ // representation introduced in cec93e9435fc3bbecc02ee5b8fd9ffa0eafc1672
+ //
+ // Guess it will be surely the case after after 01.11.2019
+ if (!IsEqualIgnoringRequiredness(*Schema_, actualSchema)) {
+ THROW_ERROR_EXCEPTION("Schema mismatch while parsing wire protocol");
+ }
+ SchemaChecked_ = true;
+ }
+
+ auto schemaData = WireReader_->GetSchemaData(*Schema_, TColumnFilter());
+
+ std::vector<TUnversionedRow> rows;
+ while (!WireReader_->IsFinished()) {
+ auto row = Schemaful_
+ ? WireReader_->ReadSchemafulRow(schemaData, false)
+ : WireReader_->ReadUnversionedRow(false);
+ rows.push_back(row);
+ }
+ ++BlockIndex_;
+
+ return CreateBatchFromUnversionedRows(MakeSharedRange(std::move(rows), MakeStrong(this)));
+ }
+
+ TFuture<void> GetReadyEvent() const override
+ {
+ return VoidFuture;
+ }
+
+ TDataStatistics GetDataStatistics() const override
+ {
+ YT_ABORT();
+ }
+
+ NChunkClient::TCodecStatistics GetDecompressionStatistics() const override
+ {
+ YT_ABORT();
+ }
+
+ bool IsFetchingCompleted() const override
+ {
+ return false;
+ }
+
+ std::vector<NChunkClient::TChunkId> GetFailedChunkIds() const override
+ {
+ return {};
+ }
+
+private:
+ const std::vector<TSharedRef> CompressedBlocks_;
+ NCompression::ICodec* const Codec_;
+ const TTableSchemaPtr Schema_;
+ bool Schemaful_;
+ const NLogging::TLogger Logger;
+
+ int BlockIndex_ = 0;
+ std::unique_ptr<IWireProtocolReader> WireReader_;
+ bool Finished_ = false;
+ bool SchemaChecked_ = false;
+
+};
+
+IWireProtocolRowsetReaderPtr CreateWireProtocolRowsetReader(
+ const std::vector<TSharedRef>& compressedBlocks,
+ NCompression::ECodec codecId,
+ TTableSchemaPtr schema,
+ bool schemaful,
+ const NLogging::TLogger& logger)
+{
+ return New<TWireProtocolRowsetReader>(
+ compressedBlocks,
+ codecId,
+ std::move(schema),
+ schemaful,
+ logger);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TWireProtocolRowsetWriter
+ : public IWireProtocolRowsetWriter
+{
+public:
+ TWireProtocolRowsetWriter(
+ NCompression::ECodec codecId,
+ size_t desiredUncompressedBlockSize,
+ TTableSchemaPtr schema,
+ bool schemaful,
+ const NLogging::TLogger& logger)
+ : Codec_(NCompression::GetCodec(codecId))
+ , DesiredUncompressedBlockSize_(desiredUncompressedBlockSize)
+ , Schema_(std::move(schema))
+ , Schemaful_(schemaful)
+ , Logger(logger.WithTag("WriterId: %v", TGuid::Create()))
+ {
+ YT_LOG_DEBUG("Wire protocol rowset writer created (Codec: %v, DesiredUncompressedBlockSize: %v)",
+ codecId,
+ DesiredUncompressedBlockSize_);
+ }
+
+ TFuture<void> Close() override
+ {
+ if (!Closed_) {
+ YT_LOG_DEBUG("Wire protocol rowset writer closed");
+ FlushBlock();
+ Closed_ = true;
+ }
+ return VoidFuture;
+ }
+
+ bool Write(TRange<TUnversionedRow> rows) override
+ {
+ YT_VERIFY(!Closed_);
+ for (auto row : rows) {
+ if (!WireWriter_) {
+ WireWriter_ = CreateWireProtocolWriter();
+ if (!SchemaWritten_) {
+ WireWriter_->WriteTableSchema(*Schema_);
+ SchemaWritten_ = true;
+ }
+ }
+ if (Schemaful_) {
+ WireWriter_->WriteSchemafulRow(row);
+ } else {
+ WireWriter_->WriteUnversionedRow(row);
+ }
+ if (WireWriter_->GetByteSize() >= DesiredUncompressedBlockSize_) {
+ FlushBlock();
+ }
+ }
+ return true;
+ }
+
+ TFuture<void> GetReadyEvent() override
+ {
+ return VoidFuture;
+ }
+
+ std::vector<TSharedRef> GetCompressedBlocks() override
+ {
+ YT_VERIFY(Closed_);
+ return CompressedBlocks_;
+ }
+
+private:
+ NCompression::ICodec* const Codec_;
+ const size_t DesiredUncompressedBlockSize_;
+ const TTableSchemaPtr Schema_;
+ const bool Schemaful_;
+ const NLogging::TLogger Logger;
+
+ std::vector<TSharedRef> CompressedBlocks_;
+ std::unique_ptr<IWireProtocolWriter> WireWriter_;
+ bool Closed_ = false;
+ bool SchemaWritten_ = false;
+
+
+ void FlushBlock()
+ {
+ if (!WireWriter_) {
+ return;
+ }
+
+ auto uncompressedBlocks = WireWriter_->Finish();
+
+ YT_LOG_DEBUG("Started compressing rowset writer block (BlockIndex: %v, UncompressedSize: %v)",
+ CompressedBlocks_.size(),
+ GetByteSize(uncompressedBlocks));
+ auto compressedBlock = Codec_->Compress(uncompressedBlocks);
+ YT_LOG_DEBUG("Finished compressing rowset writer block (BlockIndex: %v, CompressedSize: %v)",
+ CompressedBlocks_.size(),
+ compressedBlock.Size());
+
+ CompressedBlocks_.push_back(compressedBlock);
+ WireWriter_.reset();
+ }
+};
+
+IWireProtocolRowsetWriterPtr CreateWireProtocolRowsetWriter(
+ NCompression::ECodec codecId,
+ size_t desiredUncompressedBlockSize,
+ TTableSchemaPtr schema,
+ bool schemaful,
+ const NLogging::TLogger& logger)
+{
+ return New<TWireProtocolRowsetWriter>(
+ codecId,
+ desiredUncompressedBlockSize,
+ std::move(schema),
+ schemaful,
+ logger);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/table_client/wire_protocol.h b/yt/yt/client/table_client/wire_protocol.h
new file mode 100644
index 0000000000..a38259b986
--- /dev/null
+++ b/yt/yt/client/table_client/wire_protocol.h
@@ -0,0 +1,323 @@
+#pragma once
+
+#include "public.h"
+#include "schema.h"
+
+#include <yt/yt/client/api/public.h>
+
+#include <yt/yt/client/table_client/unversioned_reader.h>
+#include <yt/yt/client/table_client/unversioned_writer.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.pb.h>
+
+#include <yt/yt/core/misc/range.h>
+
+#include <yt/yt/core/compression/public.h>
+
+#include <yt/yt/core/logging/log.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EWireProtocolCommand,
+ // Read commands:
+
+ ((LookupRows)(1))
+ // Finds rows with given keys and fetches their components.
+ //
+ // Input:
+ // * TReqLookupRows
+ // * Unversioned rowset containing N keys
+ //
+ // Output:
+ // * N unversioned rows
+
+ ((VersionedLookupRows)(2))
+ // Finds rows with given keys and fetches their components.
+ //
+ // Input:
+ // * TReqLookupRows
+ // * Unversioned rowset containing N keys
+ //
+ // Output:
+ // * N versioned rows
+
+ // Write commands:
+
+ ((WriteRow)(100))
+ // Inserts a new row or completely replaces an existing one with matching key.
+ //
+ // Input:
+ // * Unversioned row
+ // Output:
+ // None
+
+ ((DeleteRow)(101))
+ // Deletes a row with a given key, if it exists.
+ //
+ // Input:
+ // * Key
+ // Output:
+ // None
+
+ ((VersionedWriteRow)(102))
+ // Writes a versioned row (possibly inserting new values and/or delete timestamps).
+ // Currently only used by replicator.
+ //
+ // Input:
+ // * Versioned row
+ // Output:
+ // None
+
+ // Other commands:
+
+ ((ReadLockWriteRow)(103))
+ // Take primary read lock and optionally modify row
+ // Deprecated.
+ //
+ // Input:
+ // * Key
+ // Output:
+ // None
+
+ ((WriteAndLockRow)(104))
+ // Take locks on row and optionally modify row
+ //
+ // Input:
+ // * Unversioned row
+ // * Lock mask
+ // Output:
+ // None
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWriteRowCommand
+{
+ TUnversionedRow Row;
+};
+
+struct TDeleteRowCommand
+{
+ TUnversionedRow Row;
+};
+
+struct TVersionedWriteRowCommand
+{
+ // Versioned write uses versioned rows for sorted tables
+ // and unversioned rows for ordered tables.
+ union
+ {
+ TUnversionedRow UnversionedRow;
+ TVersionedRow VersionedRow;
+ };
+};
+
+struct TWriteAndLockRowCommand
+{
+ TUnversionedRow Row;
+ TLockMask LockMask;
+};
+
+using TWireProtocolWriteCommand = std::variant<
+ TWriteRowCommand,
+ TDeleteRowCommand,
+ TVersionedWriteRowCommand,
+ TWriteAndLockRowCommand
+>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+EWireProtocolCommand GetWireProtocolCommand(const TWireProtocolWriteCommand& command);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Builds wire-encoded stream.
+struct IWireProtocolWriter
+{
+public:
+ virtual ~IWireProtocolWriter() = default;
+
+ virtual size_t GetByteSize() const = 0;
+
+ virtual void WriteCommand(EWireProtocolCommand command) = 0;
+
+ virtual void WriteLegacyLockBitmap(TLegacyLockBitmap lockBitmap) = 0;
+
+ virtual void WriteLockMask(TLockMask lockMask) = 0;
+
+ virtual void WriteTableSchema(const NTableClient::TTableSchema& schema) = 0;
+
+ virtual void WriteMessage(const ::google::protobuf::MessageLite& message) = 0;
+
+ virtual void WriteInt64(i64 value) = 0;
+
+ virtual size_t WriteUnversionedRow(
+ NTableClient::TUnversionedRow row,
+ const NTableClient::TNameTableToSchemaIdMapping* idMapping = nullptr) = 0;
+ virtual size_t WriteSchemafulRow(
+ NTableClient::TUnversionedRow row,
+ const NTableClient::TNameTableToSchemaIdMapping* idMapping = nullptr) = 0;
+ virtual size_t WriteVersionedRow(NTableClient::TVersionedRow row) = 0;
+
+ virtual void WriteUnversionedValueRange(
+ NTableClient::TUnversionedValueRange valueRange,
+ const NTableClient::TNameTableToSchemaIdMapping* idMapping = nullptr) = 0;
+
+ virtual void WriteUnversionedRowset(
+ TRange<NTableClient::TUnversionedRow> rowset,
+ const NTableClient::TNameTableToSchemaIdMapping* idMapping = nullptr) = 0;
+ virtual void WriteSchemafulRowset(
+ TRange<NTableClient::TUnversionedRow> rowset,
+ const NTableClient::TNameTableToSchemaIdMapping* idMapping = nullptr) = 0;
+ virtual void WriteVersionedRowset(TRange<NTableClient::TVersionedRow> rowset) = 0;
+
+ virtual std::vector<TSharedRef> Finish() = 0;
+
+ template <class TRow>
+ inline void WriteRowset(TRange<TRow> rowset);
+};
+
+template <>
+inline void IWireProtocolWriter::WriteRowset<NTableClient::TUnversionedRow>(
+ TRange<NTableClient::TUnversionedRow> rowset)
+{
+ return WriteUnversionedRowset(rowset);
+}
+
+template <>
+inline void IWireProtocolWriter::WriteRowset<NTableClient::TVersionedRow>(
+ TRange<NTableClient::TVersionedRow> rowset)
+{
+ return WriteVersionedRowset(rowset);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IWireProtocolWriter> CreateWireProtocolWriter();
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Reads wire-encoded stream.
+/*!
+ * All |ReadXXX| methods obey the following convention.
+ * Rows are captured by the row buffer passed in ctor.
+ * Values are either captured or not depending on |captureValues| argument.
+ */
+struct IWireProtocolReader
+{
+public:
+ using TIterator = const char*;
+
+ virtual ~IWireProtocolReader() = default;
+
+ virtual const NTableClient::TRowBufferPtr& GetRowBuffer() const = 0;
+
+ virtual bool IsFinished() const = 0;
+ virtual TIterator GetBegin() const = 0;
+ virtual TIterator GetEnd() const = 0;
+
+ virtual TIterator GetCurrent() const = 0;
+ virtual void SetCurrent(TIterator) = 0;
+
+ virtual TSharedRef Slice(TIterator begin, TIterator end) = 0;
+
+ virtual EWireProtocolCommand ReadCommand() = 0;
+
+ virtual TLegacyLockBitmap ReadLegacyLockBitmap() = 0;
+
+ virtual TLockMask ReadLockMask() = 0;
+
+ virtual NTableClient::TTableSchema ReadTableSchema() = 0;
+
+ virtual void ReadMessage(::google::protobuf::MessageLite* message) = 0;
+
+ virtual i64 ReadInt64() = 0;
+
+ virtual NTableClient::TUnversionedRow ReadUnversionedRow(
+ bool captureValues,
+ const TIdMapping* idMapping = nullptr) = 0;
+ virtual NTableClient::TUnversionedRow ReadSchemafulRow(
+ const TSchemaData& schemaData,
+ bool captureValues) = 0;
+ virtual NTableClient::TVersionedRow ReadVersionedRow(
+ const TSchemaData& schemaData,
+ bool captureValues,
+ const TIdMapping* valueIdMapping = nullptr) = 0;
+
+ virtual TSharedRange<NTableClient::TUnversionedRow> ReadUnversionedRowset(
+ bool captureValues,
+ const TIdMapping* idMapping = nullptr) = 0;
+ virtual TSharedRange<NTableClient::TUnversionedRow> ReadSchemafulRowset(
+ const TSchemaData& schemaData,
+ bool captureValues) = 0;
+ virtual TSharedRange<NTableClient::TVersionedRow> ReadVersionedRowset(
+ const TSchemaData& schemaData,
+ bool captureValues,
+ const TIdMapping* valueIdMapping = nullptr) = 0;
+
+ virtual TWireProtocolWriteCommand ReadWriteCommand(
+ const TSchemaData& schemaData,
+ bool captureValues,
+ bool versionedWriteIsUnversioned = false) = 0;
+
+ static TSchemaData GetSchemaData(
+ const NTableClient::TTableSchema& schema,
+ const NTableClient::TColumnFilter& filter);
+ static TSchemaData GetSchemaData(
+ const NTableClient::TTableSchema& schema);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Creates wire protocol reader.
+/*!
+ * If #rowBuffer is null, a default one is created.
+ */
+std::unique_ptr<IWireProtocolReader> CreateWireProtocolReader(
+ TSharedRef data,
+ TRowBufferPtr rowBuffer = TRowBufferPtr());
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IWireProtocolRowsetReader
+ : public NTableClient::ISchemafulUnversionedReader
+{ };
+
+DEFINE_REFCOUNTED_TYPE(IWireProtocolRowsetReader)
+
+IWireProtocolRowsetReaderPtr CreateWireProtocolRowsetReader(
+ const std::vector<TSharedRef>& compressedBlocks,
+ NCompression::ECodec codecId,
+ NTableClient::TTableSchemaPtr schema,
+ bool schemaful,
+ const NLogging::TLogger& logger);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IWireProtocolRowsetWriter
+ : public NTableClient::IUnversionedRowsetWriter
+{
+ virtual std::vector<TSharedRef> GetCompressedBlocks() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IWireProtocolRowsetWriter)
+
+IWireProtocolRowsetWriterPtr CreateWireProtocolRowsetWriter(
+ NCompression::ECodec codecId,
+ size_t desiredUncompressedBlockSize,
+ NTableClient::TTableSchemaPtr schema,
+ bool isSchemaful,
+ const NLogging::TLogger& logger);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
+
diff --git a/yt/yt/client/tablet_client/config.cpp b/yt/yt/client/tablet_client/config.cpp
new file mode 100644
index 0000000000..193a6f0417
--- /dev/null
+++ b/yt/yt/client/tablet_client/config.cpp
@@ -0,0 +1,122 @@
+#include "config.h"
+
+namespace NYT::NTabletClient {
+
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TTableMountCacheConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("reject_if_entry_is_requested_but_not_ready", &TThis::RejectIfEntryIsRequestedButNotReady)
+ .Default(false);
+}
+
+TTableMountCacheConfigPtr TTableMountCacheConfig::ApplyDynamic(
+ const TTableMountCacheDynamicConfigPtr& dynamicConfig) const
+{
+ auto mergedConfig = CloneYsonStruct(MakeStrong(this));
+ mergedConfig->ApplyDynamicInplace(dynamicConfig);
+ UpdateYsonStructField(mergedConfig->RejectIfEntryIsRequestedButNotReady, dynamicConfig->RejectIfEntryIsRequestedButNotReady);
+ mergedConfig->Postprocess();
+ return mergedConfig;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TTableMountCacheDynamicConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("reject_if_entry_is_requested_but_not_ready", &TThis::RejectIfEntryIsRequestedButNotReady)
+ .Optional();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TRemoteDynamicStoreReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("client_read_timeout", &TThis::ClientReadTimeout)
+ .Default(TDuration::Seconds(20));
+ registrar.Parameter("server_read_timeout", &TThis::ServerReadTimeout)
+ .Default(TDuration::Seconds(20));
+ registrar.Parameter("client_write_timeout", &TThis::ClientWriteTimeout)
+ .Default(TDuration::Seconds(20));
+ registrar.Parameter("server_write_timeout", &TThis::ServerWriteTimeout)
+ .Default(TDuration::Seconds(20));
+ registrar.Parameter("max_rows_per_server_read", &TThis::MaxRowsPerServerRead)
+ .GreaterThan(0)
+ .Default(1024);
+
+ registrar.Parameter("window_size", &TThis::WindowSize)
+ .Default(16_MB)
+ .GreaterThan(0);
+
+ registrar.Parameter("streaming_subrequest_failure_probability", &TThis::StreamingSubrequestFailureProbability)
+ .Default(0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TRetryingRemoteDynamicStoreReaderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("retry_count", &TThis::RetryCount)
+ .Default(10)
+ .GreaterThan(0);
+ registrar.Parameter("locate_request_backoff_time", &TThis::LocateRequestBackoffTime)
+ .Default(TDuration::Seconds(10));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TReplicatedTableOptions::Register(TRegistrar registrar)
+{
+ registrar.Parameter("enable_replicated_table_tracker", &TThis::EnableReplicatedTableTracker)
+ .Default(false);
+
+ registrar.Parameter("max_sync_replica_count", &TThis::MaxSyncReplicaCount)
+ .Alias("sync_replica_count")
+ .Optional();
+ registrar.Parameter("min_sync_replica_count", &TThis::MinSyncReplicaCount)
+ .Optional();
+
+ registrar.Parameter("sync_replica_lag_threshold", &TThis::SyncReplicaLagThreshold)
+ .Default(TDuration::Minutes(10));
+
+ registrar.Parameter("tablet_cell_bundle_name_ttl", &TThis::TabletCellBundleNameTtl)
+ .Default(TDuration::Seconds(300));
+ registrar.Parameter("tablet_cell_bundle_name_failure_interval", &TThis::RetryOnFailureInterval)
+ .Default(TDuration::Seconds(60));
+
+ registrar.Parameter("enable_preload_state_check", &TThis::EnablePreloadStateCheck)
+ .Default(false);
+ registrar.Parameter("incomplete_preload_grace_period", &TThis::IncompletePreloadGracePeriod)
+ .Default(TDuration::Minutes(5));
+
+ registrar.Parameter("preferred_sync_replica_clusters", &TThis::PreferredSyncReplicaClusters)
+ .Default(std::nullopt);
+
+ registrar.Postprocessor([] (TThis* config) {
+ if (config->MaxSyncReplicaCount && config->MinSyncReplicaCount && *config->MinSyncReplicaCount > *config->MaxSyncReplicaCount) {
+ THROW_ERROR_EXCEPTION("\"min_sync_replica_count\" must be less or equal to \"max_sync_replica_count\"");
+ }
+ });
+}
+
+std::tuple<int, int> TReplicatedTableOptions::GetEffectiveMinMaxReplicaCount(int replicaCount) const
+{
+ int maxSyncReplicas = 0;
+ int minSyncReplicas = 0;
+
+ if (!MaxSyncReplicaCount && !MinSyncReplicaCount) {
+ maxSyncReplicas = 1;
+ } else {
+ maxSyncReplicas = MaxSyncReplicaCount.value_or(replicaCount);
+ }
+
+ minSyncReplicas = MinSyncReplicaCount.value_or(maxSyncReplicas);
+
+ return std::make_tuple(minSyncReplicas, maxSyncReplicas);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/tablet_client/config.h b/yt/yt/client/tablet_client/config.h
new file mode 100644
index 0000000000..501a80dce6
--- /dev/null
+++ b/yt/yt/client/tablet_client/config.h
@@ -0,0 +1,118 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/cache_config.h>
+
+namespace NYT::NTabletClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableMountCacheConfig
+ : public TAsyncExpiringCacheConfig
+{
+public:
+ //! If entry is requested for the first time then allow only client who requested the entry to wait for it.
+ bool RejectIfEntryIsRequestedButNotReady;
+
+ TTableMountCacheConfigPtr ApplyDynamic(const TTableMountCacheDynamicConfigPtr& dynamicConfig) const;
+
+ REGISTER_YSON_STRUCT(TTableMountCacheConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableMountCacheConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableMountCacheDynamicConfig
+ : public TAsyncExpiringCacheDynamicConfig
+{
+public:
+ std::optional<bool> RejectIfEntryIsRequestedButNotReady;
+
+ REGISTER_YSON_STRUCT(TTableMountCacheDynamicConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableMountCacheDynamicConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoteDynamicStoreReaderConfig
+ : public virtual NYTree::TYsonStruct
+{
+public:
+ TDuration ClientReadTimeout;
+ TDuration ServerReadTimeout;
+ TDuration ClientWriteTimeout;
+ TDuration ServerWriteTimeout;
+ i64 MaxRowsPerServerRead;
+
+ ssize_t WindowSize;
+
+ // Testing option.
+ double StreamingSubrequestFailureProbability;
+
+ REGISTER_YSON_STRUCT(TRemoteDynamicStoreReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TRemoteDynamicStoreReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRetryingRemoteDynamicStoreReaderConfig
+ : public TRemoteDynamicStoreReaderConfig
+{
+public:
+ //! Maximum number of locate requests.
+ int RetryCount;
+
+ //! Time to wait between making another locate request.
+ TDuration LocateRequestBackoffTime;
+
+ REGISTER_YSON_STRUCT(TRetryingRemoteDynamicStoreReaderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TRetryingRemoteDynamicStoreReaderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReplicatedTableOptions
+ : public NYTree::TYsonStruct
+{
+public:
+ bool EnableReplicatedTableTracker;
+
+ std::optional<int> MaxSyncReplicaCount;
+ std::optional<int> MinSyncReplicaCount;
+
+ TDuration SyncReplicaLagThreshold;
+
+ // TODO(akozhikhov): We probably do not need these in this per-table config.
+ TDuration TabletCellBundleNameTtl;
+ TDuration RetryOnFailureInterval;
+
+ bool EnablePreloadStateCheck;
+ TDuration IncompletePreloadGracePeriod;
+
+ std::optional<std::vector<TString>> PreferredSyncReplicaClusters;
+
+ std::tuple<int, int> GetEffectiveMinMaxReplicaCount(int replicaCount) const;
+
+ REGISTER_YSON_STRUCT(TReplicatedTableOptions);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TReplicatedTableOptions)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/tablet_client/helpers.cpp b/yt/yt/client/tablet_client/helpers.cpp
new file mode 100644
index 0000000000..fcc29c1a5e
--- /dev/null
+++ b/yt/yt/client/tablet_client/helpers.cpp
@@ -0,0 +1,24 @@
+#include "public.h"
+
+namespace NYT::NTabletClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsStableReplicaMode(ETableReplicaMode mode)
+{
+ return
+ mode == ETableReplicaMode::Sync ||
+ mode == ETableReplicaMode::Async;
+}
+
+bool IsStableReplicaState(ETableReplicaState state)
+{
+ return
+ state == ETableReplicaState::Enabled ||
+ state == ETableReplicaState::Disabled;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
+
diff --git a/yt/yt/client/tablet_client/helpers.h b/yt/yt/client/tablet_client/helpers.h
new file mode 100644
index 0000000000..ef840061e1
--- /dev/null
+++ b/yt/yt/client/tablet_client/helpers.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "public.h"
+
+namespace NYT::NTabletClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsStableReplicaMode(ETableReplicaMode mode);
+bool IsStableReplicaState(ETableReplicaState state);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/tablet_client/public.cpp b/yt/yt/client/tablet_client/public.cpp
new file mode 100644
index 0000000000..b1bcfc8b8b
--- /dev/null
+++ b/yt/yt/client/tablet_client/public.cpp
@@ -0,0 +1,25 @@
+#include "public.h"
+
+namespace NYT::NTabletClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TTabletCellId NullTabletCellId;
+const TTabletId NullTabletId;
+const TStoreId NullStoreId;
+const TPartitionId NullPartitionId;
+const THunkStorageId NullHunkStorageId;
+
+const TString TReplicationLogTable::ChangeTypeColumnName("change_type");
+const TString TReplicationLogTable::KeyColumnNamePrefix("key:");
+const TString TReplicationLogTable::ValueColumnNamePrefix("value:");
+const TString TReplicationLogTable::FlagsColumnNamePrefix("flags:");
+
+const TString TUnversionedUpdateSchema::ChangeTypeColumnName("$change_type");
+const TString TUnversionedUpdateSchema::ValueColumnNamePrefix("$value:");
+const TString TUnversionedUpdateSchema::FlagsColumnNamePrefix("$flags:");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
+
diff --git a/yt/yt/client/tablet_client/public.h b/yt/yt/client/tablet_client/public.h
new file mode 100644
index 0000000000..782a8d19ec
--- /dev/null
+++ b/yt/yt/client/tablet_client/public.h
@@ -0,0 +1,224 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/client/hydra/public.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+namespace NYT::NTabletClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ETabletState,
+ // Individual states
+ ((Mounting) (0))
+ ((Mounted) (1))
+ ((Unmounting) (2))
+ ((Unmounted) (3))
+ ((Freezing) (4))
+ ((Frozen) (5))
+ ((Unfreezing) (6))
+ ((FrozenMounting) (7))
+
+ // Special states
+ ((None) (100))
+ ((Mixed) (101))
+ ((Transient) (102))
+);
+
+constexpr ETabletState MinValidTabletState = ETabletState::Mounting;
+constexpr ETabletState MaxValidTabletState = ETabletState::FrozenMounting;
+
+// Keep in sync with NRpcProxy::NProto::ETableReplicaMode.
+DEFINE_ENUM(ETableReplicaMode,
+ ((Sync) (0))
+ ((Async) (1))
+ ((AsyncToSync) (2))
+ ((SyncToAsync) (3))
+);
+
+DEFINE_ENUM(ETableReplicaContentType,
+ ((Data) (0))
+ ((Queue) (1))
+ ((External) (2))
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((TransactionLockConflict) (1700))
+ ((NoSuchTablet) (1701))
+ ((NoSuchCell) (1721))
+ ((TabletNotMounted) (1702))
+ ((AllWritesDisabled) (1703))
+ ((InvalidMountRevision) (1704))
+ ((TableReplicaAlreadyExists) (1705))
+ ((InvalidTabletState) (1706))
+ ((TableMountInfoNotReady) (1707))
+ ((TabletSnapshotExpired) (1708))
+ ((QueryInputRowCountLimitExceeded) (1709))
+ ((QueryOutputRowCountLimitExceeded) (1710))
+ ((QueryExpressionDepthLimitExceeded) (1711))
+ ((RowIsBlocked) (1712))
+ ((BlockedRowWaitTimeout) (1713))
+ ((NoSyncReplicas) (1714))
+ ((TableMustNotBeReplicated) (1715))
+ ((TableMustBeSorted) (1716))
+ ((TooManyRowsInTransaction) (1717))
+ ((UpstreamReplicaMismatch) (1718))
+ ((NoSuchDynamicStore) (1719))
+ ((BundleResourceLimitExceeded) (1720))
+ ((SyncReplicaIsNotKnown) (1722))
+ ((SyncReplicaIsNotInSyncMode) (1723))
+ ((SyncReplicaIsNotWritten) (1724))
+ ((RequestThrottled) (1725))
+ ((ColumnNotFound) (1726))
+ ((ReplicatorWriteBlockedByUser) (1727))
+ ((UserWriteBlockedByReplicator) (1728))
+ ((CannotCheckConflictsAgainstChunkStore) (1729))
+ ((InvalidBackupState) (1730))
+ ((WriteRetryIsImpossible) (1731))
+ ((SyncReplicaNotInSync) (1732))
+ ((BackupCheckpointRejected) (1733))
+ ((BackupInProgress) (1734))
+ ((ChunkIsNotPreloaded) (1735))
+ ((NoInSyncReplicas) (1736))
+ ((CellHasNoAssignedPeers) (1737))
+ ((TableSchemaIncompatible) (1738))
+ ((BundleIsBanned) (1739))
+);
+
+DEFINE_ENUM(EInMemoryMode,
+ ((None) (0))
+ ((Compressed) (1))
+ ((Uncompressed)(2))
+);
+
+DEFINE_ENUM(EOrderedTableBackupMode,
+ ((Exact) (0))
+ ((AtLeast) (1))
+ ((AtMost) (2))
+);
+
+using TTabletCellId = NHydra::TCellId;
+extern const TTabletCellId NullTabletCellId;
+
+using TTabletId = NObjectClient::TObjectId;
+extern const TTabletId NullTabletId;
+
+using TStoreId = NObjectClient::TObjectId;
+extern const TStoreId NullStoreId;
+
+using TPartitionId = NObjectClient::TObjectId;
+extern const TPartitionId NullPartitionId;
+
+using TTabletCellBundleId = NObjectClient::TObjectId;
+extern const TTabletCellBundleId NullTabletCellBundleId;
+
+using THunkStorageId = NObjectClient::TObjectId;
+extern const THunkStorageId NullHunkStorageId;
+
+constexpr int TypicalTableReplicaCount = 6;
+
+using TTableReplicaId = NObjectClient::TObjectId;
+using TTabletActionId = NObjectClient::TObjectId;
+using TDynamicStoreId = NObjectClient::TObjectId;
+using TTabletOwnerId = NObjectClient::TObjectId;
+using TAreaId = NObjectClient::TObjectId;
+
+DEFINE_BIT_ENUM(EReplicationLogDataFlags,
+ ((None) (0x0000))
+ ((Missing) (0x0001))
+ ((Aggregate) (0x0002))
+);
+
+struct TReplicationLogTable
+{
+ static const TString ChangeTypeColumnName;
+ static const TString KeyColumnNamePrefix;
+ static const TString ValueColumnNamePrefix;
+ static const TString FlagsColumnNamePrefix;
+};
+
+DEFINE_BIT_ENUM(EUnversionedUpdateDataFlags,
+ ((None) (0x0000))
+ ((Missing) (0x0001))
+ ((Aggregate) (0x0002))
+);
+
+constexpr EUnversionedUpdateDataFlags MinValidUnversionedUpdateDataFlags = EUnversionedUpdateDataFlags::None;
+constexpr EUnversionedUpdateDataFlags MaxValidUnversionedUpdateDataFlags =
+ EUnversionedUpdateDataFlags::Missing | EUnversionedUpdateDataFlags::Aggregate;
+
+struct TUnversionedUpdateSchema
+{
+ static const TString ChangeTypeColumnName;
+ static const TString ValueColumnNamePrefix;
+ static const TString FlagsColumnNamePrefix;
+};
+
+DEFINE_ENUM(ETabletCellHealth,
+ (Initializing)
+ (Good)
+ (Degraded)
+ (Failed)
+);
+
+DEFINE_ENUM(ETableReplicaState,
+ ((None) (0))
+ ((Disabling) (1))
+ ((Disabled) (2))
+ ((Enabling) (4))
+ ((Enabled) (3))
+);
+
+DEFINE_ENUM(ETableReplicaStatus,
+ ((Unknown) (0))
+
+ ((SyncInSync) (1))
+ ((SyncCatchingUp) (2))
+ ((SyncNotWritable) (3))
+
+ ((AsyncInSync) (4))
+ ((AsyncCatchingUp) (5))
+ ((AsyncNotWritable) (6))
+);
+
+DEFINE_ENUM(ETabletActionKind,
+ ((Move) (0))
+ ((Reshard) (1))
+);
+
+DEFINE_ENUM(ETabletActionState,
+ ((Preparing) (0))
+ ((Freezing) (1))
+ ((Frozen) (2))
+ ((Unmounting) (3))
+ ((Unmounted) (4))
+ ((Orphaned) (10))
+ ((Mounting) (5))
+ ((Mounted) (6))
+ ((Completed) (7))
+ ((Failing) (8))
+ ((Failed) (9))
+);
+
+DEFINE_ENUM(ETabletServiceFeatures,
+ ((WriteGenerations) (0))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TTableMountCacheConfig)
+DECLARE_REFCOUNTED_CLASS(TTableMountCacheDynamicConfig)
+DECLARE_REFCOUNTED_CLASS(TRemoteDynamicStoreReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TRetryingRemoteDynamicStoreReaderConfig)
+DECLARE_REFCOUNTED_CLASS(TReplicatedTableOptions)
+
+DECLARE_REFCOUNTED_STRUCT(TTableMountInfo)
+DECLARE_REFCOUNTED_STRUCT(TTabletInfo)
+DECLARE_REFCOUNTED_STRUCT(TTableReplicaInfo)
+DECLARE_REFCOUNTED_STRUCT(ITableMountCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/tablet_client/table_mount_cache.cpp b/yt/yt/client/tablet_client/table_mount_cache.cpp
new file mode 100644
index 0000000000..d7958f1ea3
--- /dev/null
+++ b/yt/yt/client/tablet_client/table_mount_cache.cpp
@@ -0,0 +1,182 @@
+#include "table_mount_cache.h"
+
+#include <yt/yt/client/object_client/helpers.h>
+
+#include <yt/yt/client/table_client/key_bound.h>
+
+namespace NYT::NTabletClient {
+
+using namespace NTableClient;
+using namespace NObjectClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TKeyBound TTabletInfo::GetLowerKeyBound() const
+{
+ return TKeyBound::FromRow() >= PivotKey;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TTableMountInfo::IsSorted() const
+{
+ return Schemas[ETableSchemaKind::Primary]->IsSorted();
+}
+
+bool TTableMountInfo::IsOrdered() const
+{
+ return !IsSorted();
+}
+
+bool TTableMountInfo::IsReplicated() const
+{
+ return TypeFromId(TableId) == EObjectType::ReplicatedTable;
+}
+
+bool TTableMountInfo::IsChaosReplicated() const
+{
+ return TypeFromId(TableId) == EObjectType::ChaosReplicatedTable;
+}
+
+bool TTableMountInfo::IsReplicationLog() const
+{
+ return TypeFromId(TableId) == EObjectType::ReplicationLogTable;
+}
+
+bool TTableMountInfo::IsHunkStorage() const
+{
+ return TypeFromId(TableId) == EObjectType::HunkStorage;
+}
+
+bool TTableMountInfo::IsPhysicallyLog() const
+{
+ return IsReplicated() || IsReplicationLog();
+}
+
+bool TTableMountInfo::IsChaosReplica() const
+{
+ return TypeFromId(UpstreamReplicaId) == EObjectType::ChaosTableReplica;
+}
+
+TTabletInfoPtr TTableMountInfo::GetTabletByIndexOrThrow(int tabletIndex) const
+{
+ if (tabletIndex < 0 || tabletIndex >= std::ssize(Tablets)) {
+ THROW_ERROR_EXCEPTION("Invalid tablet index for table %v: expected in range [0,%v], got %v",
+ Path,
+ Tablets.size() - 1,
+ tabletIndex);
+ }
+ return Tablets[tabletIndex];
+}
+
+int TTableMountInfo::GetTabletIndexForKey(TUnversionedValueRange key) const
+{
+ ValidateDynamic();
+ auto it = std::upper_bound(
+ Tablets.begin(),
+ Tablets.end(),
+ key,
+ [&] (TUnversionedValueRange key, const TTabletInfoPtr& rhs) {
+ return CompareValueRanges(key, rhs->PivotKey.Elements()) < 0;
+ });
+ YT_VERIFY(it != Tablets.begin());
+ return std::distance(Tablets.begin(), it - 1);
+}
+
+int TTableMountInfo::GetTabletIndexForKey(TUnversionedRow key) const
+{
+ return GetTabletIndexForKey(key.Elements());
+}
+
+TTabletInfoPtr TTableMountInfo::GetTabletForKey(TUnversionedValueRange key) const
+{
+ auto index = GetTabletIndexForKey(key);
+ return Tablets[index];
+}
+
+TTabletInfoPtr TTableMountInfo::GetTabletForRow(TUnversionedRow row) const
+{
+ int keyColumnCount = Schemas[ETableSchemaKind::Primary]->GetKeyColumnCount();
+ YT_VERIFY(static_cast<int>(row.GetCount()) >= keyColumnCount);
+ return GetTabletForKey(row.FirstNElements(keyColumnCount));
+}
+
+TTabletInfoPtr TTableMountInfo::GetTabletForRow(TVersionedRow row) const
+{
+ int keyColumnCount = Schemas[ETableSchemaKind::Primary]->GetKeyColumnCount();
+ YT_VERIFY(row.GetKeyCount() == keyColumnCount);
+ return GetTabletForKey(row.Keys());
+}
+
+int TTableMountInfo::GetRandomMountedTabletIndex() const
+{
+ ValidateTabletOwner();
+
+ if (MountedTablets.empty()) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::TabletNotMounted,
+ "Table %v has no mounted tablets",
+ Path);
+ }
+
+ return RandomNumber(MountedTablets.size());
+}
+
+TTabletInfoPtr TTableMountInfo::GetRandomMountedTablet() const
+{
+ return MountedTablets[GetRandomMountedTabletIndex()];
+}
+
+void TTableMountInfo::ValidateTabletOwner() const
+{
+ if (!Dynamic && !IsHunkStorage()) {
+ THROW_ERROR_EXCEPTION("Table %v is neither dynamic nor a hunk storage", Path);
+ }
+}
+
+void TTableMountInfo::ValidateDynamic() const
+{
+ if (!Dynamic) {
+ THROW_ERROR_EXCEPTION("Table %v is not dynamic", Path);
+ }
+}
+
+void TTableMountInfo::ValidateSorted() const
+{
+ if (!IsSorted()) {
+ THROW_ERROR_EXCEPTION("Table %v is not sorted", Path);
+ }
+}
+
+void TTableMountInfo::ValidateOrdered() const
+{
+ if (!IsOrdered()) {
+ THROW_ERROR_EXCEPTION("Table %v is not ordered", Path);
+ }
+}
+
+void TTableMountInfo::ValidateNotPhysicallyLog() const
+{
+ if (IsPhysicallyLog()) {
+ THROW_ERROR_EXCEPTION("Table %v physically contains replication log", Path);
+ }
+}
+
+void TTableMountInfo::ValidateReplicated() const
+{
+ if (!IsReplicated()) {
+ THROW_ERROR_EXCEPTION("Table %v is not replicated", Path);
+ }
+}
+
+void TTableMountInfo::ValidateReplicationLog() const
+{
+ if (!IsReplicationLog()) {
+ THROW_ERROR_EXCEPTION("Table %v is not replication log", Path);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
+
diff --git a/yt/yt/client/tablet_client/table_mount_cache.h b/yt/yt/client/tablet_client/table_mount_cache.h
new file mode 100644
index 0000000000..a83c13002d
--- /dev/null
+++ b/yt/yt/client/tablet_client/table_mount_cache.h
@@ -0,0 +1,176 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/client/hive/public.h>
+
+#include <yt/yt/client/node_tracker_client/node_directory.h>
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+#include <yt/yt/client/table_client/public.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+
+#include <yt/yt/client/chaos_client/replication_card.h>
+
+#include <yt/yt/client/ypath/public.h>
+
+#include <yt/yt/core/actions/future.h>
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+#include <util/datetime/base.h>
+
+namespace NYT::NTabletClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTabletInfo
+ : public TRefCounted
+{
+ TTabletId TabletId;
+ NHydra::TRevision MountRevision = NHydra::NullRevision;
+ ETabletState State;
+ EInMemoryMode InMemoryMode;
+ NTableClient::TLegacyOwningKey PivotKey;
+ TTabletCellId CellId;
+ NObjectClient::TObjectId TableId;
+ TInstant UpdateTime;
+ std::vector<TWeakPtr<TTableMountInfo>> Owners;
+
+ NTableClient::TKeyBound GetLowerKeyBound() const;
+};
+
+DEFINE_REFCOUNTED_TYPE(TTabletInfo)
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTableReplicaInfo
+ : public TRefCounted
+{
+ TTableReplicaId ReplicaId;
+ TString ClusterName;
+ NYPath::TYPath ReplicaPath;
+ ETableReplicaMode Mode;
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableReplicaInfo)
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Describes the primary and the auxiliary schemas derived from the table schema.
+//! Cf. TTableSchema::ToXXX methods.
+DEFINE_ENUM(ETableSchemaKind,
+ // Schema assigned to Cypress node, as is.
+ (Primary)
+ // Schema used for inserting rows.
+ (Write)
+ // Schema used for querying rows.
+ (Query)
+ // Schema used for deleting rows.
+ (Delete)
+ // Schema used for writing versioned rows (during replication).
+ (VersionedWrite)
+ // Schema used for looking up rows.
+ (Lookup)
+ // For sorted schemas, coincides with primary.
+ // For ordered, contains an additional tablet index columns.
+ (PrimaryWithTabletIndex)
+ // Schema used for replication log rows.
+ (ReplicationLog)
+);
+
+struct TTableMountInfo
+ : public TRefCounted
+{
+ NYPath::TYPath Path;
+ NObjectClient::TObjectId TableId;
+ TEnumIndexedVector<ETableSchemaKind, NTableClient::TTableSchemaPtr> Schemas;
+
+ // PhysicalPath points to a physical object, if current object is linked to some other object, then this field will point to the source.
+ // When this field is not supported on the server-side, this path will be equal to object path.
+ NYPath::TYPath PhysicalPath;
+
+ NObjectClient::TObjectId HunkStorageId;
+
+ bool Dynamic;
+ TTableReplicaId UpstreamReplicaId;
+ bool NeedKeyEvaluation;
+
+ NChaosClient::TReplicationCardId ReplicationCardId;
+
+ std::vector<TTabletInfoPtr> Tablets;
+ std::vector<TTabletInfoPtr> MountedTablets;
+
+ std::vector<TTableReplicaInfoPtr> Replicas;
+
+ //! For sorted tables, these are -infinity and +infinity.
+ //! For ordered tablets, these are |[0]| and |[tablet_count]| resp.
+ NTableClient::TLegacyOwningKey LowerCapBound;
+ NTableClient::TLegacyOwningKey UpperCapBound;
+
+ // Master reply revision for master service cache invalidation.
+ NHydra::TRevision PrimaryRevision;
+ NHydra::TRevision SecondaryRevision;
+
+ bool EnableDetailedProfiling = false;
+
+ bool IsSorted() const;
+ bool IsOrdered() const;
+ bool IsReplicated() const;
+ bool IsChaosReplicated() const;
+ bool IsReplicationLog() const;
+ bool IsPhysicallyLog() const;
+ bool IsChaosReplica() const;
+ bool IsHunkStorage() const;
+
+ TTabletInfoPtr GetTabletByIndexOrThrow(int tabletIndex) const;
+ int GetTabletIndexForKey(NTableClient::TUnversionedValueRange key) const;
+ int GetTabletIndexForKey(NTableClient::TLegacyKey key) const;
+ TTabletInfoPtr GetTabletForKey(NTableClient::TUnversionedValueRange key) const;
+ TTabletInfoPtr GetTabletForRow(NTableClient::TUnversionedRow row) const;
+ TTabletInfoPtr GetTabletForRow(NTableClient::TVersionedRow row) const;
+ int GetRandomMountedTabletIndex() const;
+ TTabletInfoPtr GetRandomMountedTablet() const;
+
+ void ValidateTabletOwner() const;
+ void ValidateDynamic() const;
+ void ValidateSorted() const;
+ void ValidateOrdered() const;
+ void ValidateNotPhysicallyLog() const;
+ void ValidateReplicated() const;
+ void ValidateReplicationLog() const;
+};
+
+DEFINE_REFCOUNTED_TYPE(TTableMountInfo)
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ITableMountCache
+ : public virtual TRefCounted
+{
+ virtual TFuture<TTableMountInfoPtr> GetTableInfo(const NYPath::TYPath& path) = 0;
+ virtual TTabletInfoPtr FindTabletInfo(TTabletId tabletId) = 0;
+ virtual void InvalidateTablet(TTabletInfoPtr tabletInfo) = 0;
+
+ //! If #error is unretryable, returns null.
+ //! Otherwise invalidates cached tablet info (if it can be inferred from #error)
+ //! and returns actual retryable error code as first element and
+ //! tablet info as second element.
+ virtual std::pair<std::optional<TErrorCode>, TTabletInfoPtr> InvalidateOnError(
+ const TError& error,
+ bool forceRetry) = 0;
+
+ virtual void Clear() = 0;
+
+ virtual void Reconfigure(TTableMountCacheConfigPtr config) = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ITableMountCache)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/tablet_client/table_mount_cache_detail.cpp b/yt/yt/client/tablet_client/table_mount_cache_detail.cpp
new file mode 100644
index 0000000000..88b09bd406
--- /dev/null
+++ b/yt/yt/client/tablet_client/table_mount_cache_detail.cpp
@@ -0,0 +1,266 @@
+#include "table_mount_cache_detail.h"
+
+#include "config.h"
+
+#include <yt/yt/core/yson/string.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <yt/yt/core/profiling/timing.h>
+
+#include <library/cpp/yt/misc/hash.h>
+
+namespace NYT::NTabletClient {
+
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr auto TabletCacheSweepPeriod = TDuration::Seconds(60);
+
+///////////////////////////////////////////////////////////////////////////////
+
+TTabletInfoCache::TTabletInfoCache(NLogging::TLogger logger)
+ : Logger(std::move(logger))
+{ }
+
+TTabletInfoPtr TTabletInfoCache::Find(TTabletId tabletId)
+{
+ SweepExpiredEntries();
+
+ auto guard = ReaderGuard(MapLock_);
+ ProcessNextGCQueueEntry();
+ auto it = Map_.find(tabletId);
+ return it != Map_.end() ? it->second.Lock() : nullptr;
+}
+
+TTabletInfoPtr TTabletInfoCache::Insert(const TTabletInfoPtr& tabletInfo)
+{
+ SweepExpiredEntries();
+
+ auto guard = WriterGuard(MapLock_);
+ ProcessNextGCQueueEntry();
+ typename decltype(Map_)::insert_ctx context;
+ auto it = Map_.find(tabletInfo->TabletId, context);
+ if (it != Map_.end()) {
+ if (auto existingTabletInfo = it->second.Lock()) {
+ if (tabletInfo->MountRevision < existingTabletInfo->MountRevision) {
+ THROW_ERROR_EXCEPTION(
+ EErrorCode::InvalidMountRevision,
+ "Tablet mount revision %x is outdated",
+ tabletInfo->MountRevision)
+ << TErrorAttribute("tablet_id", tabletInfo->TabletId);
+ }
+
+ for (const auto& owner : existingTabletInfo->Owners) {
+ if (!owner.IsExpired()) {
+ tabletInfo->Owners.push_back(owner);
+ }
+ }
+ }
+ it->second = MakeWeak(tabletInfo);
+ } else {
+ Map_.emplace_direct(context, tabletInfo->TabletId, tabletInfo);
+ guard.Release();
+
+ auto gcGuard = Guard(GCLock_);
+ GCQueue_.push(tabletInfo->TabletId);
+ }
+
+ return tabletInfo;
+}
+
+void TTabletInfoCache::Clear()
+{
+ decltype(Map_) other;
+ {
+ auto guard = WriterGuard(MapLock_);
+ other = std::move(Map_);
+ }
+}
+
+void TTabletInfoCache::SweepExpiredEntries()
+{
+ auto now = NProfiling::GetCpuInstant();
+ auto deadline = ExpiredEntriesSweepDeadline_.load(std::memory_order::relaxed);
+ if (now < deadline) {
+ return;
+ }
+
+ if (!ExpiredEntriesSweepDeadline_.compare_exchange_strong(deadline, now + NProfiling::DurationToCpuDuration(TabletCacheSweepPeriod))) {
+ return;
+ }
+
+ decltype(ExpiredTabletIds_) expiredTabletIds;
+ {
+ auto gcGuard = Guard(GCLock_);
+ expiredTabletIds = std::move(ExpiredTabletIds_);
+ }
+
+ if (!expiredTabletIds.empty()) {
+ YT_LOG_DEBUG("Start sweeping expired tablet info (ExpiredTabletCount: %v)",
+ expiredTabletIds.size());
+
+ for (auto id : expiredTabletIds) {
+ auto guard = WriterGuard(MapLock_);
+ if (auto it = Map_.find(id); it) {
+ if (it->second.IsExpired()) {
+ Map_.erase(it);
+ continue;
+ }
+
+ guard.Release();
+
+ auto gcGuard = Guard(GCLock_);
+ GCQueue_.push(id);
+ }
+ }
+
+ YT_LOG_DEBUG("Finish sweeping expired tablet info");
+ }
+}
+
+void TTabletInfoCache::ProcessNextGCQueueEntry()
+{
+ VERIFY_SPINLOCK_AFFINITY(MapLock_);
+ auto gcGuard = Guard(GCLock_);
+ if (!GCQueue_.empty()) {
+ const auto& id = GCQueue_.front();
+ if (auto it = Map_.find(id); it) {
+ if (it->second.IsExpired()) {
+ ExpiredTabletIds_.push_back(id);
+ } else {
+ GCQueue_.push(id);
+ }
+ }
+ GCQueue_.pop();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTableMountCacheBase::TTableMountCacheBase(
+ TTableMountCacheConfigPtr config,
+ NLogging::TLogger logger,
+ NProfiling::TProfiler profiler)
+ : TAsyncExpiringCache(
+ config,
+ logger.WithTag("Cache: TableMount"),
+ profiler)
+ , Logger(std::move(logger))
+ , TabletInfoCache_(Logger)
+ , Config_(std::move(config))
+{ }
+
+TFuture<TTableMountInfoPtr> TTableMountCacheBase::GetTableInfo(const NYPath::TYPath& path)
+{
+ auto [future, requestInitialized] = TAsyncExpiringCache::GetExtended(path);
+
+ bool shouldThrow = false;
+ if (!requestInitialized && !future.IsSet()) {
+ auto guard = ReaderGuard(SpinLock_);
+ shouldThrow = Config_->RejectIfEntryIsRequestedButNotReady;
+ }
+ if (shouldThrow) {
+ // COMPAT(babenko): replace with TransientFailure error code.
+ THROW_ERROR_EXCEPTION(NRpc::EErrorCode::Unavailable,
+ "Mount info is unavailable, please try again")
+ << TError(NTabletClient::EErrorCode::TableMountInfoNotReady,
+ "Table mount info is not ready, but has already been requested")
+ << TErrorAttribute("path", path);
+ }
+
+ return future;
+}
+
+TTabletInfoPtr TTableMountCacheBase::FindTabletInfo(TTabletId tabletId)
+{
+ return TabletInfoCache_.Find(tabletId);
+}
+
+void TTableMountCacheBase::InvalidateTablet(TTabletInfoPtr tabletInfo)
+{
+ for (const auto& weakOwner : tabletInfo->Owners) {
+ if (auto owner = weakOwner.Lock()) {
+ InvalidateTable(owner);
+ }
+ }
+}
+
+std::pair<std::optional<TErrorCode>, TTabletInfoPtr> TTableMountCacheBase::InvalidateOnError(
+ const TError& error,
+ bool forceRetry)
+{
+ static const std::vector<TErrorCode> retriableCodes = {
+ NTabletClient::EErrorCode::NoSuchTablet,
+ NTabletClient::EErrorCode::TabletNotMounted,
+ NTabletClient::EErrorCode::InvalidMountRevision,
+ NYTree::EErrorCode::ResolveError
+ };
+
+ if (!error.IsOK()) {
+ for (auto code : retriableCodes) {
+ if (auto retriableError = error.FindMatching(code)) {
+ auto tabletId = retriableError->Attributes().Find<TTabletId>("tablet_id");
+ if (!tabletId) {
+ continue;
+ }
+
+ auto isTabletUnmounted = retriableError->Attributes().Get<bool>("is_tablet_unmounted", false);
+ auto tabletInfo = FindTabletInfo(*tabletId);
+ if (tabletInfo) {
+ YT_LOG_DEBUG(error,
+ "Invalidating tablet in table mount cache "
+ "(TabletId: %v, CellId: %v, MountRevision: %x, IsTabletUnmounted: %v, Owners: %v)",
+ tabletInfo->TabletId,
+ tabletInfo->CellId,
+ tabletInfo->MountRevision,
+ isTabletUnmounted,
+ MakeFormattableView(tabletInfo->Owners, [] (auto* builder, const auto& weakOwner) {
+ if (auto owner = weakOwner.Lock()) {
+ FormatValue(builder, owner->Path, TStringBuf());
+ } else {
+ builder->AppendString(TStringBuf("<expired>"));
+ }
+ }));
+
+ InvalidateTablet(tabletInfo);
+ }
+
+ std::optional<TErrorCode> retryableErrorCode = code;
+ if (code == NTabletClient::EErrorCode::TabletNotMounted &&
+ isTabletUnmounted &&
+ !forceRetry)
+ {
+ retryableErrorCode = std::nullopt;
+ }
+
+ return std::make_pair(retryableErrorCode, tabletInfo);
+ }
+ }
+ }
+
+ return std::make_pair(std::nullopt, nullptr);
+}
+
+void TTableMountCacheBase::Clear()
+{
+ TAsyncExpiringCache::Clear();
+ TabletInfoCache_.Clear();
+ YT_LOG_DEBUG("Table mount info cache cleared");
+}
+
+void TTableMountCacheBase::Reconfigure(TTableMountCacheConfigPtr config)
+{
+ TAsyncExpiringCache::Reconfigure(config);
+ {
+ auto guard = WriterGuard(SpinLock_);
+ Config_ = config;
+ }
+ YT_LOG_DEBUG("Table mount info cache reconfigured (NewConfig: %v)",
+ NYson::ConvertToYsonString(config, NYson::EYsonFormat::Text).AsStringBuf());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/tablet_client/table_mount_cache_detail.h b/yt/yt/client/tablet_client/table_mount_cache_detail.h
new file mode 100644
index 0000000000..94a2026759
--- /dev/null
+++ b/yt/yt/client/tablet_client/table_mount_cache_detail.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include "table_mount_cache.h"
+
+#include <yt/yt/core/misc/async_expiring_cache.h>
+
+#include <yt/yt/core/logging/log.h>
+
+#include <yt/yt/core/profiling/public.h>
+
+#include <library/cpp/yt/threading/rw_spin_lock.h>
+#include <library/cpp/yt/threading/spin_lock.h>
+
+namespace NYT::NTabletClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTabletInfoCache
+{
+public:
+ explicit TTabletInfoCache(NLogging::TLogger logger);
+
+ TTabletInfoPtr Find(TTabletId tabletId);
+ TTabletInfoPtr Insert(const TTabletInfoPtr& tabletInfo);
+ void Clear();
+
+private:
+ const NLogging::TLogger Logger;
+
+ std::atomic<NProfiling::TCpuInstant> ExpiredEntriesSweepDeadline_ = 0;
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TReaderWriterSpinLock, MapLock_);
+ THashMap<TTabletId, TWeakPtr<TTabletInfo>> Map_;
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, GCLock_);
+ std::queue<TTabletId> GCQueue_;
+ std::vector<TTabletId> ExpiredTabletIds_;
+
+ void SweepExpiredEntries();
+ void ProcessNextGCQueueEntry();
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class TTableMountCacheBase
+ : public ITableMountCache
+ , public TAsyncExpiringCache<NYPath::TYPath, TTableMountInfoPtr>
+{
+public:
+ TTableMountCacheBase(
+ TTableMountCacheConfigPtr config,
+ NLogging::TLogger logger,
+ NProfiling::TProfiler profiler = {});
+
+ TFuture<TTableMountInfoPtr> GetTableInfo(const NYPath::TYPath& path) override;
+ TTabletInfoPtr FindTabletInfo(TTabletId tabletId) override;
+ void InvalidateTablet(TTabletInfoPtr tabletInfo) override;
+ std::pair<std::optional<TErrorCode>, TTabletInfoPtr> InvalidateOnError(
+ const TError& error,
+ bool forceRetry) override;
+
+ void Clear() override;
+
+ void Reconfigure(TTableMountCacheConfigPtr config) override;
+
+protected:
+ const NLogging::TLogger Logger;
+
+ TTabletInfoCache TabletInfoCache_;
+
+ virtual void InvalidateTable(const TTableMountInfoPtr& tableInfo) = 0;
+
+private:
+ YT_DECLARE_SPIN_LOCK(NThreading::TReaderWriterSpinLock, SpinLock_);
+ TTableMountCacheConfigPtr Config_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTabletClient
diff --git a/yt/yt/client/transaction_client/batching_timestamp_provider.cpp b/yt/yt/client/transaction_client/batching_timestamp_provider.cpp
new file mode 100644
index 0000000000..e6a5e299ab
--- /dev/null
+++ b/yt/yt/client/transaction_client/batching_timestamp_provider.cpp
@@ -0,0 +1,170 @@
+#include "batching_timestamp_provider.h"
+#include "timestamp_provider.h"
+#include "private.h"
+
+#include <yt/yt/core/concurrency/periodic_executor.h>
+#include <yt/yt/core/concurrency/thread_affinity.h>
+
+#include <yt/yt/library/tracing/batch_trace.h>
+
+namespace NYT::NTransactionClient {
+
+using namespace NConcurrency;
+using namespace NTracing;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBatchingTimestampProvider
+ : public ITimestampProvider
+{
+public:
+ TBatchingTimestampProvider(
+ ITimestampProviderPtr underlying,
+ TDuration batchPeriod)
+ : Underlying_(std::move(underlying))
+ , BatchPeriod_(batchPeriod)
+ { }
+
+ TFuture<TTimestamp> GenerateTimestamps(int count) override
+ {
+ VERIFY_THREAD_AFFINITY_ANY();
+
+ TFuture<TTimestamp> result;
+
+ {
+ auto guard = Guard(SpinLock_);
+ PendingRequests_.emplace_back();
+ PendingRequests_.back().Count = count;
+ PendingRequests_.back().Promise = NewPromise<TTimestamp>();
+ result = PendingRequests_.back().Promise.ToFuture().ToUncancelable();
+
+ BatchTrace_.Join();
+
+ TNullTraceContextGuard nullTraceContextGuard;
+ MaybeScheduleSendGenerateRequest(guard);
+ }
+
+ return result;
+ }
+
+ TTimestamp GetLatestTimestamp() override
+ {
+ VERIFY_THREAD_AFFINITY_ANY();
+
+ return Underlying_->GetLatestTimestamp();
+ }
+
+ private:
+ const ITimestampProviderPtr Underlying_;
+ const TDuration BatchPeriod_;
+
+ struct TRequest
+ {
+ int Count;
+ TPromise<TTimestamp> Promise;
+ };
+
+ YT_DECLARE_SPIN_LOCK(NThreading::TSpinLock, SpinLock_);
+ bool GenerateInProgress_ = false;
+ bool FlushScheduled_ = false;
+ std::vector<TRequest> PendingRequests_;
+ TBatchTrace BatchTrace_;
+
+ TInstant LastRequestTime_;
+
+ void MaybeScheduleSendGenerateRequest(TGuard<NThreading::TSpinLock>& guard)
+ {
+ VERIFY_SPINLOCK_AFFINITY(SpinLock_);
+
+ if (PendingRequests_.empty() || GenerateInProgress_) {
+ return;
+ }
+
+ auto now = GetInstant();
+
+ if (LastRequestTime_ + BatchPeriod_ < now) {
+ SendGenerateRequest(guard);
+ } else if (!FlushScheduled_) {
+ FlushScheduled_ = true;
+ TDelayedExecutor::Submit(
+ BIND([this, this_ = MakeStrong(this)] {
+ auto guard = Guard(SpinLock_);
+ FlushScheduled_ = false;
+ if (GenerateInProgress_) {
+ return;
+ }
+ SendGenerateRequest(guard);
+ }),
+ BatchPeriod_ - (now - LastRequestTime_));
+ }
+ }
+
+ void SendGenerateRequest(TGuard<NThreading::TSpinLock>& guard)
+ {
+ VERIFY_SPINLOCK_AFFINITY(SpinLock_);
+
+ YT_VERIFY(!GenerateInProgress_);
+ GenerateInProgress_ = true;
+ LastRequestTime_ = GetInstant();
+
+ std::vector<TRequest> requests;
+ requests.swap(PendingRequests_);
+
+ auto [traceContext, sampled] = BatchTrace_.StartSpan("BatchingTimestampProvider:SendGenerateRequest");
+
+ guard.Release();
+
+ TTraceContextGuard traceContextGuard(traceContext);
+
+ int count = 0;
+ for (const auto& request : requests) {
+ count += request.Count;
+ }
+
+ Underlying_->GenerateTimestamps(count).Subscribe(BIND(
+ &TBatchingTimestampProvider::OnGenerateResponse,
+ MakeStrong(this),
+ Passed(std::move(requests))));
+ }
+
+ void OnGenerateResponse(
+ std::vector<TRequest> requests,
+ const TErrorOr<TTimestamp>& firstTimestampOrError)
+ {
+ VERIFY_THREAD_AFFINITY_ANY();
+
+ {
+ auto guard = Guard(SpinLock_);
+
+ YT_VERIFY(GenerateInProgress_);
+ GenerateInProgress_ = false;
+
+ MaybeScheduleSendGenerateRequest(guard);
+ }
+
+ if (firstTimestampOrError.IsOK()) {
+ auto timestamp = firstTimestampOrError.Value();
+ for (const auto& request : requests) {
+ request.Promise.Set(timestamp);
+ timestamp += request.Count;
+ }
+ } else {
+ for (const auto& request : requests) {
+ request.Promise.Set(TError(firstTimestampOrError));
+ }
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ITimestampProviderPtr CreateBatchingTimestampProvider(
+ ITimestampProviderPtr underlying,
+ TDuration batchPeriod)
+{
+ return New<TBatchingTimestampProvider>(std::move(underlying), batchPeriod);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/batching_timestamp_provider.h b/yt/yt/client/transaction_client/batching_timestamp_provider.h
new file mode 100644
index 0000000000..c8aeead636
--- /dev/null
+++ b/yt/yt/client/transaction_client/batching_timestamp_provider.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "public.h"
+
+#include <util/datetime/base.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+ITimestampProviderPtr CreateBatchingTimestampProvider(
+ ITimestampProviderPtr underlying,
+ TDuration batchPeriod);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/config.cpp b/yt/yt/client/transaction_client/config.cpp
new file mode 100644
index 0000000000..3264e5dcd1
--- /dev/null
+++ b/yt/yt/client/transaction_client/config.cpp
@@ -0,0 +1,34 @@
+#include "config.h"
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TRemoteTimestampProviderConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("rpc_timeout", &TThis::RpcTimeout)
+ .Default(TDuration::Seconds(3));
+ registrar.Parameter("latest_timestamp_update_period", &TThis::LatestTimestampUpdatePeriod)
+ // COMPAT(babenko)
+ .Alias("update_period")
+ .Default(TDuration::MilliSeconds(500));
+
+ registrar.Parameter("batch_period", &TThis::BatchPeriod)
+ .Default(TDuration::MilliSeconds(10));
+
+ registrar.Parameter("enable_timestamp_provider_discovery", &TThis::EnableTimestampProviderDiscovery)
+ .Default(false);
+ registrar.Parameter("timestamp_provider_discovery_period", &TThis::TimestampProviderDiscoveryPeriod)
+ .Default(TDuration::Minutes(1));
+ registrar.Parameter("timestamp_provider_discovery_period_splay", &TThis::TimestampProviderDiscoveryPeriodSplay)
+ .Default(TDuration::Seconds(10));
+
+ registrar.Preprocessor([] (TThis* config) {
+ config->RetryAttempts = 100;
+ config->RetryTimeout = TDuration::Minutes(3);
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/config.h b/yt/yt/client/transaction_client/config.h
new file mode 100644
index 0000000000..c4114b72c5
--- /dev/null
+++ b/yt/yt/client/transaction_client/config.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/rpc/config.h>
+
+#include <yt/yt/core/ytree/yson_serializable.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoteTimestampProviderConfig
+ : public NRpc::TBalancingChannelConfig
+ , public NRpc::TRetryingChannelConfig
+{
+public:
+ //! Timeout for RPC requests to timestamp provider.
+ TDuration RpcTimeout;
+
+ //! Interval between consecutive updates of latest timestamp.
+ TDuration LatestTimestampUpdatePeriod;
+
+ //! All generation requests coming within this period are batched
+ //! together.
+ TDuration BatchPeriod;
+
+ bool EnableTimestampProviderDiscovery;
+ TDuration TimestampProviderDiscoveryPeriod;
+ TDuration TimestampProviderDiscoveryPeriodSplay;
+
+ REGISTER_YSON_STRUCT(TRemoteTimestampProviderConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TRemoteTimestampProviderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/helpers.cpp b/yt/yt/client/transaction_client/helpers.cpp
new file mode 100644
index 0000000000..0350650991
--- /dev/null
+++ b/yt/yt/client/transaction_client/helpers.cpp
@@ -0,0 +1,101 @@
+#include "helpers.h"
+
+#include <yt/yt/client/object_client/helpers.h>
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/guid.h>
+
+namespace NYT::NTransactionClient {
+
+using namespace NObjectClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool IsMasterTransactionId(TTransactionId id)
+{
+ auto type = TypeFromId(id);
+ // NB: Externalized transactions are for internal use only.
+ return type == NObjectClient::EObjectType::Transaction ||
+ type == NObjectClient::EObjectType::NestedTransaction ||
+ type == EObjectType::UploadTransaction ||
+ type == EObjectType::UploadNestedTransaction;
+}
+
+void ValidateTabletTransactionId(TTransactionId id)
+{
+ auto type = TypeFromId(id);
+ if (type != EObjectType::Transaction &&
+ type != EObjectType::AtomicTabletTransaction &&
+ type != EObjectType::NonAtomicTabletTransaction)
+ {
+ THROW_ERROR_EXCEPTION("%v is not a valid tablet transaction id",
+ id);
+ }
+}
+
+void ValidateMasterTransactionId(TTransactionId id)
+{
+ if (!IsMasterTransactionId(id)) {
+ THROW_ERROR_EXCEPTION("%v is not a valid master transaction id",
+ id);
+ }
+}
+
+std::pair<TInstant, TInstant> TimestampToInstant(TTimestamp timestamp)
+{
+ auto instant = TInstant::Seconds(UnixTimeFromTimestamp(timestamp));
+ return {
+ instant,
+ instant + TDuration::Seconds(1)
+ };
+}
+
+std::pair<TTimestamp, TTimestamp> InstantToTimestamp(TInstant instant)
+{
+ auto time = instant.Seconds();
+ return {
+ TimestampFromUnixTime(time),
+ TimestampFromUnixTime(time + 1)
+ };
+}
+
+std::pair<TDuration, TDuration> TimestampDiffToDuration(TTimestamp loTimestamp, TTimestamp hiTimestamp)
+{
+ YT_ASSERT(loTimestamp <= hiTimestamp);
+ auto loInstant = TimestampToInstant(loTimestamp);
+ auto hiInstant = TimestampToInstant(hiTimestamp);
+ return std::make_pair(
+ hiInstant.first >= loInstant.second ? hiInstant.first - loInstant.second : TDuration::Zero(),
+ hiInstant.second - loInstant.first);
+}
+
+ui64 UnixTimeFromTimestamp(TTimestamp timestamp)
+{
+ return timestamp >> TimestampCounterWidth;
+}
+
+TTimestamp TimestampFromUnixTime(ui64 time)
+{
+ return time << TimestampCounterWidth;
+}
+
+TTimestamp EmbedCellTagIntoTimestamp(TTimestamp timestamp, NObjectClient::TCellTag cellTag)
+{
+ static_assert(sizeof(TCellTag) == 2, "Invalid TCellTag size");
+
+ YT_VERIFY((timestamp & ((1ull << TimestampCounterWidth) - 1)) == 0);
+
+ return timestamp ^ (static_cast<ui32>(cellTag.Underlying()) << (TimestampCounterWidth - 16));
+}
+
+bool CanAdvanceTimestampWithEmbeddedCellTag(TTimestamp timestamp, int delta)
+{
+ static_assert(sizeof(TCellTag) == 2, "Invalid TCellTag size");
+
+ return (timestamp >> (TimestampCounterWidth - 16)) ==
+ ((timestamp + delta) >> (TimestampCounterWidth - 16));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/helpers.h b/yt/yt/client/transaction_client/helpers.h
new file mode 100644
index 0000000000..f2ccbee427
--- /dev/null
+++ b/yt/yt/client/transaction_client/helpers.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "public.h"
+
+#include <util/datetime/base.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Represents that only values with timestamp in range [|RetentionTimestamp|, |Timestamp|]
+//! should be read.
+//! Note that both endpoints are inclusive.
+struct TReadTimestampRange
+{
+ NTransactionClient::TTimestamp Timestamp = NTransactionClient::NullTimestamp;
+ NTransactionClient::TTimestamp RetentionTimestamp = NTransactionClient::NullTimestamp;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Checks if #id represents a valid transaction accepted by masters:
+//! the type of #id must be either
+//! #EObjectType::Transaction or #EObjectType::NestedTransaction.
+bool IsMasterTransactionId(TTransactionId id);
+
+//! Checks if #id represents a valid transaction accepted by tablets:
+//! the type of #id must be either
+//! #EObjectType::Transaction, #EObjectType::AtomicTabletTransaction,
+//! or #EObjectType::NonAtomicTabletTransaction.
+void ValidateTabletTransactionId(TTransactionId id);
+
+//! Checks if #id represents a valid transaction accepted by masters:
+//! the type of #id must be one of
+//! #EObjectType::Transaction, #EObjectType::NestedTransaction,
+//! #EObjectType::UploadTransaction, or #EObjectType::UploadNestedTransaction.
+void ValidateMasterTransactionId(TTransactionId id);
+
+//! Returns a range of instants containing a given timestamp.
+std::pair<TInstant, TInstant> TimestampToInstant(TTimestamp timestamp);
+
+//! Returns a range of timestamps containing a given timestamp.
+std::pair<TTimestamp, TTimestamp> InstantToTimestamp(TInstant instant);
+
+//! Returns a range of durations between given timestamps.
+std::pair<TDuration, TDuration> TimestampDiffToDuration(TTimestamp loTimestamp, TTimestamp hiTimestamp);
+
+//! Extracts the "unix time" part of the timestamp.
+ui64 UnixTimeFromTimestamp(TTimestamp timestamp);
+
+//! Constructs the timestamp from a given unix time (assuming zero counter part).
+TTimestamp TimestampFromUnixTime(ui64 time);
+
+//! Embeds the cell tag into the higher counter bits of the timestamp (assuming zero rest counter part).
+TTimestamp EmbedCellTagIntoTimestamp(TTimestamp timestamp, NObjectClient::TCellTag cellTag);
+
+//! Checks if the timestamp with embedded cell tag can be advanced by |delta|
+//! without overflowing counter part.
+bool CanAdvanceTimestampWithEmbeddedCellTag(TTimestamp timestamp, int delta);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/noop_timestamp_provider.cpp b/yt/yt/client/transaction_client/noop_timestamp_provider.cpp
new file mode 100644
index 0000000000..52aa1a955c
--- /dev/null
+++ b/yt/yt/client/transaction_client/noop_timestamp_provider.cpp
@@ -0,0 +1,34 @@
+#include "noop_timestamp_provider.h"
+
+#include "private.h"
+#include "timestamp_provider.h"
+
+namespace NYT::NTransactionClient {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class TNoopTimestampProvider
+ : public ITimestampProvider
+{
+public:
+ TFuture<TTimestamp> GenerateTimestamps(int /*count*/) override
+ {
+ return MakeFuture(NullTimestamp);
+ }
+
+ TTimestamp GetLatestTimestamp() override
+ {
+ return NullTimestamp;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ITimestampProviderPtr CreateNoopTimestampProvider()
+{
+ return New<TNoopTimestampProvider>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNTransactionClient
diff --git a/yt/yt/client/transaction_client/noop_timestamp_provider.h b/yt/yt/client/transaction_client/noop_timestamp_provider.h
new file mode 100644
index 0000000000..e4cbc7fbd1
--- /dev/null
+++ b/yt/yt/client/transaction_client/noop_timestamp_provider.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "public.h"
+
+namespace NYT::NTransactionClient {
+
+///////////////////////////////////////////////////////////////////////////////
+
+ITimestampProviderPtr CreateNoopTimestampProvider();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/private.h b/yt/yt/client/transaction_client/private.h
new file mode 100644
index 0000000000..24c8359ee4
--- /dev/null
+++ b/yt/yt/client/transaction_client/private.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <yt/yt/core/logging/log.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline const NLogging::TLogger TransactionClientLogger("TransactionClient");
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/public.h b/yt/yt/client/transaction_client/public.h
new file mode 100644
index 0000000000..ac38af4dd6
--- /dev/null
+++ b/yt/yt/client/transaction_client/public.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include <yt/yt/client/object_client/public.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+#include <library/cpp/yt/memory/ref_counted.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ETransactionType,
+ ((Master) (0)) // Accepted by both masters and tablets
+ ((Tablet) (1)) // Accepted by tablets only
+);
+
+DEFINE_ENUM(EAtomicity,
+ ((Full) (0)) // 2PC enabled, Percolator mode :)
+ ((None) (1)) // 2PC disabled, HBase mode
+);
+
+DEFINE_ENUM(EDurability,
+ ((Sync) (0)) // Wait for Hydra commit result
+ ((Async) (1)) // Reply as soon as the request is enqueued to Hydra
+);
+
+//! Only applies to ordered tables.
+DEFINE_ENUM(ECommitOrdering,
+ ((Weak) (0)) // Rows are appended to tablet in order of participant commits
+ ((Strong) (1)) // Rows are appended to tablet in order of timestamps
+);
+
+YT_DEFINE_ERROR_ENUM(
+ ((NoSuchTransaction) (11000))
+ ((NestedExternalTransactionExists) (11001))
+ ((TransactionDepthLimitReached) (11002))
+ ((InvalidTransactionState) (11003))
+ ((ParticipantFailedToPrepare) (11004))
+ ((SomeParticipantsAreDown) (11005))
+ ((AlienTransactionsForbidden) (11006))
+ ((MalformedAlienTransaction) (11007))
+ ((InvalidTransactionAtomicity) (11008))
+ ((UploadTransactionCannotHaveNested)(11009))
+ ((ForeignParentTransaction) (11010))
+ ((ForeignPrerequisiteTransaction) (11011))
+ ((IncompletePrepareSignature) (11012))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+using NObjectClient::TTransactionId;
+using NObjectClient::NullTransactionId;
+
+//! Timestamp is a cluster-wide unique monotonically increasing number
+//! used to implement the MVCC paradigm.
+/*!
+ * Timestamp is a 64-bit unsigned integer of the following structure:
+ * bits 0-29: auto-incrementing counter (allowing up to ~10^9 timestamps per second)
+ * bits 30-61: Unix time in seconds (from 1 Jan 1970)
+ * bits 62-63: reserved
+ */
+using TTimestamp = ui64;
+
+//! Number of bits in the counter part.
+constexpr int TimestampCounterWidth = 30;
+
+// Timestamp values range:
+//! Minimum valid (non-sentinel) timestamp.
+constexpr TTimestamp MinTimestamp = 0x0000000000000001ULL;
+//! Maximum valid (non-sentinel) timestamp.
+constexpr TTimestamp MaxTimestamp = 0x3fffffffffffff00ULL;
+
+// User sentinels:
+//! Uninitialized/invalid timestamp.
+constexpr TTimestamp NullTimestamp = 0x0000000000000000ULL;
+//! Truly (serializable) latest committed version.
+//! May cause row blocking if concurrent writes are in progress.
+constexpr TTimestamp SyncLastCommittedTimestamp = 0x3fffffffffffff01ULL;
+//! Relaxed (non-serializable) latest committed version.
+//! Never leads to row blocking but may miss some concurrent writes.
+constexpr TTimestamp AsyncLastCommittedTimestamp = 0x3fffffffffffff04ULL;
+//! Used to fetch all committed values during e.g. flushes or compactions.
+//! Returns all versions that were committed at the moment the reader was created.
+//! Never leads to row blocking but may miss some concurrent writes.
+constexpr TTimestamp AllCommittedTimestamp = 0x3fffffffffffff03ULL;
+
+// System sentinels:
+//! Used by TSortedDynamicStore to mark values being written by transactions.
+constexpr TTimestamp UncommittedTimestamp = 0x3fffffffffffff02ULL;
+//! Used by TSortedDynamicStore in TLockDescriptor::PrepareTimestamp.
+//! Must be larger than SyncLastCommittedTimestamp.
+constexpr TTimestamp NotPreparedTimestamp = 0x3fffffffffffffffULL;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(ITimestampProvider)
+DECLARE_REFCOUNTED_CLASS(TRemoteTimestampProviderConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/remote_timestamp_provider.cpp b/yt/yt/client/transaction_client/remote_timestamp_provider.cpp
new file mode 100644
index 0000000000..045620a75e
--- /dev/null
+++ b/yt/yt/client/transaction_client/remote_timestamp_provider.cpp
@@ -0,0 +1,111 @@
+#include "remote_timestamp_provider.h"
+#include "batching_timestamp_provider.h"
+#include "timestamp_provider_base.h"
+#include "private.h"
+#include "config.h"
+#include "timestamp_service_proxy.h"
+
+#include <yt/yt/core/rpc/balancing_channel.h>
+#include <yt/yt/core/rpc/retrying_channel.h>
+
+#include <yt/yt/core/concurrency/periodic_executor.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NTransactionClient {
+
+using namespace NRpc;
+using namespace NYTree;
+using namespace NObjectClient;
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+IChannelPtr CreateTimestampProviderChannel(
+ TRemoteTimestampProviderConfigPtr config,
+ IChannelFactoryPtr channelFactory)
+{
+ auto endpointDescription = TString("TimestampProvider");
+ auto endpointAttributes = ConvertToAttributes(BuildYsonStringFluently()
+ .BeginMap()
+ .Item("timestamp_provider").Value(true)
+ .EndMap());
+ auto channel = CreateBalancingChannel(
+ config,
+ std::move(channelFactory),
+ std::move(endpointDescription),
+ std::move(endpointAttributes));
+ channel = CreateRetryingChannel(
+ config,
+ std::move(channel));
+ return channel;
+}
+
+IChannelPtr CreateTimestampProviderChannelFromAddresses(
+ TRemoteTimestampProviderConfigPtr config,
+ IChannelFactoryPtr channelFactory,
+ const std::vector<TString>& discoveredAddresses)
+{
+ auto channelConfig = CloneYsonStruct(config);
+ if (!discoveredAddresses.empty()) {
+ channelConfig->Addresses = discoveredAddresses;
+ }
+ return CreateTimestampProviderChannel(channelConfig, channelFactory);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRemoteTimestampProvider
+ : public TTimestampProviderBase
+{
+public:
+ TRemoteTimestampProvider(
+ IChannelPtr channel,
+ TRemoteTimestampProviderConfigPtr config)
+ : TTimestampProviderBase(config->LatestTimestampUpdatePeriod)
+ , Config_(std::move(config))
+ , Proxy_(std::move(channel))
+ {
+ Proxy_.SetDefaultTimeout(Config_->RpcTimeout);
+ }
+
+private:
+ const TRemoteTimestampProviderConfigPtr Config_;
+
+ TTimestampServiceProxy Proxy_;
+
+ TFuture<TTimestamp> DoGenerateTimestamps(int count) override
+ {
+ auto req = Proxy_.GenerateTimestamps();
+ req->SetResponseHeavy(true);
+ req->set_count(count);
+ return req->Invoke().Apply(BIND([] (const TTimestampServiceProxy::TRspGenerateTimestampsPtr& rsp) {
+ return static_cast<TTimestamp>(rsp->timestamp());
+ }));
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+ITimestampProviderPtr CreateRemoteTimestampProvider(
+ TRemoteTimestampProviderConfigPtr config,
+ IChannelPtr channel)
+{
+ return New<TRemoteTimestampProvider>(std::move(channel), std::move(config));
+}
+
+ITimestampProviderPtr CreateBatchingRemoteTimestampProvider(
+ TRemoteTimestampProviderConfigPtr config,
+ IChannelPtr channel)
+{
+ auto underlying = CreateRemoteTimestampProvider(config, std::move(channel));
+ return CreateBatchingTimestampProvider(
+ std::move(underlying),
+ config->BatchPeriod);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
+
diff --git a/yt/yt/client/transaction_client/remote_timestamp_provider.h b/yt/yt/client/transaction_client/remote_timestamp_provider.h
new file mode 100644
index 0000000000..91a7179e92
--- /dev/null
+++ b/yt/yt/client/transaction_client/remote_timestamp_provider.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NRpc::IChannelPtr CreateTimestampProviderChannel(
+ TRemoteTimestampProviderConfigPtr config,
+ NRpc::IChannelFactoryPtr channelFactory);
+
+NRpc::IChannelPtr CreateTimestampProviderChannelFromAddresses(
+ TRemoteTimestampProviderConfigPtr config,
+ NRpc::IChannelFactoryPtr channelFactory,
+ const std::vector<TString>& addresses);
+
+ITimestampProviderPtr CreateBatchingTimestampProvider(
+ ITimestampProviderPtr underlying,
+ TDuration batchPeriod);
+
+ITimestampProviderPtr CreateRemoteTimestampProvider(
+ TRemoteTimestampProviderConfigPtr config,
+ NRpc::IChannelPtr channel);
+
+ITimestampProviderPtr CreateBatchingRemoteTimestampProvider(
+ TRemoteTimestampProviderConfigPtr config,
+ NRpc::IChannelPtr channel);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/timestamp_provider.h b/yt/yt/client/transaction_client/timestamp_provider.h
new file mode 100644
index 0000000000..5bcefafc9b
--- /dev/null
+++ b/yt/yt/client/transaction_client/timestamp_provider.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Manages cluster-wide unique timestamps.
+/*!
+ * Thread affinity: any
+ */
+struct ITimestampProvider
+ : public virtual TRefCounted
+{
+ //! Generates a contiguous range of timestamps (of size #count)
+ //! that are guaranteed to be larger than all timestamps previously obtained via this instance.
+ //! Returns the first timestamp of that range.
+ virtual TFuture<TTimestamp> GenerateTimestamps(int count = 1) = 0;
+
+ //! Returns the latest timestamp returned from #GenerateTimestamps.
+ virtual TTimestamp GetLatestTimestamp() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(ITimestampProvider)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
+
diff --git a/yt/yt/client/transaction_client/timestamp_provider_base.cpp b/yt/yt/client/transaction_client/timestamp_provider_base.cpp
new file mode 100644
index 0000000000..88db51c486
--- /dev/null
+++ b/yt/yt/client/transaction_client/timestamp_provider_base.cpp
@@ -0,0 +1,101 @@
+#include "timestamp_provider_base.h"
+#include "private.h"
+
+#include <yt/yt/core/actions/invoker_util.h>
+
+#include <yt/yt/core/concurrency/thread_affinity.h>
+#include <yt/yt/core/concurrency/periodic_executor.h>
+
+namespace NYT::NTransactionClient {
+
+using namespace NConcurrency;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto& Logger = TransactionClientLogger;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTimestampProviderBase::TTimestampProviderBase(std::optional<TDuration> latestTimestampUpdatePeriod)
+ : LatestTimestampUpdatePeriod_(latestTimestampUpdatePeriod)
+{ }
+
+TFuture<TTimestamp> TTimestampProviderBase::GenerateTimestamps(int count)
+{
+ VERIFY_THREAD_AFFINITY_ANY();
+
+ YT_LOG_DEBUG("Generating fresh timestamps (Count: %v)", count);
+
+ return DoGenerateTimestamps(count).Apply(BIND(
+ &TTimestampProviderBase::OnGenerateTimestamps,
+ MakeStrong(this),
+ count));
+}
+
+TTimestamp TTimestampProviderBase::GetLatestTimestamp()
+{
+ VERIFY_THREAD_AFFINITY_ANY();
+
+ auto result = LatestTimestamp_.load(std::memory_order::relaxed);
+
+ if (LatestTimestampUpdatePeriod_ && ++GetLatestTimestampCallCounter_ == 1) {
+ LatestTimestampExecutor_ = New<TPeriodicExecutor>(
+ GetSyncInvoker(),
+ BIND(&TTimestampProviderBase::UpdateLatestTimestamp, MakeWeak(this)),
+ *LatestTimestampUpdatePeriod_);
+ LatestTimestampExecutor_->Start();
+ }
+
+ return result;
+
+}
+
+TFuture<TTimestamp> TTimestampProviderBase::OnGenerateTimestamps(
+ int count,
+ const TErrorOr<TTimestamp>& timestampOrError)
+{
+ if (!timestampOrError.IsOK()) {
+ auto error = TError("Error generating fresh timestamps") << timestampOrError;
+ YT_LOG_ERROR(error);
+ return MakeFuture<TTimestamp>(error);
+ }
+
+ auto firstTimestamp = timestampOrError.Value();
+ auto lastTimestamp = firstTimestamp + count - 1;
+
+ YT_LOG_DEBUG("Fresh timestamps generated (Timestamps: %v-%v)",
+ firstTimestamp,
+ lastTimestamp);
+
+ auto latestTimestamp = LatestTimestamp_.load(std::memory_order::relaxed);
+ while (true) {
+ if (latestTimestamp >= lastTimestamp) {
+ break;
+ }
+ if (LatestTimestamp_.compare_exchange_weak(latestTimestamp, lastTimestamp, std::memory_order::relaxed)) {
+ break;
+ }
+ }
+
+ return MakeFuture<TTimestamp>(firstTimestamp);
+}
+
+void TTimestampProviderBase::UpdateLatestTimestamp()
+{
+ VERIFY_THREAD_AFFINITY_ANY();
+
+ YT_LOG_DEBUG("Updating latest timestamp");
+ GenerateTimestamps(1).Subscribe(
+ BIND([] (const TErrorOr<TTimestamp>& timestampOrError) {
+ if (timestampOrError.IsOK()) {
+ YT_LOG_DEBUG("Latest timestamp updated (Timestamp: %v)",
+ timestampOrError.Value());
+ } else {
+ YT_LOG_WARNING(timestampOrError, "Error updating latest timestamp");
+ }
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
diff --git a/yt/yt/client/transaction_client/timestamp_provider_base.h b/yt/yt/client/transaction_client/timestamp_provider_base.h
new file mode 100644
index 0000000000..cc3168819e
--- /dev/null
+++ b/yt/yt/client/transaction_client/timestamp_provider_base.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "timestamp_provider.h"
+
+#include <yt/yt/core/concurrency/public.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Mostly implements the tracking of the latest timestamp, thus factoring out
+//! common code between the actual implementations.
+class TTimestampProviderBase
+ : public ITimestampProvider
+{
+protected:
+ explicit TTimestampProviderBase(std::optional<TDuration> latestTimestampUpdatePeriod);
+
+ virtual TFuture<TTimestamp> DoGenerateTimestamps(int count) = 0;
+
+public:
+ TFuture<TTimestamp> GenerateTimestamps(int count) override;
+ TTimestamp GetLatestTimestamp() override;
+
+private:
+ const std::optional<TDuration> LatestTimestampUpdatePeriod_;
+
+ std::atomic<i64> GetLatestTimestampCallCounter_ = 0;
+ NConcurrency::TPeriodicExecutorPtr LatestTimestampExecutor_;
+ std::atomic<TTimestamp> LatestTimestamp_ = MinTimestamp;
+
+ TFuture<TTimestamp> OnGenerateTimestamps(
+ int count,
+ const TErrorOr<TTimestamp>& timestampOrError);
+ void UpdateLatestTimestamp();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
+
diff --git a/yt/yt/client/transaction_client/timestamp_service_proxy.h b/yt/yt/client/transaction_client/timestamp_service_proxy.h
new file mode 100644
index 0000000000..070505d2dd
--- /dev/null
+++ b/yt/yt/client/transaction_client/timestamp_service_proxy.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt_proto/yt/client/transaction_client/proto/timestamp_service.pb.h>
+
+#include <yt/yt/core/rpc/client.h>
+
+namespace NYT::NTransactionClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTimestampServiceProxy
+ : public NRpc::TProxyBase
+{
+public:
+ DEFINE_RPC_PROXY(TTimestampServiceProxy, TimestampService);
+
+ DEFINE_RPC_PROXY_METHOD(NProto, GenerateTimestamps,
+ .SetMultiplexingBand(NRpc::EMultiplexingBand::Control));
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTransactionClient
+
diff --git a/yt/yt/client/unittests/check_schema_compatibility_ut.cpp b/yt/yt/client/unittests/check_schema_compatibility_ut.cpp
new file mode 100644
index 0000000000..c1bb4440f5
--- /dev/null
+++ b/yt/yt/client/unittests/check_schema_compatibility_ut.cpp
@@ -0,0 +1,349 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/comparator.h>
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/check_schema_compatibility.h>
+
+namespace NYT::NTableClient {
+
+static void PrintTo(ESchemaCompatibility typeCompatibility, std::ostream* stream)
+{
+ (*stream) << "ESchemaCompatibility::" << ToString(typeCompatibility).c_str();
+}
+
+namespace {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTableSchemaCompatibilityTest
+ : public ::testing::Test
+{ };
+
+TEST_F(TTableSchemaCompatibilityTest, CheckTableSchemaCompatibilityTest)
+{
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Int64),
+ }, /*strict*/ false),
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Int64),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Int64),
+ }),
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Int64),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Int64),
+ TColumnSchema("bar", EValueType::Int64),
+ }),
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Int64),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Int64),
+ }),
+ TTableSchema({
+ TColumnSchema("foo", EValueType::Uint64),
+ }),
+ /*ignoreSortOrder*/ true ).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", ESimpleLogicalValueType::Int32),
+ }),
+ TTableSchema({
+ TColumnSchema("foo", ESimpleLogicalValueType::Int64),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::RequireValidation,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", ESimpleLogicalValueType::Int32),
+ }),
+ TTableSchema({
+ TColumnSchema("foo", ESimpleLogicalValueType::Int8),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ }),
+ TTableSchema({
+ TColumnSchema("foo", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::RequireValidation,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("foo", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+ }),
+ TTableSchema({
+ TColumnSchema("foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ // Missing "foo" values are filled with nulls that's OK.
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({}, /*strict*/ true),
+ TTableSchema({
+ TColumnSchema("foo", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ // First table might have column foo with value of any type
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({}, /*strict*/ false),
+ TTableSchema({
+ TColumnSchema("foo", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ // Missing "foo" values are filled with nulls that's OK.
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({}, /*strict*/ true),
+ TTableSchema({
+ TColumnSchema("foo", ESimpleLogicalValueType::Int64),
+ }),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("a", ListLogicalType(SimpleLogicalType((ESimpleLogicalValueType::Int64)))),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64),
+ }, /*strict*/ true),
+ TTableSchema({
+ TColumnSchema("b", ESimpleLogicalValueType::Int64),
+ }, /*strict*/ false),
+ /*ignoreSortOrder*/ true).first
+ );
+
+ //
+ // ignoreSortOrder = false
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }),
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }),
+ /*ignoreSortOrder*/ false).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64),
+ }),
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }),
+ /*ignoreSortOrder*/ false).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }),
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64),
+ }),
+ /*ignoreSortOrder*/ false).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }, /*strict*/ true, /*uniqueKeys*/ true),
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64),
+ }, /*strict*/ true, /*uniqueKeys*/ true),
+ /*ignoreSortOrder*/ false).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }, /*strict*/ true, /*uniqueKeys*/ true),
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }, /*strict*/ true, /*uniqueKeys*/ true),
+ /*ignoreSortOrder*/ false).first
+ );
+
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }),
+ TTableSchema({
+ TColumnSchema("b", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ TColumnSchema("a", ESimpleLogicalValueType::Int64, ESortOrder::Ascending),
+ }),
+ /*ignoreSortOrder*/ false).first
+ );
+}
+
+TEST_F(TTableSchemaCompatibilityTest, TestCheckTableSchemaCompatibility)
+{
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Ascending),
+ TColumnSchema("key2", ESimpleLogicalValueType::String, ESortOrder::Ascending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }),
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Ascending),
+ TColumnSchema("key2", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }),
+ false).first);
+
+ EXPECT_EQ(
+ ESchemaCompatibility::FullyCompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("key2", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }),
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("key2", ESimpleLogicalValueType::String),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }),
+ false).first);
+
+ EXPECT_EQ(
+ ESchemaCompatibility::Incompatible,
+ CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("key2", ESimpleLogicalValueType::String),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }),
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("key2", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }),
+ false).first);
+}
+
+TEST_F(TTableSchemaCompatibilityTest, DeletedColumns)
+{
+ auto comp = CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }, true, true, ETableSchemaModification::None,
+ {
+ TDeletedColumn(TStableName("value1")),
+ }),
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ TColumnSchema("value1", ESimpleLogicalValueType::String),
+ }, true, true, {}),
+ false);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, comp.first);
+ EXPECT_EQ("Column \"value1\" in output schema was deleted in the input schema",
+ comp.second.InnerErrors()[0].GetMessage());
+
+
+ comp = CheckTableSchemaCompatibility(
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }, true, true, ETableSchemaModification::None, {}),
+ TTableSchema({
+ TColumnSchema("key1", ESimpleLogicalValueType::String, ESortOrder::Descending),
+ TColumnSchema("value", ESimpleLogicalValueType::String),
+ }, true, true, ETableSchemaModification::None,
+ {
+ TDeletedColumn(TStableName("value1")),
+ }),
+ false);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, comp.first);
+ EXPECT_EQ("Deleted column \"value1\" is missing in the input schema",
+ comp.second.InnerErrors()[0].GetMessage());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/check_type_compatibility_ut.cpp b/yt/yt/client/unittests/check_type_compatibility_ut.cpp
new file mode 100644
index 0000000000..488ee42800
--- /dev/null
+++ b/yt/yt/client/unittests/check_type_compatibility_ut.cpp
@@ -0,0 +1,477 @@
+#include "logical_type_shortcuts.h"
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/complex_types/check_type_compatibility.h>
+
+using namespace NYT::NComplexTypes;
+using namespace NYT::NTableClient::NLogicalTypeShortcuts;
+
+using NYT::NTableClient::ESchemaCompatibility;
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void PrintTo(ESchemaCompatibility typeCompatibility, std::ostream* stream)
+{
+ (*stream) << "ESchemaCompatibility::" << ToString(typeCompatibility).c_str();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
+
+TEST(TCheckTypeCompatibilityTest, SimpleTypes)
+{
+ // Int
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Int8(), Int16()).first);
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Int8(), Int64()).first);
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Int16(), Int64()).first);
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(Int16(), Int8()).first);
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(Int64(), Int8()).first);
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(Int64(), Int32()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Int8(), Uint64()).first);
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Int8(), Double()).first);
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Int8(), String()).first);
+
+ // Uint
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Uint8(), Uint16()).first);
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Uint8(), Uint64()).first);
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Uint16(), Uint64()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(Uint16(), Uint8()).first);
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(Uint64(), Uint8()).first);
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(Uint64(), Uint32()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Uint8(), Int64()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Uint8(), Double()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Uint8(), String()).first);
+
+ // Float
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Float(), Double()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(Double(), Float()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Float(), String()).first);
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(Double(), Int64()).first);
+
+ // String
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible, CheckTypeCompatibility(Utf8(), String()).first);
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation, CheckTypeCompatibility(String(), Utf8()).first);
+ EXPECT_EQ(ESchemaCompatibility::Incompatible, CheckTypeCompatibility(String(), Json()).first);
+}
+
+TEST(TCheckTypeCompatibilityTest, Optional)
+{
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ Optional(Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Optional(Int8()),
+ Int8()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ Optional(Optional(Int8()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Optional(Optional(Int8())),
+ Int8()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Optional(Int8()),
+ Optional(Optional(Int8()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Optional(Optional(Int8())),
+ Optional(Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Optional(Optional(Int8())),
+ Optional(Optional(Int8()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Optional(Int8()),
+ Optional(Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ Optional(Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Int32(),
+ Optional(Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Optional(List(Int64())),
+ List(Int64())).first);
+}
+
+TEST(TCheckTypeCompatibilityTest, List)
+{
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ List(Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ List(Int8()),
+ Int8()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ List(List(Int8())),
+ List(List(Int8()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ List(Int8()),
+ List(Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ List(Int8()),
+ List(Optional(Int16()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ List(Int32()),
+ List(Optional(Int16()))).first);
+}
+
+TEST(TCheckTypeCompatibilityTest, Tuple)
+{
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ Tuple(Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8()),
+ Int8()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8()),
+ Tuple(Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8()),
+ Tuple(Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8(), Int8()),
+ Tuple(Int16(), Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8(), Int8()),
+ Tuple(Int16(), Int32())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8()),
+ Tuple(Optional(Int16()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Tuple(Int32()),
+ Tuple(Optional(Int16()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8(), Int8()),
+ Tuple(Int8(), Int8(), Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8(), Int8()),
+ Tuple(Int8(), Int8(), Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8(), Int8(), Int8()),
+ Tuple(Int8(), Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8(), Int8()),
+ Tuple(Int8(), Int8(), Optional(Int8()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Tuple(Int8(), Int8(), Optional(Int8())),
+ Tuple(Int8(), Int8())).first);
+}
+
+TEST(TCheckTypeCompatibilityTest, Struct)
+{
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ Struct("a", Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Int8()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Struct("a", Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Struct("b", Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Struct("a", Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Struct("a", Int16()),
+ Struct("a", Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int16()),
+ Struct("a", Int8(), "b", Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Struct("a", Int8(), "b", Optional(Int8()))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Struct("a", Int8(), "b", Optional(Optional(Int8())))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Struct("a", Int8(), "b", Null())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8()),
+ Struct("a", Int8(), "b", Optional(List(String())))).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8(), "b", Int8()),
+ Struct("a", Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Struct("a", Int8(), "b", Optional(Int8())),
+ Struct("a", Int8())).first);
+}
+
+TEST(TCheckTypeCompatibilityTest, VariantTuple)
+{
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ VariantTuple(Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int8()),
+ Int8()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int8()),
+ VariantTuple(Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int8(), Uint8()),
+ VariantTuple(Int16(), Uint8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int8(), Uint8()),
+ VariantTuple(Int8(), Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int8(), Uint8()),
+ VariantTuple(Int32(), Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantTuple(Int16(), Uint8()),
+ VariantTuple(Int8(), Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantTuple(Int16(), Uint16()),
+ VariantTuple(Int8(), Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantTuple(Int16(), Uint16()),
+ VariantTuple(Int16(), Uint8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantTuple(Int16(), Uint16()),
+ VariantTuple(Int16(), Uint8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int16(), Uint16()),
+ VariantTuple(Int16(), Uint16(), String())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int16(), Uint16(), String()),
+ VariantTuple(Int16(), Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ VariantTuple(Int16(), Uint16(), String()),
+ VariantTuple(Int16(), Uint16())).first);
+}
+
+TEST(TCheckTypeCompatibilityTest, VariantStruct)
+{
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Int8(),
+ VariantStruct("a", Int8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int8()),
+ Int8()).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int8()),
+ VariantStruct("a", Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int8(), "b", Uint8()),
+ VariantStruct("a", Int16(), "b", Uint8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int8(), "b", Uint8()),
+ VariantStruct("a", Int8(), "b", Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int8(), "b", Uint8()),
+ VariantStruct("a", Int32(), "b", Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint8()),
+ VariantStruct("a", Int8(), "b", Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint16()),
+ VariantStruct("a", Int8(), "b", Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint16()),
+ VariantStruct("a", Int16(), "b", Uint8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint16()),
+ VariantStruct("a", Int16(), "b", Uint8())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint16()),
+ VariantStruct("a", Int16(), "b", Uint16(), "c", String())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint16(), "c", String()),
+ VariantStruct("a", Int16(), "b", Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint16(), "c", String()),
+ VariantStruct("a", Int16(), "b", Uint16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ VariantStruct("a", Int16(), "b", Uint16()),
+ VariantStruct("b", Uint16(), "a", Int16())).first);
+}
+
+TEST(TCheckTypeCompatibilityTest, Dict)
+{
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Dict(Utf8(), Int16()),
+ Dict(Utf8(), Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Dict(Utf8(), Int16()),
+ Dict(Utf8(), Int32())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Dict(Utf8(), Int16()),
+ Dict(String(), Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::FullyCompatible,
+ CheckTypeCompatibility(
+ Dict(Utf8(), Int16()),
+ Dict(String(), Int32())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Dict(String(), Int32()),
+ Dict(Utf8(), Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Dict(String(), Int32()),
+ Dict(Utf8(), Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::RequireValidation,
+ CheckTypeCompatibility(
+ Dict(Utf8(), Int32()),
+ Dict(String(), Int16())).first);
+
+ EXPECT_EQ(ESchemaCompatibility::Incompatible,
+ CheckTypeCompatibility(
+ Dict(Utf8(), Int32()),
+ Dict(String(), Uint16())).first);
+}
diff --git a/yt/yt/client/unittests/chunk_replica_ut.cpp b/yt/yt/client/unittests/chunk_replica_ut.cpp
new file mode 100644
index 0000000000..75dfde4431
--- /dev/null
+++ b/yt/yt/client/unittests/chunk_replica_ut.cpp
@@ -0,0 +1,47 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/chunk_client/chunk_replica.h>
+
+#include <util/random/random.h>
+
+#include <stdlib.h>
+
+namespace NYT::NChunkClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TChunkReplicaWithMediumTest, Simple)
+{
+ int totalNodes = 10;
+ srand(0);
+ for (int i = 0; i < totalNodes; ++i) {
+ auto nodeId = NNodeTrackerClient::TNodeId(rand() % (1 << 23));
+ int replicaIndex = rand() % (1 << 4);
+ int mediumIndex = rand() % (1 << 7);
+ TChunkReplicaWithMedium replica(nodeId, replicaIndex, mediumIndex);
+
+ EXPECT_THAT(replica.GetNodeId(), nodeId);
+ EXPECT_THAT(replica.GetReplicaIndex(), replicaIndex);
+ EXPECT_THAT(replica.GetMediumIndex(), mediumIndex);
+
+ ui64 value;
+ ToProto(&value, replica);
+
+ TChunkReplicaWithMedium replica1;
+ FromProto(&replica1, value);
+
+ EXPECT_THAT(replica.GetNodeId(), replica1.GetNodeId());
+ EXPECT_THAT(replica.GetReplicaIndex(), replica1.GetReplicaIndex());
+ EXPECT_THAT(replica.GetMediumIndex(), replica1.GetMediumIndex());
+
+ TChunkReplica replica2(replica);
+ EXPECT_THAT(replica.GetNodeId(), replica2.GetNodeId());
+ EXPECT_THAT(replica.GetReplicaIndex(), replica2.GetReplicaIndex());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NChunkClient
diff --git a/yt/yt/client/unittests/column_sort_schema_ut.cpp b/yt/yt/client/unittests/column_sort_schema_ut.cpp
new file mode 100644
index 0000000000..897e4cdcc3
--- /dev/null
+++ b/yt/yt/client/unittests/column_sort_schema_ut.cpp
@@ -0,0 +1,70 @@
+#include <yt/yt/client/table_client/column_sort_schema.h>
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TColumnSortSchemaTest, Serialize)
+{
+ auto test = [] (TString name, ESortOrder sortOrder, TString expected) {
+ TColumnSortSchema schema{
+ .Name = name,
+ .SortOrder = sortOrder
+ };
+ auto serialized = ConvertToYsonString(schema, NYson::EYsonFormat::Text).ToString();
+ EXPECT_EQ(serialized, expected);
+ };
+
+ test("foo", ESortOrder::Ascending, "\"foo\"");
+ test("bar", ESortOrder::Descending, "{\"name\"=\"bar\";\"sort_order\"=\"descending\";}");
+}
+
+TEST(TColumnSortSchemaTest, Deserialize)
+{
+ auto test = [] (TString serialized, TString name, ESortOrder sortOrder) {
+ auto schema = ConvertTo<TColumnSortSchema>(TYsonString(serialized));
+ EXPECT_EQ(schema.Name, name);
+ EXPECT_EQ(schema.SortOrder, sortOrder);
+ };
+
+ test("{\"name\"=\"foo\";\"sort_order\"=\"ascending\"}", "foo", ESortOrder::Ascending);
+ test("{\"name\"=\"bar\";\"sort_order\"=\"descending\"}", "bar", ESortOrder::Descending);
+ test("\"baz\"", "baz", ESortOrder::Ascending);
+ EXPECT_THROW_WITH_SUBSTRING(test("42", "foo", ESortOrder::Ascending), "Unexpected type of column sort schema node");
+}
+
+TEST(TColumnSortSchemaTest, ProtobufConversion)
+{
+ TSortColumns sortColumns;
+ sortColumns.push_back(TColumnSortSchema{
+ .Name = "foo",
+ .SortOrder = ESortOrder::Ascending
+ });
+ sortColumns.push_back(TColumnSortSchema{
+ .Name = "bar",
+ .SortOrder = ESortOrder::Descending
+ });
+
+ NProto::TSortColumnsExt sortColumnsExt;
+ ToProto(&sortColumnsExt, sortColumns);
+
+ TSortColumns parsedSortColumns;
+ FromProto(&parsedSortColumns, sortColumnsExt);
+
+ EXPECT_EQ(sortColumns, parsedSortColumns);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/comparator_ut.cpp b/yt/yt/client/unittests/comparator_ut.cpp
new file mode 100644
index 0000000000..192b303bbe
--- /dev/null
+++ b/yt/yt/client/unittests/comparator_ut.cpp
@@ -0,0 +1,365 @@
+#include "key_helpers.h"
+
+#include <yt/yt/client/table_client/key_bound.h>
+#include <yt/yt/client/table_client/comparator.h>
+#include <yt/yt/client/table_client/helpers.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <library/cpp/iterator/zip.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static const auto IntValue1 = MakeUnversionedInt64Value(-7);
+static const auto IntValue2 = MakeUnversionedInt64Value(18);
+static const auto IntValue3 = MakeUnversionedInt64Value(42);
+static const auto StrValue1 = MakeUnversionedStringValue("foo");
+static const auto StrValue2 = MakeUnversionedStringValue("bar");
+static const auto NullValue = MakeUnversionedNullValue();
+static const auto MaxValue = MakeUnversionedSentinelValue(EValueType::Max);
+static const auto MinValue = MakeUnversionedSentinelValue(EValueType::Min);
+
+static const std::vector<TUnversionedValue> AllValues = {
+ IntValue1,
+ IntValue2,
+ StrValue1,
+ StrValue2,
+ NullValue,
+ MaxValue,
+ MinValue,
+};
+
+static const std::vector<TUnversionedValue> NoSentinelValues = {
+ IntValue1,
+ IntValue2,
+ StrValue1,
+ StrValue2,
+ NullValue,
+};
+
+class TComparatorTest
+ : public ::testing::Test
+{
+protected:
+ void InvokeForAllRows(
+ const std::vector<TUnversionedValue>& possibleValues,
+ int minLength,
+ int maxLength,
+ std::function<void(const TUnversionedOwningRow& row)> callback)
+ {
+ std::vector<TUnversionedValue> stack;
+ auto recursiveFill = [&] (int index, auto recursiveFill) {
+ if (index >= minLength && index <= maxLength) {
+ callback(MakeRow(stack));
+ }
+ if (index == maxLength) {
+ return;
+ }
+ for (const auto& value : possibleValues) {
+ stack.push_back(value);
+ recursiveFill(index + 1, recursiveFill);
+ stack.pop_back();
+ }
+ };
+ recursiveFill(0, recursiveFill);
+ }
+
+ std::vector<TKey> GenerateKeys(
+ const std::vector<TUnversionedValue>& possibleValues,
+ int keyLength)
+ {
+ RowStorage_.clear();
+ InvokeForAllRows(possibleValues, keyLength, keyLength, [&] (auto row) { RowStorage_.push_back(row); });
+
+ std::vector<TKey> keys;
+ keys.reserve(RowStorage_.size());
+ for (const auto& row : RowStorage_) {
+ keys.push_back(TKey::FromRow(row));
+ }
+ return keys;
+ }
+
+ std::vector<TComparator> GenerateComparators(int minLength, int maxLength)
+ {
+ std::vector<TComparator> result;
+ std::vector<ESortOrder> stack;
+ auto recursiveFill = [&] (int index, auto recursiveFill) {
+ if (index >= minLength && index <= maxLength) {
+ result.emplace_back(stack);
+ }
+ if (index == maxLength) {
+ return;
+ }
+ for (const auto& value : TEnumTraits<ESortOrder>::GetDomainValues()) {
+ stack.push_back(value);
+ recursiveFill(index + 1, recursiveFill);
+ stack.pop_back();
+ }
+ };
+ recursiveFill(0, recursiveFill);
+ return result;
+ }
+
+private:
+ std::vector<TUnversionedOwningRow> RowStorage_;
+};
+
+TEST_F(TComparatorTest, StressNewAndLegacyTestEquivalence)
+{
+ constexpr int KeyLength = 3;
+ const auto Comparator = MakeComparator(KeyLength);
+ constexpr int LegacyRowLength = 5;
+
+ // Generate all possible keys of length 3.
+ auto allKeys = GenerateKeys(NoSentinelValues, KeyLength);
+
+ auto validateTestPreservation = [&] (const TKeyBound& keyBound, TUnversionedRow legacyRow) {
+ bool isUpper = keyBound.IsUpper;
+ for (const auto& key : allKeys) {
+ auto legacyTest = isUpper ? key.AsOwningRow() < legacyRow : key.AsOwningRow() >= legacyRow;
+ auto newTest = Comparator.TestKey(key, keyBound);
+
+ if (legacyTest != newTest) {
+ Cerr
+ << "Legacy row: " << ToString(legacyRow) << Endl
+ << "Key bound: " << ToString(keyBound) << Endl
+ << "Key: " << ToString(key.AsOwningRow()) << Endl
+ << "LegacyTest: " << legacyTest << Endl
+ << "NewTest: " << newTest << Endl;
+ // Somehow ASSERTTs do not stop execution in our gtest :(
+ THROW_ERROR_EXCEPTION("Failure");
+ }
+ }
+ };
+
+ // Legacy -> New.
+ // Check that all possible legacy bounds of length up to 5 produce
+ // same test result as corresponding key bounds over all keys of length 3.
+
+ auto validateCurrentLegacyRow = [&] (const auto& legacyRow) {
+ for (bool isUpper : {false, true}) {
+ auto keyBound = KeyBoundFromLegacyRow(legacyRow, isUpper, KeyLength);
+ validateTestPreservation(keyBound, legacyRow);
+ }
+ };
+
+ // Test all possible legacy bounds of length up to 5.
+ InvokeForAllRows(AllValues, 0, LegacyRowLength, validateCurrentLegacyRow);
+
+ // New -> Legacy.
+ // Check that all possible key bounds of length up to 3 produce
+ // same test result as corresponding legacy bounds over all keys of length 3.
+
+ auto validateCurrentKeyBound = [&] (const auto& row) {
+ for (bool isUpper : {false, true}) {
+ for (bool isInclusive : {false, true}) {
+ auto keyBound = TOwningKeyBound::FromRow(row, isInclusive, isUpper);
+ auto legacyRow = KeyBoundToLegacyRow(keyBound);
+ validateTestPreservation(keyBound, legacyRow);
+ }
+ }
+ };
+
+ // Test all possible key bounds of length up to 3.
+ InvokeForAllRows(NoSentinelValues, 0, KeyLength, validateCurrentKeyBound);
+}
+
+TEST_F(TComparatorTest, KeyBoundComparisonWellFormedness)
+{
+ auto testComparator = [&] (const TComparator& comparator) {
+ std::vector<TOwningKeyBound> keyBounds;
+ InvokeForAllRows(NoSentinelValues, 0, comparator.GetLength(), [&] (const auto& row) {
+ for (bool isUpper : {false, true}) {
+ for (bool isInclusive : {false, true}) {
+ keyBounds.emplace_back(TOwningKeyBound::FromRow(row, isInclusive, isUpper));
+ }
+ }
+ });
+
+ for (int lowerVsUpperResult : {-1, 0, 1}) {
+ for (const auto& keyBoundA : keyBounds) {
+ // Reflexivity.
+ EXPECT_EQ(0, comparator.CompareKeyBounds(keyBoundA, keyBoundA, lowerVsUpperResult));
+ for (const auto& keyBoundB : keyBounds) {
+ // Antisymmetry.
+ EXPECT_EQ(
+ comparator.CompareKeyBounds(keyBoundA, keyBoundB, lowerVsUpperResult),
+ -comparator.CompareKeyBounds(keyBoundB, keyBoundA, lowerVsUpperResult));
+ for (const auto& keyBoundC : keyBounds) {
+ // Transitivity.
+ int compAB = comparator.CompareKeyBounds(keyBoundA, keyBoundB, lowerVsUpperResult);
+ int compBC = comparator.CompareKeyBounds(keyBoundB, keyBoundC, lowerVsUpperResult);
+ int compAC = comparator.CompareKeyBounds(keyBoundA, keyBoundC, lowerVsUpperResult);
+ if (compAB == -1 && compBC == -1) {
+ EXPECT_EQ(compAC, -1);
+ } else if (compAB <= 0 && compBC <= 0) {
+ EXPECT_LE(compAC, 0);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ constexpr int MaxKeyLength = 2;
+
+ auto comparators = GenerateComparators(1, MaxKeyLength);
+
+ for (const auto& comparator : comparators) {
+ testComparator(comparator);
+ }
+}
+
+TEST_F(TComparatorTest, KeyBoundLowerVsUpperResult)
+{
+ constexpr int KeyLength = 3;
+ const auto Comparator = MakeComparator(KeyLength);
+
+ std::vector<TOwningKeyBound> keyBounds;
+ InvokeForAllRows(NoSentinelValues, 0, KeyLength, [&] (const auto& row) {
+ for (bool isInclusive : {false, true}) {
+ auto upperBound = TOwningKeyBound::FromRow(row, isInclusive, /* isUpper */ true);
+ auto lowerBound = upperBound.Invert();
+ for (int lowerVsUpperResult : {-1, 0, 1}) {
+ EXPECT_EQ(lowerVsUpperResult, Comparator.CompareKeyBounds(lowerBound, upperBound, lowerVsUpperResult));
+ }
+ }
+ });
+}
+
+TEST_F(TComparatorTest, KeyBoundMonotonicity)
+{
+ auto testComparator = [&] (const TComparator& comparator) {;
+ // Generate all possible keys of length 3.
+ auto allKeys = GenerateKeys(NoSentinelValues, comparator.GetLength());
+
+ // And all possible upper key bounds of length up to 3.
+ std::vector<TOwningKeyBound> keyBounds;
+ InvokeForAllRows(NoSentinelValues, 0, comparator.GetLength(), [&] (const auto& row) {
+ for (bool isInclusive : {false, true}) {
+ auto upperBound = TOwningKeyBound::FromRow(row, isInclusive, /* isUpper */ true);
+ keyBounds.emplace_back(std::move(upperBound));
+ }
+ });
+
+ std::sort(keyBounds.begin(), keyBounds.end(), [&] (const auto& lhs, const auto& rhs) {
+ return comparator.CompareKeyBounds(lhs, rhs) < 0;
+ });
+
+ // Check that for any key K, predicate "key bound KB admits K" is monotonic
+ // while iterating with KB over #keyBounds (i.e. is false up to some moment, and
+ // is true after that).
+
+ for (const auto& key : allKeys) {
+ bool previousTestResult = false;
+ for (const auto& keyBound : keyBounds) {
+ auto testResult = comparator.TestKey(key, keyBound);
+ EXPECT_LE(previousTestResult, testResult);
+ previousTestResult = testResult;
+ }
+ }
+ };
+
+ constexpr int MaxKeyLength = 3;
+ for (const auto& comparator : GenerateComparators(1, MaxKeyLength)) {
+ testComparator(comparator);
+ }
+}
+
+TEST_F(TComparatorTest, SortOrder)
+{
+ auto row1 = MakeRow({IntValue1});
+ auto key1 = TKey::FromRow(row1);
+ auto keyBoundLe2 = MakeKeyBound({IntValue2}, /* isInclusive */ true, /* isUpper */ true);
+ auto row3 = MakeRow({IntValue3});
+ auto key3 = TKey::FromRow(row3);
+
+ auto comparatorAscending = TComparator({ESortOrder::Ascending});
+ auto comparatorDescending = TComparator({ESortOrder::Descending});
+
+ EXPECT_LT(comparatorAscending.CompareKeys(key1, key3), 0);
+ EXPECT_GT(comparatorDescending.CompareKeys(key1, key3), 0);
+ EXPECT_TRUE(comparatorAscending.TestKey(key1, keyBoundLe2));
+ EXPECT_FALSE(comparatorAscending.TestKey(key3, keyBoundLe2));
+ EXPECT_FALSE(comparatorDescending.TestKey(key1, keyBoundLe2));
+ EXPECT_TRUE(comparatorDescending.TestKey(key3, keyBoundLe2));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TComparatorTest, CompareWithWidening)
+{
+ auto int1 = MakeUnversionedInt64Value(1);
+ auto int2 = MakeUnversionedInt64Value(2);
+ auto null = MakeUnversionedSentinelValue(EValueType::Null);
+
+ // Equal length.
+ EXPECT_EQ(CompareWithWidening({}, {}), 0);
+ EXPECT_EQ(CompareWithWidening({int1}, {int1}), 0);
+ EXPECT_EQ(CompareWithWidening({int1}, {int2}), -1);
+ EXPECT_EQ(CompareWithWidening({int2}, {int1}), 1);
+ EXPECT_EQ(CompareWithWidening({int1, int2}, {int1, int1}), 2);
+ EXPECT_EQ(CompareWithWidening({int1, int1}, {int1, int2}), -2);
+
+ // Different length.
+ EXPECT_EQ(CompareWithWidening({int1}, {int1, int2}), -2);
+ EXPECT_EQ(CompareWithWidening({int1}, {int1, null}), 0);
+ EXPECT_EQ(CompareWithWidening({int1, null}, {int1, null}), 0);
+ EXPECT_EQ(CompareWithWidening({int1, null}, {int1, null, null}), 0);
+ EXPECT_EQ(CompareWithWidening({int1, null}, {int1, null, int1}), -3);
+
+ // Lower bound shorter bound.
+ EXPECT_FALSE(TestKeyWithWidening({int1, int2}, TKeyBoundRef({int1}, false)));
+ EXPECT_TRUE(TestKeyWithWidening({int1, int2}, TKeyBoundRef({int1}, true)));
+
+ // Upper bound shorter bound.
+ EXPECT_FALSE(TestKeyWithWidening({int1, int2}, TKeyBoundRef({int1}, false, true)));
+ EXPECT_TRUE(TestKeyWithWidening({int1, int2}, TKeyBoundRef({int1}, true, true)));
+
+ // Lower bound equal length.
+ EXPECT_FALSE(TestKeyWithWidening({int1, int1}, TKeyBoundRef({int1, int1}, false)));
+ EXPECT_TRUE(TestKeyWithWidening({int1, int1}, TKeyBoundRef({int1, int1}, true)));
+
+ EXPECT_FALSE(TestKeyWithWidening({int1, int1}, TKeyBoundRef({int1, int2}, false)));
+ EXPECT_FALSE(TestKeyWithWidening({int1, int1}, TKeyBoundRef({int1, int2}, true)));
+
+ // Upper bound.
+ EXPECT_TRUE(TestKeyWithWidening({int1, int1}, TKeyBoundRef({int1, int2}, false, true)));
+ EXPECT_TRUE(TestKeyWithWidening({int1, int1}, TKeyBoundRef({int1, int2}, true, true)));
+
+ // Lower bound with null and widening.
+ EXPECT_FALSE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, null}, false)));
+ EXPECT_TRUE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, null}, true)));
+
+ // Upper bound with null and widening.
+ EXPECT_FALSE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, null}, false ,true)));
+ EXPECT_TRUE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, null}, true ,true)));
+
+ // Lower bound longer bound.
+ EXPECT_FALSE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, int2}, false)));
+ EXPECT_FALSE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, int2}, true)));
+
+ EXPECT_TRUE(TestKeyWithWidening({int2}, TKeyBoundRef({int1, int2}, false)));
+ EXPECT_TRUE(TestKeyWithWidening({int2}, TKeyBoundRef({int1, int2}, true)));
+
+ // Upper bound longer bound.
+ EXPECT_TRUE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, int2}, false ,true)));
+ EXPECT_TRUE(TestKeyWithWidening({int1}, TKeyBoundRef({int1, int2}, true ,true)));
+
+ EXPECT_FALSE(TestKeyWithWidening({int2}, TKeyBoundRef({int1, int2}, false ,true)));
+ EXPECT_FALSE(TestKeyWithWidening({int2}, TKeyBoundRef({int1, int2}, true ,true)));
+
+ auto sortOrdersAAD = {ESortOrder::Ascending, ESortOrder::Ascending, ESortOrder::Descending};
+
+ EXPECT_FALSE(TestKeyWithWidening({int1, null}, TKeyBoundRef({int1, null, int1})));
+ EXPECT_TRUE(TestKeyWithWidening({int1, null}, TKeyBoundRef({int1, null, int1}), sortOrdersAAD));
+
+ EXPECT_TRUE(TestKeyWithWidening({int1, null}, TKeyBoundRef({int1, null, int1}, false, true)));
+ EXPECT_FALSE(TestKeyWithWidening({int1, null}, TKeyBoundRef({int1, null, int1}, false, true), sortOrdersAAD));
+}
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/composite_compare_ut.cpp b/yt/yt/client/unittests/composite_compare_ut.cpp
new file mode 100644
index 0000000000..81a12bec20
--- /dev/null
+++ b/yt/yt/client/unittests/composite_compare_ut.cpp
@@ -0,0 +1,76 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/composite_compare.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/yson/writer.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TCompositeCompare, Simple)
+{
+ auto compare = [] (TStringBuf lhs, TStringBuf rhs) {
+ return CompareCompositeValues(NYson::TYsonStringBuf(lhs), NYson::TYsonStringBuf(rhs));
+ };
+
+ EXPECT_EQ(-1, compare("-4", "42"));
+ EXPECT_EQ(1, compare("42", "-4"));
+ EXPECT_EQ(0, compare("-4", "-4"));
+
+ EXPECT_EQ(-1, compare("4u", "42u"));
+ EXPECT_EQ(1, compare("42u", "4u"));
+ EXPECT_EQ(0, compare("4u", "4u"));
+
+ EXPECT_EQ(-1, compare("4.0", "4.2"));
+ EXPECT_EQ(1, compare("4.2", "4.0"));
+ EXPECT_EQ(0, compare("4.0", "4.0"));
+
+ EXPECT_EQ(1, compare("%nan ", "-5.0"));
+ EXPECT_EQ(-1, compare("-5.0", "%nan "));
+ EXPECT_EQ(0, compare("%nan ", "%nan "));
+
+ EXPECT_EQ(-1, compare("%false", "%true"));
+ EXPECT_EQ(1, compare("%true", "%false"));
+ EXPECT_EQ(0, compare("%true", "%true"));
+
+ EXPECT_EQ(1, compare("foo", "bar"));
+ EXPECT_EQ(-1, compare("foo", "fooo"));
+ EXPECT_EQ(0, compare("foo", "foo"));
+
+ // Tuple<Int64,Int64> or List<Int64>
+ EXPECT_EQ(-1, compare("[1; 2]", "[1; 3]"));
+ EXPECT_EQ(1, compare("[1; 3]", "[1; 2]"));
+
+ // List<Int64>
+ EXPECT_EQ(1, compare("[1; 2; 3]", "[1; 2]"));
+ EXPECT_EQ(-1, compare("[1; 2]", "[1; 2; 3]"));
+ EXPECT_EQ(0, compare("[1; 2; 3]", "[1; 2; 3]"));
+
+ // List<Optional<Int64>>
+ EXPECT_EQ(1, compare("[1; 2; #]", "[1; 2]"));
+ EXPECT_EQ(-1, compare("[1; 2]", "[1; 2; #]"));
+ EXPECT_EQ(-1, compare("[1; 2; #]", "[1; 2; 3]"));
+ EXPECT_EQ(1, compare("[1; 2; 3]", "[1; 2; #]"));
+}
+
+TEST(TCompositeCompare, CompositeFingerprint)
+{
+ auto getFarmHash = [] (TStringBuf value) {
+ return CompositeFarmHash(NYson::TYsonStringBuf(value));
+ };
+
+ EXPECT_EQ(getFarmHash("-42"), GetFarmFingerprint(MakeUnversionedInt64Value(-42)));
+ EXPECT_EQ(getFarmHash("100500u"), GetFarmFingerprint(MakeUnversionedUint64Value(100500)));
+ EXPECT_EQ(getFarmHash("3.25"), GetFarmFingerprint(MakeUnversionedDoubleValue(3.25)));
+ EXPECT_EQ(getFarmHash("%true"), GetFarmFingerprint(MakeUnversionedBooleanValue(true)));
+ EXPECT_EQ(getFarmHash("%false"), GetFarmFingerprint(MakeUnversionedBooleanValue(false)));
+ EXPECT_EQ(getFarmHash("#"), GetFarmFingerprint(MakeUnversionedNullValue()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/connection_ut.cpp b/yt/yt/client/unittests/connection_ut.cpp
new file mode 100644
index 0000000000..c9baba4468
--- /dev/null
+++ b/yt/yt/client/unittests/connection_ut.cpp
@@ -0,0 +1,50 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/api/rpc_proxy/private.h>
+
+namespace NYT::NApi::NRpcProxy {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProxyUrlTest
+ : public ::testing::Test
+{ };
+
+TEST_F(TProxyUrlTest, ParseProxyUrlAliasingRules)
+{
+ auto emptyRules = ParseProxyUrlAliasingRules("");
+ ASSERT_EQ(emptyRules.size(), 0u);
+ auto notEmptyRules = ParseProxyUrlAliasingRules(R"({primary="localhost:12345"})");
+ ASSERT_EQ(notEmptyRules.size(), 1u);
+ ASSERT_EQ(notEmptyRules.at("primary"), "localhost:12345");
+}
+
+TEST_F(TProxyUrlTest, ApplyProxyUrlAliasingRules)
+{
+ {
+ TString url = "markov";
+ ApplyProxyUrlAliasingRules(url, THashMap<TString, TString>({{"primary", "localhost:12345"}}));
+ ASSERT_EQ(url, "markov");
+ }
+ // See ENV in ya.make
+ {
+ TString url = "primary";
+ ApplyProxyUrlAliasingRules(url, THashMap<TString, TString>({{"primary", "localhost:12345"}}));
+ ASSERT_EQ(url, "localhost:12345");
+ }
+}
+
+TEST_F(TProxyUrlTest, NormalizeHttpProxyUrl)
+{
+ ASSERT_EQ(NormalizeHttpProxyUrl("markov"), "http://markov.yt.yandex.net");
+ // See ENV in ya.make
+ ASSERT_EQ(
+ NormalizeHttpProxyUrl("primary", THashMap<TString, TString>({{"primary", "localhost:12345"}})),
+ "http://localhost:12345");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NApi::NRpcProxy
diff --git a/yt/yt/client/unittests/dsv_parser_ut.cpp b/yt/yt/client/unittests/dsv_parser_ut.cpp
new file mode 100644
index 0000000000..0a0c724f9e
--- /dev/null
+++ b/yt/yt/client/unittests/dsv_parser_ut.cpp
@@ -0,0 +1,365 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/test_framework/yson_consumer_mock.h>
+
+#include <yt/yt/client/formats/dsv_parser.h>
+
+namespace NYT::NFormats {
+namespace {
+
+using namespace NYson;
+
+using ::testing::InSequence;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TDsvParserTest, Simple)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("integer"));
+ EXPECT_CALL(Mock, OnStringScalar("42"));
+ EXPECT_CALL(Mock, OnKeyedItem("string"));
+ EXPECT_CALL(Mock, OnStringScalar("some"));
+ EXPECT_CALL(Mock, OnKeyedItem("double"));
+ EXPECT_CALL(Mock, OnStringScalar("10"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("foo"));
+ EXPECT_CALL(Mock, OnStringScalar("bar"));
+ EXPECT_CALL(Mock, OnKeyedItem("one"));
+ EXPECT_CALL(Mock, OnStringScalar("1"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "integer=42\tstring=some\tdouble=10\n"
+ "foo=bar\tone=1\n";
+ ParseDsv(input, &Mock);
+}
+
+TEST(TDsvParserTest, EmptyInput)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ TString input = "";
+ ParseDsv(input, &Mock);
+}
+
+TEST(TDsvParserTest, BinaryData)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+
+ auto a = TString("\0\0\0\0", 4);
+ auto b = TString("\x80\0\x16\xC8", 4);
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("ntr"));
+ EXPECT_CALL(Mock, OnStringScalar(a));
+ EXPECT_CALL(Mock, OnKeyedItem("xrp"));
+ EXPECT_CALL(Mock, OnStringScalar(b));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "ntr=\\0\\0\\0\\0\txrp=\x80\\0\x16\xC8\n";
+ ParseDsv(input, &Mock);
+}
+
+TEST(TDsvParserTest, EmptyRecord)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "\n";
+ ParseDsv(input, &Mock);
+}
+
+TEST(TDsvParserTest, EmptyRecords)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "\n\n";
+ ParseDsv(input, &Mock);
+}
+
+TEST(TDsvParserTest, EmptyKeysAndValues)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem(""));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "=\n";
+ ParseDsv(input, &Mock);
+}
+
+TEST(TDsvParserTest, UnescapedZeroInInput)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+
+ TString input = TString("a\0b=v", 5);
+ EXPECT_ANY_THROW(
+ ParseDsv(input, &Mock);
+ );
+}
+
+TEST(TDsvParserTest, ZerosAreNotTerminals)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ TString key = TString("a\0b", 3);
+ TString value = TString("c\0d", 3);
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem(key));
+ EXPECT_CALL(Mock, OnStringScalar(value));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "a\\0b=c\\0d\n";
+ ParseDsv(input, &Mock);
+}
+
+TEST(TDsvParserTest, UnterminatedRecord)
+{
+ NiceMock<TMockYsonConsumer> Mock;
+
+ TString input = "a=b";
+ EXPECT_ANY_THROW(
+ ParseDsv(input, &Mock);
+ );
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTskvParserTest: public ::testing::Test
+{
+public:
+ StrictMock<TMockYsonConsumer> Mock;
+ NiceMock<TMockYsonConsumer> ErrorMock;
+
+ TDsvFormatConfigPtr Config;
+
+ void SetUp() override {
+ Config = New<TDsvFormatConfig>();
+ Config->LinePrefix = "tskv";
+ }
+};
+
+TEST_F(TTskvParserTest, Simple)
+{
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("id"));
+ EXPECT_CALL(Mock, OnStringScalar("1"));
+ EXPECT_CALL(Mock, OnKeyedItem("guid"));
+ EXPECT_CALL(Mock, OnStringScalar("100500"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("id"));
+ EXPECT_CALL(Mock, OnStringScalar("2"));
+ EXPECT_CALL(Mock, OnKeyedItem("guid"));
+ EXPECT_CALL(Mock, OnStringScalar("20025"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "tskv\n"
+ "tskv\tid=1\tguid=100500\t\n"
+ "tskv\tid=2\tguid=20025\n";
+ ParseDsv(input, &Mock, Config);
+}
+
+TEST_F(TTskvParserTest, SimpleWithNewLine)
+{
+ InSequence dummy;
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("foo"));
+ EXPECT_CALL(Mock, OnStringScalar("bar"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "tskv\tfoo=bar\n";
+ ParseDsv(input, &Mock, Config);
+}
+
+TEST_F(TTskvParserTest, Escaping)
+{
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a=b"));
+ EXPECT_CALL(Mock, OnStringScalar("c=d or e=f"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key_with_\t,\r_and_\n"));
+ EXPECT_CALL(Mock, OnStringScalar("value_with_\t,\\_and_\r\n"));
+ EXPECT_CALL(Mock, OnKeyedItem("another_key"));
+ EXPECT_CALL(Mock, OnStringScalar("another_value"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "t\\s\\kv\n"
+ "tskv" "\t" "a\\=b" "=" "c\\=d or e=f" "\n" // Note: unescaping is less strict
+ "tskv" "\t"
+ "key_with_\\t,\r_and_\\n"
+ "="
+ "value_with_\\t,\\\\_and_\\r\\n"
+ "\t"
+ "an\\other_\\key=anoth\\er_v\\alue"
+ "\n";
+
+ ParseDsv(input, &Mock, Config);
+}
+
+TEST_F(TTskvParserTest, DisabledEscaping)
+{
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a\\"));
+ EXPECT_CALL(Mock, OnStringScalar("b\\t=c\\=d or e=f\\0"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "tskv\t\\x\\y\n"
+ "tskv" "\t" "a\\=b\\t" "=" "c\\=d or e=f\\0" "\n";
+
+ Config->EnableEscaping = false;
+
+ ParseDsv(input, &Mock, Config);
+}
+
+TEST_F(TTskvParserTest, AllowedUnescapedSymbols)
+{
+ Config->LinePrefix = "prefix_with_=";
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("just_key"));
+ EXPECT_CALL(Mock, OnStringScalar("value_with_="));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "prefix_with_=" "\t" "just_key" "=" "value_with_=" "\n";
+ ParseDsv(input, &Mock, Config);
+}
+
+TEST_F(TTskvParserTest, UndefinedValues)
+{
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("b"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "tskv" "\t" "tskv" "\t" "tskv" "\n"
+ "tskv\t" "some_key" "\t\t\t" "a=b" "\t" "another_key" "\n" // Note: consequent \t
+ "tskv\n";
+ ParseDsv(input, &Mock, Config);
+}
+
+
+TEST_F(TTskvParserTest, OnlyLinePrefix)
+{
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "tskv\n";
+ ParseDsv(input, &Mock, Config);
+}
+
+TEST_F(TTskvParserTest, OnlyLinePrefixAndTab)
+{
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "tskv\t\n";
+ ParseDsv(input, &Mock, Config);
+}
+
+TEST_F(TTskvParserTest, NotFinishedLinePrefix)
+{
+ TString input = "tsk";
+
+ EXPECT_ANY_THROW(
+ ParseDsv(input, &ErrorMock, Config)
+ );
+}
+
+TEST_F(TTskvParserTest, WrongLinePrefix)
+{
+ TString input =
+ "tskv\ta=b\n"
+ "tZkv\tc=d\te=f\n"
+ "tskv\ta=b\n";
+
+ EXPECT_ANY_THROW(
+ ParseDsv(input, &ErrorMock, Config);
+ );
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NDriver
diff --git a/yt/yt/client/unittests/dsv_writer_ut.cpp b/yt/yt/client/unittests/dsv_writer_ut.cpp
new file mode 100644
index 0000000000..b5f96caacd
--- /dev/null
+++ b/yt/yt/client/unittests/dsv_writer_ut.cpp
@@ -0,0 +1,316 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/formats/dsv_parser.h>
+#include <yt/yt/client/formats/dsv_writer.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+namespace NYT::NFormats {
+namespace {
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NConcurrency;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TDsvWriterTest, StringScalar)
+{
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream);
+
+ consumer.OnStringScalar("0-2-xb-1234");
+ EXPECT_EQ("0-2-xb-1234", outputStream.Str());
+}
+
+TEST(TDsvWriterTest, ListContainingDifferentTypes)
+{
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream);
+
+ consumer.OnBeginList();
+ consumer.OnListItem();
+ consumer.OnInt64Scalar(100);
+ consumer.OnListItem();
+ consumer.OnStringScalar("foo");
+ consumer.OnListItem();
+ consumer.OnListItem();
+ consumer.OnBeginMap();
+ consumer.OnKeyedItem("a");
+ consumer.OnStringScalar("10");
+ consumer.OnKeyedItem("b");
+ consumer.OnStringScalar("c");
+ consumer.OnEndMap();
+ consumer.OnEndList();
+
+ TString output =
+ "100\n"
+ "foo\n"
+ "\n"
+ "a=10\tb=c\n";
+
+ EXPECT_EQ(output, outputStream.Str());
+}
+
+TEST(TDsvWriterTest, ListInsideList)
+{
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream);
+
+ consumer.OnBeginList();
+ consumer.OnListItem();
+ EXPECT_ANY_THROW(consumer.OnBeginList());
+}
+
+TEST(TDsvWriterTest, ListInsideMap)
+{
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream);
+
+ consumer.OnBeginMap();
+ consumer.OnKeyedItem("foo");
+ EXPECT_ANY_THROW(consumer.OnBeginList());
+}
+
+TEST(TDsvWriterTest, MapInsideMap)
+{
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream);
+
+ consumer.OnBeginMap();
+ consumer.OnKeyedItem("foo");
+ EXPECT_ANY_THROW(consumer.OnBeginMap());
+}
+
+TEST(TDsvWriterTest, WithoutEsacping)
+{
+ auto config = New<TDsvFormatConfig>();
+ config->EnableEscaping = false;
+
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream, config);
+
+ consumer.OnStringScalar("string_with_\t_\\_=_and_\n");
+
+ TString output = "string_with_\t_\\_=_and_\n";
+
+ EXPECT_EQ(output, outputStream.Str());
+}
+
+TEST(TDsvWriterTest, ListUsingOnRaw)
+{
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream);
+
+ consumer.OnRaw("[10; 20; 30]", EYsonType::Node);
+ TString output =
+ "10\n"
+ "20\n"
+ "30\n";
+
+ EXPECT_EQ(output, outputStream.Str());
+}
+
+TEST(TDsvWriterTest, MapUsingOnRaw)
+{
+ TStringStream outputStream;
+ TDsvNodeConsumer consumer(&outputStream);
+
+ consumer.OnRaw("{a=b; c=d}", EYsonType::Node);
+ TString output = "a=b\tc=d";
+
+ EXPECT_EQ(output, outputStream.Str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TDsvWriterTest, SimpleTabular)
+{
+ auto nameTable = New<TNameTable>();
+ auto integerId = nameTable->RegisterName("integer");
+ auto stringId = nameTable->RegisterName("string");
+ auto doubleId = nameTable->RegisterName("double");
+ auto fooId = nameTable->RegisterName("foo");
+ auto oneId = nameTable->RegisterName("one");
+ auto tableIndexId = nameTable->RegisterName(TableIndexColumnName);
+ auto rowIndexId = nameTable->RegisterName(RowIndexColumnName);
+ auto rangeIndexId = nameTable->RegisterName(RangeIndexColumnName);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedInt64Value(42, integerId));
+ row1.AddValue(MakeUnversionedStringValue("some", stringId));
+ row1.AddValue(MakeUnversionedDoubleValue(10., doubleId));
+ row1.AddValue(MakeUnversionedInt64Value(2, tableIndexId));
+ row1.AddValue(MakeUnversionedInt64Value(42, rowIndexId));
+ row1.AddValue(MakeUnversionedInt64Value(1, rangeIndexId));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("bar", fooId));
+ row2.AddValue(MakeUnversionedSentinelValue(EValueType::Null, integerId));
+ row2.AddValue(MakeUnversionedInt64Value(1, oneId));
+ row2.AddValue(MakeUnversionedInt64Value(2, tableIndexId));
+ row2.AddValue(MakeUnversionedInt64Value(43, rowIndexId));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow()};
+
+ TStringStream outputStream;
+ auto config = New<TDsvFormatConfig>();
+ config->EnableTableIndex = true;
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ controlAttributes->EnableTableIndex = true;
+ auto writer = CreateSchemalessWriterForDsv(
+ config,
+ nameTable,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&outputStream)),
+ false,
+ controlAttributes,
+ 0);
+
+ EXPECT_EQ(true, writer->Write(rows));
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output =
+ "integer=42\tstring=some\tdouble=10.\t@table_index=2\n"
+ "foo=bar\tone=1\t@table_index=2\n";
+ EXPECT_EQ(output, outputStream.Str());
+}
+
+TEST(TDsvWriterTest, AnyTabular)
+{
+ auto nameTable = New<TNameTable>();
+ auto anyId = nameTable->RegisterName("any");
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedAnyValue("[]", anyId));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ TStringStream outputStream;
+ auto controlAttributes = New<TControlAttributesConfig>();
+ auto writer = CreateSchemalessWriterForDsv(
+ New<TDsvFormatConfig>(),
+ nameTable,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&outputStream)),
+ false,
+ controlAttributes,
+ 0);
+
+ EXPECT_FALSE(writer->Write(rows));
+ EXPECT_ANY_THROW(writer->GetReadyEvent().Get().ThrowOnError());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TTskvWriterTest, SimpleTabular)
+{
+ auto nameTable = New<TNameTable>();
+ auto id1 = nameTable->RegisterName("id");
+ auto id2 = nameTable->RegisterName("guid");
+ auto tableIndexId = nameTable->RegisterName(TableIndexColumnName);
+ auto rowIndexId = nameTable->RegisterName(RowIndexColumnName);
+ auto rangeIndexId = nameTable->RegisterName(RangeIndexColumnName);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedInt64Value(2, tableIndexId));
+ row1.AddValue(MakeUnversionedInt64Value(42, rowIndexId));
+ row1.AddValue(MakeUnversionedInt64Value(1, rangeIndexId));
+
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("1", id1));
+ row2.AddValue(MakeUnversionedInt64Value(100500, id2));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("2", id1));
+ row3.AddValue(MakeUnversionedInt64Value(20025, id2));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow(), row3.GetRow() };
+
+ TStringStream outputStream;
+ auto config = New<TDsvFormatConfig>();
+ config->LinePrefix = "tskv";
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ auto writer = CreateSchemalessWriterForDsv(
+ config,
+ nameTable,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&outputStream)),
+ false,
+ controlAttributes,
+ 0);
+
+ EXPECT_EQ(true, writer->Write(rows));
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output =
+ "tskv\n"
+ "tskv\tid=1\tguid=100500\n"
+ "tskv\tid=2\tguid=20025\n";
+
+ EXPECT_EQ(output, outputStream.Str());
+}
+
+TEST(TTskvWriterTest, Escaping)
+{
+ auto key1 = TString("\0 is escaped", 12);
+
+ auto nameTable = New<TNameTable>();
+ auto id1 = nameTable->RegisterName(key1);
+ auto id2 = nameTable->RegisterName("Escaping in in key: \r \t \n \\ =");
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue(key1, id1));
+ row.AddValue(MakeUnversionedStringValue("Escaping in value: \r \t \n \\ =", id2));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ TStringStream outputStream;
+ auto config = New<TDsvFormatConfig>();
+ config->LinePrefix = "tskv";
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ auto writer = CreateSchemalessWriterForDsv(
+ config,
+ nameTable,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&outputStream)),
+ false,
+ controlAttributes,
+ 0);
+
+ EXPECT_EQ(true, writer->Write(rows));
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output =
+ "tskv"
+ "\t"
+
+ "\\0 is escaped"
+ "="
+ "\\0 is escaped"
+
+ "\t"
+
+ "Escaping in in key: \\r \\t \\n \\\\ \\="
+ "="
+ "Escaping in value: \\r \\t \\n \\\\ =" // Note: = is not escaped
+
+ "\n";
+
+ EXPECT_EQ(output, outputStream.Str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/farm_fingerprint_stability_ut.cpp b/yt/yt/client/unittests/farm_fingerprint_stability_ut.cpp
new file mode 100644
index 0000000000..1d01e1eef6
--- /dev/null
+++ b/yt/yt/client/unittests/farm_fingerprint_stability_ut.cpp
@@ -0,0 +1,99 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/unversioned_value.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+/////////////////////////////////////////////////////////////////////////////
+
+// The goal is to provide a sanity check for stability of FarmHash and FarmFingerprint functions
+// for TUnversionedRow and TUnversionedValue.
+class TFarmHashTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<
+ std::tuple<TUnversionedValue, TUnversionedValue, ui64, ui64, ui64>>
+{ };
+
+TEST_P(TFarmHashTest, TFarmHashUnversionedValueTest)
+{
+ const auto& params = GetParam();
+
+ std::array<TUnversionedValue, 2> values{std::get<0>(params), std::get<1>(params)};
+ auto valueRange = MakeRange(values);
+
+ auto expected1 = std::get<2>(params);
+ auto expected2 = std::get<3>(params);
+ auto expected3 = std::get<4>(params);
+
+ static_assert(std::is_same_v<ui64, decltype(TDefaultUnversionedValueHash()(values[0]))>);
+ EXPECT_EQ(expected1, TDefaultUnversionedValueHash()(values[0]));
+ EXPECT_EQ(expected2, TDefaultUnversionedValueHash()(values[1]));
+
+ static_assert(std::is_same_v<ui64, decltype(GetFarmFingerprint(values[0]))>);
+ EXPECT_EQ(expected1, GetFarmFingerprint(values[0]));
+ EXPECT_EQ(expected2, GetFarmFingerprint(values[1]));
+
+ static_assert(std::is_same_v<ui64, decltype(GetFarmFingerprint(valueRange))>);
+ EXPECT_EQ(expected3, GetFarmFingerprint(valueRange));
+}
+
+TEST_P(TFarmHashTest, TFarmHashUnversionedRowTest)
+{
+ const auto& params = GetParam();
+
+ char buf[sizeof(TUnversionedRowHeader) + 2 * sizeof(TUnversionedValue)];
+ *reinterpret_cast<TUnversionedRowHeader*>(buf) = TUnversionedRowHeader{2, 2};
+ *reinterpret_cast<TUnversionedValue*>(buf + sizeof(TUnversionedRowHeader)) = std::get<0>(params);
+ *reinterpret_cast<TUnversionedValue*>(buf + sizeof(TUnversionedRowHeader) + sizeof(TUnversionedValue)) = std::get<1>(params);
+ TUnversionedRow row(reinterpret_cast<TUnversionedRowHeader*>(buf));
+
+ auto expected = std::get<4>(params);
+
+ static_assert(std::is_same_v<ui64, decltype(TDefaultUnversionedRowHash()(row))>);
+ EXPECT_EQ(expected, TDefaultUnversionedRowHash()(row));
+
+ static_assert(std::is_same_v<ui64, decltype(GetFarmFingerprint(row))>);
+ EXPECT_EQ(expected, GetFarmFingerprint(row));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TFarmHashTest,
+ TFarmHashTest,
+ ::testing::Values(
+ std::make_tuple(
+ MakeUnversionedInt64Value(12345678, /*id*/ 0, EValueFlags::None),
+ MakeUnversionedUint64Value(42, /*id*/ 1, EValueFlags::Aggregate),
+ 18329046069279503950ULL,
+ 17355217915646310598ULL,
+ 16453323425893019626ULL),
+ std::make_tuple(
+ MakeUnversionedUint64Value(12345678, /*id*/ 1, EValueFlags::Aggregate),
+ MakeUnversionedBooleanValue(true, /*id*/ 2, EValueFlags::Aggregate),
+ 18329046069279503950ULL,
+ 10105606910506535461ULL,
+ 10502610411105654667ULL),
+ std::make_tuple(
+ MakeUnversionedDoubleValue(42.0, /*id*/ 2, EValueFlags::Aggregate),
+ MakeUnversionedStringValue("0", /*id*/ 3, EValueFlags::None),
+ 6259286942292166412ULL,
+ 15198969275252572735ULL,
+ 12125805494429148155ULL),
+ std::make_tuple(
+ MakeUnversionedBooleanValue(false, /*id*/ 3, EValueFlags::Aggregate),
+ MakeUnversionedStringValue("", /*id*/ 4, EValueFlags::None),
+ 0ULL,
+ 11160318154034397263ULL,
+ 10248854568006048452ULL),
+ std::make_tuple(
+ MakeUnversionedStringValue("abc", /*id*/ 4, EValueFlags::None),
+ MakeUnversionedInt64Value(-1000000, /*id*/ 5, EValueFlags::None),
+ 2640714258260161385ULL,
+ 13952380479379003069ULL,
+ 9998489714118868374ULL)));
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/format_writer_ut.h b/yt/yt/client/unittests/format_writer_ut.h
new file mode 100644
index 0000000000..4680090755
--- /dev/null
+++ b/yt/yt/client/unittests/format_writer_ut.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <yt/yt/client/formats/format.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+namespace NYT::NFormats {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TestNameTableExpansion(ISchemalessFormatWriterPtr writer, NTableClient::TNameTablePtr nameTable)
+{
+ // We write five rows, on each iteration we double number of
+ // columns in the NameTable.
+ for (int iteration = 0; iteration < 5; ++iteration) {
+ NTableClient::TUnversionedOwningRowBuilder row;
+ for (int index = 0; index < (1 << iteration); ++index) {
+ auto key = "Column" + ToString(index);
+ auto value = "Value" + ToString(index);
+ int columnId = nameTable->GetIdOrRegisterName(key);
+ row.AddValue(NTableClient::MakeUnversionedStringValue(value, columnId));
+ }
+ auto completeRow = row.FinishRow();
+ EXPECT_EQ(true, writer->Write({completeRow.Get()}));
+ }
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/key_bound_compressor_ut.cpp b/yt/yt/client/unittests/key_bound_compressor_ut.cpp
new file mode 100644
index 0000000000..8add67880f
--- /dev/null
+++ b/yt/yt/client/unittests/key_bound_compressor_ut.cpp
@@ -0,0 +1,88 @@
+#include "key_helpers.h"
+
+#include <yt/yt/client/table_client/key_bound.h>
+#include <yt/yt/client/table_client/key_bound_compressor.h>
+#include <yt/yt/client/table_client/helpers.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <library/cpp/iterator/zip.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TKeyBoundCompressorTest, Simple)
+{
+ TRowBufferPtr rowBuffer = New<TRowBuffer>();
+ auto preimageRow = [&] (std::optional<TStringBuf> first = std::nullopt, std::optional<int> second = std::nullopt) {
+ TUnversionedRowBuilder builder;
+ if (first) {
+ builder.AddValue(MakeUnversionedStringValue(*first, 0));
+ }
+ if (second) {
+ builder.AddValue(MakeUnversionedInt64Value(*second, 1));
+ }
+ return rowBuffer->CaptureRow(builder.GetRow());
+ };
+
+ auto imageRow = [&] (std::optional<int> first = std::nullopt, std::optional<int> second = std::nullopt) {
+ TUnversionedRowBuilder builder;
+ if (first) {
+ builder.AddValue(MakeUnversionedInt64Value(*first, 0));
+ }
+ if (second) {
+ builder.AddValue(MakeUnversionedInt64Value(*second, 1));
+ }
+ return rowBuffer->CaptureRow(builder.GetRow());
+ };
+
+ // See comment in key_bound_compressor.h for the readable version of the list below.
+ std::vector<std::tuple<TKeyBound, TKeyBound, int>> tuples{
+ {TKeyBound::FromRow() >= preimageRow( ), TKeyBound::FromRow() >= imageRow( ), 0},
+ {TKeyBound::FromRow() >= preimageRow("bar" ), TKeyBound::FromRow() >= imageRow(0 ), 1},
+ {TKeyBound::FromRow() >= preimageRow("bar", 42), TKeyBound::FromRow() >= imageRow(0, 0), 2},
+ {TKeyBound::FromRow() <= preimageRow("bar", 57), TKeyBound::FromRow() <= imageRow(0, 1), 3},
+ {TKeyBound::FromRow() <= preimageRow("bar" ), TKeyBound::FromRow() <= imageRow(0 ), 4},
+ {TKeyBound::FromRow() < preimageRow("foo" ), TKeyBound::FromRow() < imageRow(1 ), 5},
+ {TKeyBound::FromRow() >= preimageRow("foo" ), TKeyBound::FromRow() >= imageRow(1 ), 5},
+ {TKeyBound::FromRow() > preimageRow("foo", 30), TKeyBound::FromRow() > imageRow(1, 0), 6},
+ {TKeyBound::FromRow() < preimageRow("foo", 99), TKeyBound::FromRow() < imageRow(1, 1), 7},
+ {TKeyBound::FromRow() <= preimageRow("foo", 99), TKeyBound::FromRow() <= imageRow(1, 1), 7},
+ {TKeyBound::FromRow() > preimageRow("foo", 99), TKeyBound::FromRow() > imageRow(1, 1), 7},
+ {TKeyBound::FromRow() < preimageRow("qux", 18), TKeyBound::FromRow() < imageRow(2, 0), 8},
+ {TKeyBound::FromRow() < preimageRow("zzz" ), TKeyBound::FromRow() < imageRow(3 ), 9},
+ {TKeyBound::FromRow() <= preimageRow( ), TKeyBound::FromRow() <= imageRow( ), 10},
+ {TKeyBound::FromRow() > preimageRow( ), TKeyBound::FromRow() > imageRow( ), 10},
+ };
+
+ TComparator comparator(std::vector<ESortOrder>{ESortOrder::Ascending, ESortOrder::Ascending});
+ TKeyBoundCompressor compressor(comparator);
+
+ for (const auto& [key, _1, _2] : tuples) {
+ compressor.Add(key);
+ }
+
+ compressor.InitializeMapping();
+
+ for (const auto& [key, componentWise, total] : tuples) {
+ auto [actualComponentWise, actualTotal] = compressor.GetImage(key);
+ TKeyBound qwe = actualComponentWise;
+ (void)qwe;
+ EXPECT_EQ(componentWise, actualComponentWise)
+ << "For key " << ToString(key);
+ EXPECT_EQ(total, actualTotal)
+ << "For key " << ToString(key);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/key_bound_ut.cpp b/yt/yt/client/unittests/key_bound_ut.cpp
new file mode 100644
index 0000000000..c92be63a2f
--- /dev/null
+++ b/yt/yt/client/unittests/key_bound_ut.cpp
@@ -0,0 +1,258 @@
+#include "key_helpers.h"
+
+#include <yt/yt/client/table_client/key_bound.h>
+#include <yt/yt/client/table_client/helpers.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <library/cpp/iterator/zip.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TKeyBoundTest, Simple)
+{
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedDoubleValue(3.14, 0));
+ builder.AddValue(MakeUnversionedInt64Value(-42, 1));
+ builder.AddValue(MakeUnversionedUint64Value(27, 2));
+ TString str = "Foo";
+ builder.AddValue(MakeUnversionedStringValue(str, 3));
+
+ auto owningRow = builder.FinishRow();
+ // Builder captures string, so this address is different from str.data().
+ auto* strPtr = owningRow[3].Data.String;
+
+ auto row = owningRow;
+ auto rowBeginPtr = row.Begin();
+ {
+ auto keyBound = TKeyBound::FromRow(row, /* isInclusive */ false, /* isUpper */ false);
+ EXPECT_EQ(row, keyBound.Prefix);
+ EXPECT_EQ(rowBeginPtr, keyBound.Prefix.Begin());
+ }
+ {
+ // Steal row.
+ auto stolenKeyBound = TKeyBound::FromRow(std::move(row), /* isInclusive */ false, /* isUpper */ false);
+ EXPECT_EQ(owningRow, stolenKeyBound.Prefix);
+ EXPECT_EQ(rowBeginPtr, stolenKeyBound.Prefix.Begin());
+ }
+ {
+ auto owningKeyBound = TOwningKeyBound::FromRow(owningRow, /* isInclusive */ false, /* isUpper */ false);
+ EXPECT_EQ(owningRow, owningKeyBound.Prefix);
+ }
+ {
+ // Steal owningRow.
+ auto stolenOwningKeyBound = TOwningKeyBound::FromRow(std::move(owningRow), /* isInclusive */ false, /* isUpper */ false);
+ EXPECT_EQ(EValueType::String, stolenOwningKeyBound.Prefix[3].Type);
+ EXPECT_EQ(strPtr, stolenOwningKeyBound.Prefix[3].Data.String);
+ }
+}
+
+TEST(TKeyBound, Helper)
+{
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedDoubleValue(3.14, 0));
+ builder.AddValue(MakeUnversionedInt64Value(-42, 1));
+ builder.AddValue(MakeUnversionedUint64Value(27, 2));
+ TString str = "Foo";
+ builder.AddValue(MakeUnversionedStringValue(str, 3));
+
+ auto owningRow = builder.FinishRow();
+
+ auto explicitGT = TOwningKeyBound::FromRow(owningRow, /* isInclusive */ false, /* isUpper */ false);
+ auto helperGT = TOwningKeyBound::FromRow() > owningRow;
+ auto helperGTUnchecked = TOwningKeyBound::FromRowUnchecked() > owningRow;
+ EXPECT_EQ(explicitGT, helperGT);
+ EXPECT_EQ(explicitGT, helperGTUnchecked);
+
+ auto explicitGE = TOwningKeyBound::FromRow(owningRow, /* isInclusive */ true, /* isUpper */ false);
+ auto helperGE = TOwningKeyBound::FromRow() >= owningRow;
+ auto helperGEUnchecked = TOwningKeyBound::FromRowUnchecked() >= owningRow;
+ EXPECT_EQ(explicitGE, helperGE);
+ EXPECT_EQ(explicitGE, helperGEUnchecked);
+
+ auto explicitLT = TOwningKeyBound::FromRow(owningRow, /* isInclusive */ false, /* isUpper */ true);
+ auto helperLT = TOwningKeyBound::FromRow() < owningRow;
+ auto helperLTUnchecked = TOwningKeyBound::FromRowUnchecked() < owningRow;
+ EXPECT_EQ(explicitLT, helperLT);
+ EXPECT_EQ(explicitLT, helperLTUnchecked);
+
+ auto explicitLE = TOwningKeyBound::FromRow(owningRow, /* isInclusive */ true, /* isUpper */ true);
+ auto helperLE = TOwningKeyBound::FromRow() <= owningRow;
+ auto helperLEUnchecked = TOwningKeyBound::FromRowUnchecked() <= owningRow;
+ EXPECT_EQ(explicitLE, helperLE);
+ EXPECT_EQ(explicitLE, helperLEUnchecked);
+}
+
+TEST(TKeyBoundTest, KeyBoundToLegacyRow)
+{
+ auto intValue = MakeUnversionedInt64Value(42);
+ auto maxValue = MakeUnversionedSentinelValue(EValueType::Max);
+
+ std::vector<TOwningKeyBound> keyBounds = {
+ MakeKeyBound({intValue}, /* isInclusive */ false, /* isUpper */ false),
+ MakeKeyBound({intValue}, /* isInclusive */ false, /* isUpper */ true),
+ MakeKeyBound({intValue}, /* isInclusive */ true, /* isUpper */ false),
+ MakeKeyBound({intValue}, /* isInclusive */ true, /* isUpper */ true),
+ };
+
+ auto expectedLegacyRows = {
+ MakeRow({intValue, maxValue}),
+ MakeRow({intValue}),
+ MakeRow({intValue}),
+ MakeRow({intValue, maxValue}),
+ };
+
+ for (const auto& [keyBound, legacyRow] : Zip(keyBounds, expectedLegacyRows)) {
+ EXPECT_EQ(KeyBoundToLegacyRow(keyBound), legacyRow);
+ }
+}
+
+TEST(TKeyBoundTest, KeyBoundFromLegacyRow)
+{
+ auto intValue1 = MakeUnversionedInt64Value(42);
+ auto intValue2 = MakeUnversionedInt64Value(-7);
+ auto intValue3 = MakeUnversionedInt64Value(0);
+ auto maxValue = MakeUnversionedSentinelValue(EValueType::Max);
+ auto minValue = MakeUnversionedSentinelValue(EValueType::Min);
+ const int KeyLength = 2;
+
+ // Refer to comment in KeyBoundFromLegacyRow for detailed explanation of possible cases.
+
+ // (A)
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2, intValue3}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ false, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2, intValue3}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ true, /* isUpper */ true));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2, maxValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ false, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2, maxValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ true, /* isUpper */ true));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2, minValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ false, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2, minValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ true, /* isUpper */ true));
+
+ // (B)
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ true, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, intValue2}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1, intValue2}, /* isInclusive */ false, /* isUpper */ true));
+
+ // (C)
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ true));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ true));
+
+ // (C), arbitrary garbage after first sentinel does not change outcome.
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue, minValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue, minValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ true));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue, maxValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue, maxValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ true));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue, intValue2}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, minValue, intValue2}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ true));
+
+ // (D)
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ true));
+
+ // (D), arbitrary garbage after first sentinel does not change outcome.
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue, minValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue, minValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ true));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue, maxValue}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue, maxValue}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ true));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue, intValue2}), /* isUpper */ false, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ false, /* isUpper */ false));
+ EXPECT_EQ(
+ KeyBoundFromLegacyRow(MakeRow({intValue1, maxValue, intValue2}), /* isUpper */ true, KeyLength),
+ MakeKeyBound({intValue1}, /* isInclusive */ true, /* isUpper */ true));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TKeyBoundTest, Serialization)
+{
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedDoubleValue(3.14, 0));
+ builder.AddValue(MakeUnversionedInt64Value(-42, 1));
+ builder.AddValue(MakeUnversionedUint64Value(27, 2));
+ TString str = "Foo";
+ builder.AddValue(MakeUnversionedStringValue(str, 3));
+
+ auto owningRow = builder.FinishRow();
+
+ auto keyBoundGT = TOwningKeyBound::FromRow() > owningRow;
+ auto keyBoundGE = TOwningKeyBound::FromRow() >= owningRow;
+ auto keyBoundLT = TOwningKeyBound::FromRow() < owningRow;
+ auto keyBoundLE = TOwningKeyBound::FromRow() <= owningRow;
+
+ EXPECT_EQ(
+ ConvertToYsonString(keyBoundGT, EYsonFormat::Text).AsStringBuf(),
+ "[\">\";[3.14;-42;27u;\"Foo\";];]");
+ EXPECT_EQ(
+ ConvertToYsonString(keyBoundGE, EYsonFormat::Text).AsStringBuf(),
+ "[\">=\";[3.14;-42;27u;\"Foo\";];]");
+ EXPECT_EQ(
+ ConvertToYsonString(keyBoundLT, EYsonFormat::Text).AsStringBuf(),
+ "[\"<\";[3.14;-42;27u;\"Foo\";];]");
+ EXPECT_EQ(
+ ConvertToYsonString(keyBoundLE, EYsonFormat::Text).AsStringBuf(),
+ "[\"<=\";[3.14;-42;27u;\"Foo\";];]");
+ EXPECT_EQ(
+ ConvertToYsonString(TOwningKeyBound(), EYsonFormat::Text).AsStringBuf(),
+ "#");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/key_helpers.cpp b/yt/yt/client/unittests/key_helpers.cpp
new file mode 100644
index 0000000000..f6ee9ad97c
--- /dev/null
+++ b/yt/yt/client/unittests/key_helpers.cpp
@@ -0,0 +1,28 @@
+#include "key_helpers.h"
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedOwningRow MakeRow(const std::vector<TUnversionedValue>& values)
+{
+ TUnversionedOwningRowBuilder builder;
+ for (const auto& value : values) {
+ builder.AddValue(value);
+ }
+ return builder.FinishRow();
+}
+
+TOwningKeyBound MakeKeyBound(const std::vector<TUnversionedValue>& values, bool isInclusive, bool isUpper)
+{
+ return TOwningKeyBound::FromRow(MakeRow(values), isInclusive, isUpper);
+}
+
+TComparator MakeComparator(int keyLength)
+{
+ return TComparator(std::vector<ESortOrder>(keyLength, ESortOrder::Ascending));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/key_helpers.h b/yt/yt/client/unittests/key_helpers.h
new file mode 100644
index 0000000000..c0df86521a
--- /dev/null
+++ b/yt/yt/client/unittests/key_helpers.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <yt/yt/client/table_client/key.h>
+#include <yt/yt/client/table_client/key_bound.h>
+#include <yt/yt/client/table_client/comparator.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TUnversionedOwningRow MakeRow(const std::vector<TUnversionedValue>& values);
+TOwningKeyBound MakeKeyBound(const std::vector<TUnversionedValue>& values, bool isInclusive, bool isUpper);
+TComparator MakeComparator(int keyLength);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/key_ut.cpp b/yt/yt/client/unittests/key_ut.cpp
new file mode 100644
index 0000000000..0d35528f7b
--- /dev/null
+++ b/yt/yt/client/unittests/key_ut.cpp
@@ -0,0 +1,63 @@
+#include <yt/yt/client/table_client/key.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/yson/string.h>
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TKeyTest, Simple)
+{
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedDoubleValue(3.14, 0));
+ builder.AddValue(MakeUnversionedInt64Value(-42, 1));
+ builder.AddValue(MakeUnversionedUint64Value(27, 2));
+ TString str = "Foo";
+ builder.AddValue(MakeUnversionedStringValue(str, 3));
+
+ auto row = builder.FinishRow();
+ {
+ auto key = TKey::FromRow(row);
+ EXPECT_EQ(row, key.AsOwningRow());
+ EXPECT_EQ(row.Begin(), key.Begin());
+ EXPECT_EQ(4, key.GetLength());
+ }
+ {
+ TUnversionedOwningRow shortenedRow(row.FirstNElements(2));
+ auto key = TKey::FromRow(row, /*length*/2);
+ EXPECT_EQ(shortenedRow, key.AsOwningRow());
+ EXPECT_EQ(row.Begin(), key.Begin());
+ EXPECT_EQ(2, key.GetLength());
+ }
+}
+
+TEST(TKeyTest, Serialization)
+{
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedDoubleValue(3.14, 0));
+ builder.AddValue(MakeUnversionedInt64Value(-42, 1));
+ builder.AddValue(MakeUnversionedUint64Value(27, 2));
+ TString str = "Foo";
+ builder.AddValue(MakeUnversionedStringValue(str, 3));
+ auto row = builder.FinishRow();
+ TKey key = TKey::FromRow(row);
+
+ EXPECT_EQ(
+ ConvertToYsonString(key, EYsonFormat::Text).AsStringBuf(),
+ "[3.14;-42;27u;\"Foo\";]");
+ EXPECT_EQ(
+ ConvertToYsonString(TKey(), EYsonFormat::Text).AsStringBuf(),
+ "#");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/logical_type_shortcuts.h b/yt/yt/client/unittests/logical_type_shortcuts.h
new file mode 100644
index 0000000000..7b22006a06
--- /dev/null
+++ b/yt/yt/client/unittests/logical_type_shortcuts.h
@@ -0,0 +1,135 @@
+#include <yt/yt/client/table_client/logical_type.h>
+
+/**
+ * This file contains a bunch of shortcut functions for creating logical types.
+ * They are useful in places where we have to create a lot of logical types e.g. in tests.
+ */
+
+namespace NYT::NTableClient::NLogicalTypeShortcuts {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Simple types:
+// TLogicalTypePtr Int8();
+// and friends.
+
+#define CREATE_SIMPLE_TYPE_FUNCTION(name) \
+ inline TLogicalTypePtr name() \
+ { \
+ return SimpleLogicalType(ESimpleLogicalValueType::name); \
+ }
+
+CREATE_SIMPLE_TYPE_FUNCTION(Int8)
+CREATE_SIMPLE_TYPE_FUNCTION(Int16)
+CREATE_SIMPLE_TYPE_FUNCTION(Int32)
+CREATE_SIMPLE_TYPE_FUNCTION(Int64)
+
+CREATE_SIMPLE_TYPE_FUNCTION(Uint8)
+CREATE_SIMPLE_TYPE_FUNCTION(Uint16)
+CREATE_SIMPLE_TYPE_FUNCTION(Uint32)
+CREATE_SIMPLE_TYPE_FUNCTION(Uint64)
+
+CREATE_SIMPLE_TYPE_FUNCTION(Float)
+CREATE_SIMPLE_TYPE_FUNCTION(Double)
+
+CREATE_SIMPLE_TYPE_FUNCTION(Utf8)
+CREATE_SIMPLE_TYPE_FUNCTION(String)
+
+CREATE_SIMPLE_TYPE_FUNCTION(Date)
+CREATE_SIMPLE_TYPE_FUNCTION(Datetime)
+CREATE_SIMPLE_TYPE_FUNCTION(Timestamp)
+CREATE_SIMPLE_TYPE_FUNCTION(Interval)
+
+CREATE_SIMPLE_TYPE_FUNCTION(Json)
+
+CREATE_SIMPLE_TYPE_FUNCTION(Null)
+CREATE_SIMPLE_TYPE_FUNCTION(Void)
+CREATE_SIMPLE_TYPE_FUNCTION(Uuid)
+
+#undef CREATE_SIMPLE_TYPE_FUNCTION
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline TLogicalTypePtr Yson()
+{
+ return SimpleLogicalType(ESimpleLogicalValueType::Any);
+}
+
+inline TLogicalTypePtr Bool()
+{
+ return SimpleLogicalType(ESimpleLogicalValueType::Boolean);
+}
+
+inline TLogicalTypePtr Optional(const TLogicalTypePtr& element)
+{
+ return OptionalLogicalType(element);
+}
+
+inline TLogicalTypePtr Decimal(int precision, int scale)
+{
+ return DecimalLogicalType(precision, scale);
+}
+
+inline TLogicalTypePtr List(const TLogicalTypePtr& element)
+{
+ return ListLogicalType(element);
+}
+
+template <typename... T>
+inline TLogicalTypePtr Tuple(const T&... args)
+{
+ return TupleLogicalType(std::vector<TLogicalTypePtr>{args...});
+}
+
+namespace NPrivate {
+inline void StructFieldList(std::vector<TStructField>* /*fields*/)
+{ }
+
+template <typename... T>
+inline void StructFieldList(
+ std::vector<TStructField>* fields,
+ const TString& name,
+ const TLogicalTypePtr& type,
+ const T&... args)
+{
+ fields->push_back({name, type});
+ StructFieldList(fields, args...);
+}
+
+} // namespace NPrivate
+
+template <typename... T>
+inline TLogicalTypePtr Struct(const T&... args)
+{
+ std::vector<TStructField> fields;
+ NPrivate::StructFieldList(&fields, args...);
+ return StructLogicalType(fields);
+}
+
+template <typename... T>
+inline TLogicalTypePtr VariantTuple(const T&... args)
+{
+ return VariantTupleLogicalType(std::vector<TLogicalTypePtr>{args...});
+}
+
+template <typename... T>
+inline TLogicalTypePtr VariantStruct(const T&... args)
+{
+ std::vector<TStructField> fields;
+ NPrivate::StructFieldList(&fields, args...);
+ return VariantStructLogicalType(fields);
+}
+
+inline TLogicalTypePtr Dict(const TLogicalTypePtr& key, const TLogicalTypePtr& value)
+{
+ return DictLogicalType(key, value);
+}
+
+inline TLogicalTypePtr Tagged(TString tag, const TLogicalTypePtr& element)
+{
+ return TaggedLogicalType(std::move(tag), element);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient::NLogicalTypeShortcuts
diff --git a/yt/yt/client/unittests/logical_type_ut.cpp b/yt/yt/client/unittests/logical_type_ut.cpp
new file mode 100644
index 0000000000..0cf744313e
--- /dev/null
+++ b/yt/yt/client/unittests/logical_type_ut.cpp
@@ -0,0 +1,1059 @@
+#include "logical_type_shortcuts.h"
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <library/cpp/resource/resource.h>
+
+#include <util/string/split.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TLogicalTypeTest, TestCastToV1Type)
+{
+ using namespace NLogicalTypeShortcuts;
+
+ EXPECT_EQ(
+ CastToV1Type(Int64()),
+ std::pair(ESimpleLogicalValueType::Int64, true));
+
+ EXPECT_EQ(
+ CastToV1Type(Optional(Int64())),
+ std::pair(ESimpleLogicalValueType::Int64, false));
+
+ EXPECT_EQ(
+ CastToV1Type(Optional(Optional(Int64()))),
+ std::pair(ESimpleLogicalValueType::Any, false));
+
+ EXPECT_EQ(
+ CastToV1Type(Null()),
+ std::pair(ESimpleLogicalValueType::Null, false));
+
+ EXPECT_EQ(
+ CastToV1Type(Optional(Null())),
+ std::pair(ESimpleLogicalValueType::Any, false));
+
+ EXPECT_EQ(
+ CastToV1Type(List(Int64())),
+ std::pair(ESimpleLogicalValueType::Any, true));
+
+ EXPECT_EQ(
+ CastToV1Type(Struct("value", Int64())),
+ std::pair(ESimpleLogicalValueType::Any, true));
+
+ EXPECT_EQ(
+ CastToV1Type(Tuple(Int64(), Uint64())),
+ std::pair(ESimpleLogicalValueType::Any, true));
+
+ EXPECT_EQ(
+ CastToV1Type(Dict(String(), Uint64())),
+ std::pair(ESimpleLogicalValueType::Any, true));
+
+ EXPECT_EQ(
+ CastToV1Type(Tagged("foo", String())),
+ std::pair(ESimpleLogicalValueType::String, true));
+
+ EXPECT_EQ(
+ CastToV1Type(Tagged("foo", Optional(String()))),
+ std::pair(ESimpleLogicalValueType::String, false));
+
+ EXPECT_EQ(
+ CastToV1Type(Tagged("foo", Optional(Optional(String())))),
+ std::pair(ESimpleLogicalValueType::Any, false));
+
+ EXPECT_EQ(
+ CastToV1Type(TaggedLogicalType("foo", List(String()))),
+ std::pair(ESimpleLogicalValueType::Any, true));
+
+ EXPECT_EQ(
+ CastToV1Type(DecimalLogicalType(3, 2)),
+ std::pair(ESimpleLogicalValueType::String, true));
+}
+
+TEST(TLogicalTypeTest, DictValidationTest)
+{
+ EXPECT_NO_THROW(DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))
+ ));
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("example_column", DictLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any)),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ ))),
+ "is not allowed in dict key");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("example_column", DictLogicalType(
+ ListLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ ))),
+ "is not allowed in dict key");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("example_column", DictLogicalType(
+ StructLogicalType({
+ {"foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"bar", SimpleLogicalType(ESimpleLogicalValueType::Any)},
+ }),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ ))),
+ "is not allowed in dict key");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("example_column", DictLogicalType(
+ VariantStructLogicalType({
+ {"foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"bar", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))},
+ }),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ ))),
+ "is not allowed in dict key");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("example_column", DictLogicalType(
+ TaggedLogicalType("bar", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ ))),
+ "is not allowed in dict key");
+ EXPECT_NO_THROW(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("example_column", DictLogicalType(
+ TaggedLogicalType("bar", SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ ))));
+
+ EXPECT_NO_THROW(
+ ValidateLogicalType(
+ TComplexTypeFieldDescriptor("example_column",
+ DictLogicalType(
+ DecimalLogicalType(3, 2),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ )
+ )
+ ));
+}
+
+TEST(TLogicalTypeTest, TestDetag)
+{
+ EXPECT_EQ(
+ *DetagLogicalType(
+ TaggedLogicalType("tag", SimpleLogicalType(ESimpleLogicalValueType::String))
+ ),
+ *SimpleLogicalType(ESimpleLogicalValueType::String)
+ );
+ EXPECT_EQ(
+ *DetagLogicalType(
+ TaggedLogicalType("tag",
+ StructLogicalType({
+ {"list", TaggedLogicalType("tag2", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int8)))},
+ {"tuple", TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Double),
+ TaggedLogicalType("tag3", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))),
+ })
+ },
+ })
+ )
+ ),
+ *StructLogicalType({
+ {"list", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int8))},
+ {"tuple", TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Double),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)),
+ })
+ },
+ })
+ );
+}
+
+
+static const std::vector<TLogicalTypePtr> ComplexTypeExampleList = {
+ // Simple types
+ SimpleLogicalType(ESimpleLogicalValueType::Null),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ SimpleLogicalType(ESimpleLogicalValueType::Utf8),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+
+
+ // Decimals
+ DecimalLogicalType(1, 0),
+ DecimalLogicalType(1, 1),
+ DecimalLogicalType(3, 2),
+ DecimalLogicalType(35, 0),
+ DecimalLogicalType(35, 35),
+ OptionalLogicalType(DecimalLogicalType(1, 0)),
+ OptionalLogicalType(DecimalLogicalType(1, 1)),
+ OptionalLogicalType(DecimalLogicalType(3, 2)),
+ OptionalLogicalType(DecimalLogicalType(35, 0)),
+ OptionalLogicalType(DecimalLogicalType(35, 35)),
+
+ // Optionals
+ OptionalLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Utf8))),
+ OptionalLogicalType(
+ ListLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Utf8)))),
+
+ // Lists
+ ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Utf8)),
+ ListLogicalType(
+ ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Utf8))),
+ ListLogicalType(
+ ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::String))),
+
+ // Structs
+ StructLogicalType({
+ {"key", SimpleLogicalType(ESimpleLogicalValueType::Utf8)},
+ {"value", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Utf8))},
+ }),
+ StructLogicalType({
+ {"value", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Utf8))},
+ {"key", SimpleLogicalType(ESimpleLogicalValueType::Utf8)},
+ }),
+ StructLogicalType({
+ {"key", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"value", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ }),
+
+ // Tuples
+ TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ }),
+ TupleLogicalType({
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ }),
+ TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ }),
+
+ // VariantTuple
+ VariantTupleLogicalType(
+ {
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ }),
+ VariantTupleLogicalType(
+ {
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ }),
+ VariantTupleLogicalType(
+ {
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ }),
+
+ // VariantStruct
+ VariantStructLogicalType(
+ {
+ {"string", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"int", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ }),
+ VariantStructLogicalType(
+ {
+ {"int", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"string", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }),
+ VariantStructLogicalType(
+ {
+ {"string", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"string", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }),
+
+ // Dict
+ DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ ),
+ DictLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ ),
+ DictLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null)),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))
+ ),
+
+ // Tagged
+ TaggedLogicalType("foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ TaggedLogicalType("bar", SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ TaggedLogicalType("foo", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+ TaggedLogicalType("foo", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+};
+
+TEST(TLogicalTypeTest, TestAllTypesAreInExamples)
+{
+ auto allMetatypes = TEnumTraits<ELogicalMetatype>::GetDomainValues();
+ std::set<ELogicalMetatype> actualMetatypes;
+ for (const auto& example : ComplexTypeExampleList) {
+ actualMetatypes.insert(example->GetMetatype());
+ }
+ // This test checks that we have all top level metatypes in our ComplexTypeExampleList
+ // and therefore all the tests that use this example list cover all all complex metatypes.
+ EXPECT_EQ(actualMetatypes, std::set<ELogicalMetatype>(allMetatypes.begin(), allMetatypes.end()));
+}
+
+TEST(TLogicalTypeTest, TestAllExamplesHaveDifferentHash)
+{
+ std::map<int, TLogicalTypePtr> hashToType;
+ auto hashFunction = THash<TLogicalType>();
+ for (const auto& example : ComplexTypeExampleList) {
+ auto hash = hashFunction(*example);
+ auto it = hashToType.find(hash);
+ if (it != hashToType.end()) {
+ ADD_FAILURE() << Format(
+ "Type %Qv and %Qv have the same hash %v",
+ *it->second,
+ *example,
+ hash);
+ } else {
+ hashToType[hash] = example;
+ }
+ }
+}
+
+TEST(TLogicalTypeTest, TestAllExamplesAreNotEqual)
+{
+ // Hopefully our example list will never be so big that n^2 complexity is a problem.
+ for (const auto& lhs : ComplexTypeExampleList) {
+ for (const auto& rhs : ComplexTypeExampleList) {
+ if (&lhs == &rhs) {
+ EXPECT_EQ(*lhs, *rhs);
+ } else {
+ EXPECT_NE(*lhs, *rhs);
+ }
+ }
+ }
+}
+
+TEST(TLogicalTypeTest, TestBadProtobufDeserialization)
+{
+ NProto::TLogicalType proto;
+
+ TLogicalTypePtr deserializedType;
+ EXPECT_THROW_WITH_SUBSTRING(
+ FromProto(&deserializedType, proto),
+ "Cannot parse unknown logical type from proto");
+}
+
+TEST(TLogicalTypeTest, TestIsComparable) {
+ EXPECT_TRUE(IsComparable(
+ OptionalLogicalType(
+ OptionalLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ )
+ )
+ ));
+
+ EXPECT_TRUE(IsComparable(
+ ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ )
+ ));
+
+ EXPECT_TRUE(IsComparable(
+ TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ })
+ ));
+
+ EXPECT_FALSE(IsComparable(
+ StructLogicalType({
+ {"foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"bar", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ })
+ ));
+
+ EXPECT_FALSE(IsComparable(
+ DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String)
+ )
+ ));
+
+ EXPECT_FALSE(IsComparable(
+ VariantStructLogicalType({
+ {"foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"bar", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ })
+ ));
+
+ EXPECT_TRUE(IsComparable(
+ VariantTupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ })
+ ));
+
+ EXPECT_TRUE(IsComparable(DecimalLogicalType(3, 2)));
+}
+
+TString CanonizeYsonString(TString input)
+{
+ auto node = ConvertToNode(TYsonString(input));
+ auto binaryYson = ConvertToYsonString(node);
+
+ TStringStream out;
+ {
+ TYsonWriter writer(&out, EYsonFormat::Pretty);
+ ParseYsonStringBuffer(binaryYson.AsStringBuf(), EYsonType::Node, &writer);
+ }
+ return out.Str();
+}
+
+TString ToTypeV3(const TLogicalTypePtr& logicalType)
+{
+ TTypeV3LogicalTypeWrapper w{logicalType};
+ auto ysonString = ConvertToYsonString(w);
+ return CanonizeYsonString(ysonString.ToString());
+}
+
+class TLogicalTypeYson
+ : public ::testing::TestWithParam<bool>
+{
+public:
+ TLogicalTypePtr FromTypeV3(TString yson)
+ {
+ const auto parseFromNode = GetParam();
+ const auto ysonStr = TYsonStringBuf(yson, EYsonType::Node);
+ if (parseFromNode) {
+ auto wrapper = ConvertTo<TTypeV3LogicalTypeWrapper>(ConvertTo<INodePtr>(ysonStr));
+ return wrapper.LogicalType;
+ } else {
+ auto wrapper = ConvertTo<TTypeV3LogicalTypeWrapper>(ysonStr);
+ return wrapper.LogicalType;
+ }
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ ParseFromNode,
+ TLogicalTypeYson,
+ ::testing::Values(true));
+
+INSTANTIATE_TEST_SUITE_P(
+ ParseFromCursor,
+ TLogicalTypeYson,
+ ::testing::Values(false));
+
+#define CHECK_TYPE_V3(type, expectedYson) \
+ do { \
+ EXPECT_EQ(ToTypeV3(type), CanonizeYsonString(expectedYson)); \
+ EXPECT_EQ(*type, *FromTypeV3(expectedYson)); \
+} while(0)
+
+TEST_P(TLogicalTypeYson, TestTypeV3Yson)
+{
+ // Ill formed.
+ EXPECT_THROW_THAT(FromTypeV3("{}"), ::testing::AnyOf(
+ ::testing::HasSubstr("has no child with key"),
+ ::testing::HasSubstr("is required")));
+
+ // Simple types.
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Int64), "int64");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Int64), *FromTypeV3("{type_name=int64;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Uint64), "uint64");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Uint64), *FromTypeV3("{type_name=uint64;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Double), "double");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Double), *FromTypeV3("{type_name=double;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Float), "float");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Float), *FromTypeV3("{type_name=float;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Boolean), "bool");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Boolean), *FromTypeV3("{type_name=bool;}"));
+ EXPECT_THROW_WITH_SUBSTRING(FromTypeV3("boolean"), "is not valid type_v3 simple type");
+ EXPECT_THROW_WITH_SUBSTRING(FromTypeV3("{type_name=boolean}"), "is not valid type_v3 simple type");
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::String), "string");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::String), *FromTypeV3("{type_name=string;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Null), "null");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Null), *FromTypeV3("{type_name=null;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Any), "yson");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Any), *FromTypeV3("{type_name=yson;}"));
+ EXPECT_THROW_WITH_SUBSTRING(FromTypeV3("{type_name=any}"), "is not valid type_v3 simple type");
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Int32), "int32");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Int32), *FromTypeV3("{type_name=int32;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Int16), "int16");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Int16), *FromTypeV3("{type_name=int16;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Int8), "int8");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Int8), *FromTypeV3("{type_name=int8;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Uint32), "uint32");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Uint32), *FromTypeV3("{type_name=uint32;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Uint16), "uint16");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Uint16), *FromTypeV3("{type_name=uint16;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Uint8), "uint8");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Uint8), *FromTypeV3("{type_name=uint8;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Utf8), "utf8");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Utf8), *FromTypeV3("{type_name=utf8;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Date), "date");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Date), *FromTypeV3("{type_name=date;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Datetime), "datetime");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Datetime), *FromTypeV3("{type_name=datetime;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Timestamp), "timestamp");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Timestamp), *FromTypeV3("{type_name=timestamp;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Interval), "interval");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Interval), *FromTypeV3("{type_name=interval;}"));
+
+ CHECK_TYPE_V3(SimpleLogicalType(ESimpleLogicalValueType::Json), "json");
+ EXPECT_EQ(*SimpleLogicalType(ESimpleLogicalValueType::Json), *FromTypeV3("{type_name=json;}"));
+
+ // Optional.
+ CHECK_TYPE_V3(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Boolean)),
+ "{type_name=optional; item=bool}");
+
+ CHECK_TYPE_V3(
+ OptionalLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))),
+ R"(
+ {
+ type_name=optional;
+ item={
+ type_name=optional;
+ item=yson;
+ }
+ }
+ )");
+
+ EXPECT_THROW_THAT(FromTypeV3("{type_name=optional}"), ::testing::AnyOf(
+ ::testing::HasSubstr("has no child with key"),
+ ::testing::HasSubstr("is required")));
+
+ // List
+ CHECK_TYPE_V3(
+ ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Boolean)),
+ "{type_name=list; item=bool}");
+
+ CHECK_TYPE_V3(
+ ListLogicalType(ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))),
+ R"(
+ {
+ type_name=list;
+ item={
+ type_name=list;
+ item=yson;
+ }
+ }
+ )");
+
+ EXPECT_THROW_THAT(FromTypeV3("{type_name=list}"), ::testing::AnyOf(
+ ::testing::HasSubstr("has no child with key"),
+ ::testing::HasSubstr("is required")));
+
+ // Struct
+ CHECK_TYPE_V3(
+ StructLogicalType({
+ {"a", SimpleLogicalType(ESimpleLogicalValueType::Boolean)},
+ {"b", StructLogicalType({
+ { "foo", SimpleLogicalType(ESimpleLogicalValueType::Boolean) },
+ })},
+ }),
+ R"(
+ {
+ type_name=struct;
+ members=[
+ {name=a; type=bool};
+ {
+ name=b;
+ type={
+ type_name=struct;
+ members=[{name=foo;type=bool}];
+ };
+ };
+ ];
+ }
+ )");
+
+ // Tuple
+ CHECK_TYPE_V3(
+ TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean),
+ TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean)
+ })
+ }),
+ R"(
+ {
+ type_name=tuple;
+ elements=[
+ {type=bool};
+ {
+ type={
+ type_name=tuple;
+ elements=[{type=bool}];
+ };
+ };
+ ];
+ }
+ )");
+
+ // VariantStruct
+ CHECK_TYPE_V3(
+ VariantStructLogicalType({
+ {"a", SimpleLogicalType(ESimpleLogicalValueType::Boolean)},
+ {"b", VariantStructLogicalType({
+ { "foo", SimpleLogicalType(ESimpleLogicalValueType::Boolean) },
+ })},
+ }),
+ R"(
+ {
+ type_name=variant;
+ members=[
+ {name=a; type=bool};
+ {
+ name=b;
+ type={
+ type_name=variant;
+ members=[{name=foo;type=bool}];
+ };
+ };
+ ];
+ }
+ )");
+
+ // Tuple
+ CHECK_TYPE_V3(
+ VariantTupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean),
+ VariantTupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean)
+ })
+ }),
+ R"(
+ {
+ type_name=variant;
+ elements=[
+ {type=bool};
+ {
+ type={
+ type_name=variant;
+ elements=[{type=bool}];
+ };
+ };
+ ];
+ }
+ )");
+
+ // Dict
+ CHECK_TYPE_V3(
+ DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean),
+ DictLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Boolean)),
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean)
+ )
+ ),
+ R"(
+ {
+ type_name=dict;
+ key=bool;
+ value={
+ type_name=dict;
+ key={type_name=optional; item=bool};
+ value=bool;
+ }
+ }
+ )");
+
+ // Tagged
+ CHECK_TYPE_V3(
+ TaggedLogicalType(
+ "foo",
+ TaggedLogicalType(
+ "bar",
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean)
+ )
+ ),
+ R"(
+ {
+ type_name=tagged;
+ tag=foo;
+ item={
+ type_name=tagged;
+ tag=bar;
+ item=bool;
+ }
+ }
+ )");
+
+ // Decimal
+ CHECK_TYPE_V3(
+ DecimalLogicalType(3, 2),
+ R"(
+ {
+ type_name=decimal;
+ precision=3;
+ scale=2;
+ }
+ )");
+}
+
+class TLogicalTypeTestExamples
+ : public ::testing::TestWithParam<TLogicalTypePtr>
+{ };
+
+TEST_P(TLogicalTypeTestExamples, TestProtoSerialization)
+{
+ auto type = GetParam();
+
+ NProto::TLogicalType proto;
+ ToProto(&proto, type);
+
+ TLogicalTypePtr deserializedType;
+ FromProto(&deserializedType, proto);
+
+ EXPECT_EQ(*type, *deserializedType);
+}
+
+TEST_P(TLogicalTypeTestExamples, TestYsonSerialization)
+{
+ auto type = GetParam();
+ auto yson = ConvertToYsonString(type);
+ auto deserializedType = ConvertTo<TLogicalTypePtr>(yson);
+ EXPECT_EQ(*type, *deserializedType);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Examples,
+ TLogicalTypeTestExamples,
+ ::testing::ValuesIn(ComplexTypeExampleList));
+
+using TCombineTypeFunc = std::function<TLogicalTypePtr(const TLogicalTypePtr&)>;
+
+std::vector<std::pair<TString, TCombineTypeFunc>> CombineFunctions = {
+ {
+ "optional",
+ [](const TLogicalTypePtr& type) {
+ return OptionalLogicalType(type);
+ },
+ },
+ {
+ "list",
+ [](const TLogicalTypePtr& type) {
+ return ListLogicalType(type);
+ },
+ },
+ {
+ "struct",
+ [](const TLogicalTypePtr& type) {
+ return StructLogicalType({{"field", type}});
+ },
+ },
+ {
+ "tuple",
+ [](const TLogicalTypePtr& type) {
+ return TupleLogicalType({type});
+ },
+ },
+ {
+ "variant_struct",
+ [](const TLogicalTypePtr& type) {
+ return VariantStructLogicalType({{"field", type}});
+ },
+ },
+ {
+ "variant_tuple",
+ [](const TLogicalTypePtr& type) {
+ return VariantTupleLogicalType({type});
+ },
+ },
+ {
+ "dict",
+ [](const TLogicalTypePtr& type) {
+ return DictLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String), type);
+ },
+ },
+ {
+ "dict-key",
+ [](const TLogicalTypePtr& type) {
+ return DictLogicalType(type, SimpleLogicalType(ESimpleLogicalValueType::String));
+ },
+ },
+ {
+ "dict-value",
+ [](const TLogicalTypePtr& type) {
+ return DictLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String), type);
+ }
+ },
+ {
+ "tagged",
+ [](const TLogicalTypePtr& type) {
+ return TaggedLogicalType("foo", type);
+ }
+ }
+};
+
+TEST(TLogicalTypeTest, TestAllTypesInCombineFunctions)
+{
+ const auto allMetatypes = TEnumTraits<ELogicalMetatype>::GetDomainValues();
+ std::set<ELogicalMetatype> actualMetatypes;
+ for (const auto& [name, function] : CombineFunctions) {
+ auto combined = function(SimpleLogicalType(ESimpleLogicalValueType::Int64));
+ actualMetatypes.insert(combined->GetMetatype());
+ }
+ std::set<ELogicalMetatype> expectedMetatypes(allMetatypes.begin(), allMetatypes.end());
+ expectedMetatypes.erase(ELogicalMetatype::Simple);
+ expectedMetatypes.erase(ELogicalMetatype::Decimal);
+ EXPECT_EQ(actualMetatypes, expectedMetatypes);
+}
+
+class TCombineLogicalMetatypeTests
+ : public ::testing::TestWithParam<std::pair<TString,TCombineTypeFunc>>
+{ };
+
+INSTANTIATE_TEST_SUITE_P(
+ CombineFunctions,
+ TCombineLogicalMetatypeTests,
+ ::testing::ValuesIn(CombineFunctions));
+
+TEST_P(TCombineLogicalMetatypeTests, TestValidateStruct)
+{
+ auto badType = StructLogicalType({{"", SimpleLogicalType(ESimpleLogicalValueType::Int64)}});
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", badType)),
+ "Name of struct field #0 is empty");
+
+ const auto& [combineName, combineFunc] = GetParam();
+ const auto combinedType1 = combineFunc(badType);
+ const auto combinedType2 = combineFunc(combinedType1);
+ EXPECT_NE(*combinedType1, *badType);
+ EXPECT_NE(*combinedType1, *combinedType2);
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", combinedType1)),
+ "Name of struct field #0 is empty");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", combinedType2)),
+ "Name of struct field #0 is empty");
+}
+
+TEST_P(TCombineLogicalMetatypeTests, TestValidateAny)
+{
+ const auto& [combineName, combineFunc] = GetParam();
+
+ if (combineName == "optional" || combineName == "dict-key") {
+ // Skip tests for these combiners.
+ return;
+ }
+
+ auto badType = SimpleLogicalType(ESimpleLogicalValueType::Any);
+ auto goodType = OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any));
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", badType)),
+ "is disallowed outside of optional");
+ EXPECT_NO_THROW(ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", goodType)));
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", combineFunc(badType))),
+ "is disallowed outside of optional");
+ EXPECT_NO_THROW(ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", combineFunc(goodType))));
+}
+
+TEST_P(TCombineLogicalMetatypeTests, TestTrivialDetag)
+{
+ using namespace NLogicalTypeShortcuts;
+
+ const auto& [combineName, combineFunc] = GetParam();
+ if (combineName == "tagged") {
+ // Skip test for this combiner.
+ return;
+ }
+ const auto& logicalType = Utf8();
+
+ const auto combinedType = combineFunc(logicalType);
+
+ const auto detaggedType = DetagLogicalType(combinedType);
+ EXPECT_EQ(*detaggedType, *combinedType);
+ EXPECT_EQ(detaggedType.Get(), combinedType.Get());
+}
+
+TEST_P(TCombineLogicalMetatypeTests, TestNonTrivialDetag)
+{
+ using namespace NLogicalTypeShortcuts;
+
+ const auto& [combineName, combineFunc] = GetParam();
+ if (combineName == "tagged") {
+ // Skip test for this combiner.
+ return;
+ }
+ const auto& logicalType = TaggedLogicalType("foo", Utf8());
+
+ const auto combinedType = combineFunc(logicalType);
+
+ const auto detaggedType = DetagLogicalType(combinedType);
+ const auto expectedDetaggedType = combineFunc(Utf8());
+
+ EXPECT_EQ(*expectedDetaggedType, *detaggedType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TStructValidationTest
+ : public ::testing::TestWithParam<ELogicalMetatype>
+{ };
+
+INSTANTIATE_TEST_SUITE_P(
+ Tests,
+ TStructValidationTest,
+ ::testing::ValuesIn({ELogicalMetatype::Struct, ELogicalMetatype::VariantStruct}));
+
+TEST_P(TStructValidationTest, Test)
+{
+ auto metatype = GetParam();
+ auto validate = [&] (const std::vector<TStructField>& fields) {
+ std::function<TLogicalTypePtr(std::vector<TStructField> fields)> typeConstructor;
+ if (metatype == ELogicalMetatype::Struct) {
+ typeConstructor = StructLogicalType;
+ } else {
+ YT_VERIFY(metatype == ELogicalMetatype::VariantStruct);
+ typeConstructor = VariantStructLogicalType;
+ }
+ ValidateLogicalType(TComplexTypeFieldDescriptor("test-column", typeConstructor(fields)));
+ };
+
+ EXPECT_NO_THROW(validate({}));
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ validate({{"", SimpleLogicalType(ESimpleLogicalValueType::Int64)}}),
+ "Name of struct field #0 is empty");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ validate({
+ {"a", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"a", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ }),
+ "Struct field name \"a\" is used twice");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ validate({
+ {TString(257, 'a'), SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ }),
+ "Name of struct field #0 exceeds limit");
+ EXPECT_THROW_WITH_SUBSTRING(
+ validate({
+ {"\xFF", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ }),
+ "Name of struct field #0 is not valid utf8");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::vector<std::vector<TString>> ParseData(TStringBuf data, int expectedFieldsCount) {
+ TString noComments;
+ {
+ TMemoryInput in(data);
+ TString line;
+ while (in.ReadLine(line)) {
+ if (StripString(line).StartsWith('#')) {
+ continue;
+ }
+ noComments += line;
+ }
+ }
+
+ std::vector<std::vector<TString>> result;
+ for (TStringBuf record : StringSplitter(noComments).SplitByString(";;")) {
+ record = StripString(record);
+ if (record.Empty()) {
+ continue;
+ }
+ std::vector<TString> fields;
+ for (TStringBuf field : StringSplitter(record).SplitByString("::")) {
+ fields.emplace_back(StripString(field));
+ }
+ if (static_cast<int>(fields.size()) != expectedFieldsCount) {
+ ythrow yexception() << "Unexpected field count expected: " << expectedFieldsCount << " actual: " << fields.size();
+ }
+ result.push_back(fields);
+ }
+ return result;
+}
+
+TEST(TTestLogicalTypesWithData, GoodTypes)
+{
+ auto records = ParseData(NResource::Find("/types/good"), 2);
+
+ for (const auto& record : records) {
+ const auto& typeYson = record.at(0);
+ // TODO(levysotsky): Remove when tz_* types are supported.
+ if (typeYson.Contains("tz_")) {
+ continue;
+ }
+ const auto& typeText = record.at(1);
+ TString context = Format("text: %v\nyson: %v\n", typeText, typeYson);
+ auto wrapError = [&] (const std::exception& ex) {
+ return yexception() << "Unexpected error: " << ex.what() << '\n' << context;
+ };
+
+ TLogicalTypePtr type;
+ try {
+ type = ConvertTo<TLogicalTypePtr>(TYsonStringBuf(typeYson));
+ } catch (const std::exception& ex) {
+ ythrow wrapError(ex);
+ }
+
+ auto serialized = ConvertToYsonString(type);
+ TLogicalTypePtr type2;
+ try {
+ type2 = ConvertTo<TLogicalTypePtr>(serialized);
+ } catch (const std::exception& ex) {
+ ythrow wrapError(ex);
+ }
+
+ EXPECT_EQ(*type, *type2);
+ }
+}
+
+TEST(TTestLogicalTypesWithData, BadTypes)
+{
+ auto records = ParseData(NResource::Find("/types/bad"), 3);
+
+ for (const auto& record : records) {
+ const auto& typeYson = record.at(0);
+ // TODO(levysotsky): Remove when tz_* types are supported.
+ if (typeYson.Contains("tz_")) {
+ continue;
+ }
+
+ EXPECT_THROW(ConvertTo<TLogicalTypePtr>(TYsonStringBuf(typeYson)), TErrorException);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/mock/client.h b/yt/yt/client/unittests/mock/client.h
new file mode 100644
index 0000000000..0714651e81
--- /dev/null
+++ b/yt/yt/client/unittests/mock/client.h
@@ -0,0 +1,617 @@
+#pragma once
+
+#include <yt/yt/client/api/connection.h>
+#include <yt/yt/client/api/client.h>
+#include <yt/yt/client/api/file_writer.h>
+#include <yt/yt/client/api/journal_reader.h>
+#include <yt/yt/client/api/journal_writer.h>
+#include <yt/yt/client/api/transaction.h>
+
+#include <yt/yt/client/chaos_client/replication_card_cache.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/client/tablet_client/table_mount_cache.h>
+
+#include <yt/yt/client/transaction_client/timestamp_provider.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <library/cpp/testing/gtest_extensions/gtest_extensions.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMockClient
+ : public IClient
+{
+public:
+ MOCK_METHOD(IConnectionPtr, GetConnection, (), (override));
+
+ MOCK_METHOD(std::optional<TStringBuf>, GetClusterName, (bool fetchIfNull), (override));
+
+ MOCK_METHOD(TFuture<ITransactionPtr>, StartTransaction, (
+ NTransactionClient::ETransactionType type,
+ const TTransactionStartOptions& options), (override));
+
+ MOCK_METHOD(TFuture<IUnversionedRowsetPtr>, LookupRows, (
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TLookupRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<IVersionedRowsetPtr>, VersionedLookupRows, (
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TVersionedLookupRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<IUnversionedRowsetPtr>>, MultiLookup, (
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TSelectRowsResult>, SelectRows, (
+ const TString& query,
+ const TSelectRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NQueueClient::IQueueRowsetPtr>, PullQueue, (
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullQueueOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NQueueClient::IQueueRowsetPtr>, PullConsumer, (
+ const NYPath::TRichYPath& consumerPath,
+ const NYPath::TRichYPath& queuePath,
+ i64 offset,
+ int partitionIndex,
+ const NQueueClient::TQueueRowBatchReadOptions& rowBatchReadOptions,
+ const TPullConsumerOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, RegisterQueueConsumer, (
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ bool vital,
+ const TRegisterQueueConsumerOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, UnregisterQueueConsumer, (
+ const NYPath::TRichYPath& queuePath,
+ const NYPath::TRichYPath& consumerPath,
+ const TUnregisterQueueConsumerOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<TListQueueConsumerRegistrationsResult>>, ListQueueConsumerRegistrations, (
+ const std::optional<NYPath::TRichYPath>& queuePath,
+ const std::optional<NYPath::TRichYPath>& consumerPath,
+ const TListQueueConsumerRegistrationsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, ExplainQuery, (
+ const TString& query,
+ const TExplainQueryOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TPullRowsResult>, PullRows, (
+ const NYPath::TYPath& path,
+ const TPullRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<ITableReaderPtr>, CreateTableReader, (
+ const NYPath::TRichYPath& path,
+ const TTableReaderOptions& options), (override));
+
+ MOCK_METHOD(TFuture<ITableWriterPtr>, CreateTableWriter, (
+ const NYPath::TRichYPath& path,
+ const TTableWriterOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, GetNode, (
+ const NYPath::TYPath& path,
+ const TGetNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SetNode, (
+ const NYPath::TYPath& path,
+ const NYson::TYsonString& value,
+ const TSetNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, MultisetAttributesNode, (
+ const NYPath::TYPath& path,
+ const NYTree::IMapNodePtr& attributes,
+ const TMultisetAttributesNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, RemoveNode, (
+ const NYPath::TYPath& path,
+ const TRemoveNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, ListNode, (
+ const NYPath::TYPath& path,
+ const TListNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, CreateNode, (
+ const NYPath::TYPath& path,
+ NObjectClient::EObjectType type,
+ const TCreateNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TLockNodeResult>, LockNode, (
+ const NYPath::TYPath& path,
+ NCypressClient::ELockMode mode,
+ const TLockNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, UnlockNode, (
+ const NYPath::TYPath& path,
+ const TUnlockNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, CopyNode, (
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TCopyNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, MoveNode, (
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TMoveNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, LinkNode, (
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TLinkNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ConcatenateNodes, (
+ const std::vector<NYPath::TRichYPath>& srcPaths,
+ const NYPath::TRichYPath& dstPath,
+ const TConcatenateNodesOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ExternalizeNode, (
+ const NYPath::TYPath& path,
+ NObjectClient::TCellTag cellTag,
+ const TExternalizeNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, InternalizeNode, (
+ const NYPath::TYPath& path,
+ const TInternalizeNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<bool>, NodeExists, (
+ const NYPath::TYPath& path,
+ const TNodeExistsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NObjectClient::TObjectId>, CreateObject, (
+ NObjectClient::EObjectType type,
+ const TCreateObjectOptions& options), (override));
+
+ MOCK_METHOD(TFuture<IFileReaderPtr>, CreateFileReader, (
+ const NYPath::TYPath& path,
+ const TFileReaderOptions& options), (override));
+
+ MOCK_METHOD(IFileWriterPtr, CreateFileWriter, (
+ const NYPath::TRichYPath& path,
+ const TFileWriterOptions& options), (override));
+
+ MOCK_METHOD(IJournalReaderPtr, CreateJournalReader, (
+ const NYPath::TYPath& path,
+ const TJournalReaderOptions& options), (override));
+
+ MOCK_METHOD(IJournalWriterPtr, CreateJournalWriter, (
+ const NYPath::TYPath& path,
+ const TJournalWriterOptions& options), (override));
+
+ MOCK_METHOD(TFuture<int>, BuildSnapshot, (const TBuildSnapshotOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TCellIdToSnapshotIdMap>, BuildMasterSnapshots, (
+ const TBuildMasterSnapshotsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SwitchLeader, (
+ NHydra::TCellId cellId,
+ const TString& newLeaderAddress,
+ const TSwitchLeaderOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ResetStateHash, (
+ NHydra::TCellId cellId,
+ const TResetStateHashOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, GCCollect, (
+ const TGCCollectOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, KillProcess, (
+ const TString& address,
+ const TKillProcessOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TString>, WriteCoreDump, (
+ const TString& address,
+ const TWriteCoreDumpOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TGuid>, WriteLogBarrier, (
+ const TString& address,
+ const TWriteLogBarrierOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TString>, WriteOperationControllerCoreDump, (
+ NJobTrackerClient::TOperationId operationId,
+ const TWriteOperationControllerCoreDumpOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, HealExecNode, (
+ const TString& address,
+ const THealExecNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SuspendCoordinator, (
+ NObjectClient::TCellId coordinatorCellid,
+ const TSuspendCoordinatorOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ResumeCoordinator, (
+ NObjectClient::TCellId coordinatorCellid,
+ const TResumeCoordinatorOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, MigrateReplicationCards, (
+ NObjectClient::TCellId chaosCellid,
+ const TMigrateReplicationCardsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SuspendChaosCells, (
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendChaosCellsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ResumeChaosCells, (
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeChaosCellsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SuspendTabletCells, (
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TSuspendTabletCellsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ResumeTabletCells, (
+ const std::vector<NObjectClient::TCellId>& cellIds,
+ const TResumeTabletCellsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TMaintenanceId>, AddMaintenance, (
+ EMaintenanceComponent component,
+ const TString& address,
+ EMaintenanceType type,
+ const TString& comment,
+ const TAddMaintenanceOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TMaintenanceCounts>, RemoveMaintenance, (
+ EMaintenanceComponent component,
+ const TString& address,
+ const TMaintenanceFilter& filter,
+ const TRemoveMaintenanceOptions& options), (override));
+
+ // IClient
+ NTabletClient::ITableMountCachePtr TableMountCache;
+ NTransactionClient::ITimestampProviderPtr TimestampProvider;
+
+ MOCK_METHOD(void, Terminate, (), (override));
+ MOCK_METHOD(const NChaosClient::IReplicationCardCachePtr&, GetReplicationCardCache, (), (override));
+
+ const NTabletClient::ITableMountCachePtr& GetTableMountCache() override
+ {
+ return TableMountCache;
+ }
+ const NTransactionClient::ITimestampProviderPtr& GetTimestampProvider() override
+ {
+ return TimestampProvider;
+ }
+
+ MOCK_METHOD(ITransactionPtr, AttachTransaction, (
+ NTransactionClient::TTransactionId transactionId,
+ const TTransactionAttachOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, MountTable, (
+ const NYPath::TYPath& path,
+ const TMountTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, UnmountTable, (
+ const NYPath::TYPath& path,
+ const TUnmountTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, RemountTable, (
+ const NYPath::TYPath& path,
+ const TRemountTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, FreezeTable, (
+ const NYPath::TYPath& path,
+ const TFreezeTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, UnfreezeTable, (
+ const NYPath::TYPath& path,
+ const TUnfreezeTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ReshardTable, (
+ const NYPath::TYPath& path,
+ const std::vector<NTableClient::TLegacyOwningKey>& pivotKeys,
+ const TReshardTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ReshardTable, (
+ const NYPath::TYPath& path,
+ int tabletCount,
+ const TReshardTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<NTabletClient::TTabletActionId>>, ReshardTableAutomatic, (
+ const NYPath::TYPath& path,
+ const TReshardTableAutomaticOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, TrimTable, (
+ const NYPath::TYPath& path,
+ int tabletIndex,
+ i64 trimmedRowCount,
+ const TTrimTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AlterTable, (
+ const NYPath::TYPath& path,
+ const TAlterTableOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AlterTableReplica, (
+ NTabletClient::TTableReplicaId replicaId,
+ const TAlterTableReplicaOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, GetTablePivotKeys, (
+ const NYPath::TYPath& path,
+ const TGetTablePivotKeysOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, CreateTableBackup,(
+ const TBackupManifestPtr& manifest,
+ const TCreateTableBackupOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, RestoreTableBackup, (
+ const TBackupManifestPtr& manifest,
+ const TRestoreTableBackupOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<NTabletClient::TTableReplicaId>>, GetInSyncReplicas, (
+ const NYPath::TYPath& path,
+ const NTableClient::TNameTablePtr& nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TGetInSyncReplicasOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<NTabletClient::TTableReplicaId>>, GetInSyncReplicas, (
+ const NYPath::TYPath& path,
+ const TGetInSyncReplicasOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<TTabletInfo>>, GetTabletInfos, (
+ const NYPath::TYPath& path,
+ const std::vector<int>& tabletIndexes,
+ const TGetTabletInfosOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TGetTabletErrorsResult>, GetTabletErrors, (
+ const NYPath::TYPath& path,
+ const TGetTabletErrorsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<NTabletClient::TTabletActionId>>, BalanceTabletCells, (
+ const TString& tabletCellBundle,
+ const std::vector<NYPath::TYPath>& movableTables,
+ const TBalanceTabletCellsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NChaosClient::TReplicationCardPtr>, GetReplicationCard, (
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TGetReplicationCardOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, UpdateChaosTableReplicaProgress, (
+ NChaosClient::TReplicaId replicaId,
+ const TUpdateChaosTableReplicaProgressOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AlterReplicationCard, (
+ NChaosClient::TReplicationCardId replicationCardId,
+ const TAlterReplicationCardOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TSkynetSharePartsLocationsPtr>, LocateSkynetShare, (
+ const NYPath::TRichYPath& path,
+ const TLocateSkynetShareOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<NTableClient::TColumnarStatistics>>, GetColumnarStatistics, (
+ const std::vector<NYPath::TRichYPath>& path,
+ const TGetColumnarStatisticsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TMultiTablePartitions>, PartitionTables, (
+ const std::vector<NYPath::TRichYPath>& paths,
+ const TPartitionTablesOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, TruncateJournal, (
+ const NYPath::TYPath& path,
+ i64 rowCount,
+ const TTruncateJournalOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TGetFileFromCacheResult>, GetFileFromCache, (
+ const TString& md5,
+ const TGetFileFromCacheOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TPutFileToCacheResult>, PutFileToCache, (
+ const NYPath::TYPath& path,
+ const TString& expectedMD5,
+ const TPutFileToCacheOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AddMember, (
+ const TString& group,
+ const TString& member,
+ const TAddMemberOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, RemoveMember, (
+ const TString& group,
+ const TString& member,
+ const TRemoveMemberOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TCheckPermissionResponse>, CheckPermission, (
+ const TString& user,
+ const NYPath::TYPath& path,
+ NYTree::EPermission permission,
+ const TCheckPermissionOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TCheckPermissionByAclResult>, CheckPermissionByAcl, (
+ const std::optional<TString>& user,
+ NYTree::EPermission permission,
+ NYTree::INodePtr acl,
+ const TCheckPermissionByAclOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, TransferAccountResources, (
+ const TString& srcAccount,
+ const TString& dstAccount,
+ NYTree::INodePtr resourceDelta,
+ const TTransferAccountResourcesOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, TransferPoolResources, (
+ const TString& srcPool,
+ const TString& dstPool,
+ const TString& poolTree,
+ NYTree::INodePtr resourceDelta,
+ const TTransferPoolResourcesOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NScheduler::TOperationId>, StartOperation, (
+ NScheduler::EOperationType type,
+ const NYson::TYsonString& spec,
+ const TStartOperationOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AbortOperation, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TAbortOperationOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SuspendOperation, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TSuspendOperationOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ResumeOperation, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TResumeOperationOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, CompleteOperation, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TCompleteOperationOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, UpdateOperationParameters, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const NYson::TYsonString& parameters,
+ const TUpdateOperationParametersOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TOperation>, GetOperation, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TGetOperationOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, DumpJobContext, (
+ NJobTrackerClient::TJobId jobId,
+ const NYPath::TYPath& path,
+ const TDumpJobContextOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NConcurrency::IAsyncZeroCopyInputStreamPtr>, GetJobInput, (
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, GetJobInputPaths, (
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobInputPathsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, GetJobSpec, (
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobSpecOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TSharedRef>, GetJobStderr, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobStderrOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TSharedRef>, GetJobFailContext, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobFailContextOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TListOperationsResult>, ListOperations, (
+ const TListOperationsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TListJobsResult>, ListJobs, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ const TListJobsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, GetJob, (
+ const NScheduler::TOperationIdOrAlias& operationIdOrAlias,
+ NJobTrackerClient::TJobId jobId,
+ const TGetJobOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AbandonJob, (
+ NJobTrackerClient::TJobId jobId,
+ const TAbandonJobOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TPollJobShellResponse>, PollJobShell, (
+ NJobTrackerClient::TJobId jobId,
+ const std::optional<TString>& shellName,
+ const NYson::TYsonString& parameters,
+ const TPollJobShellOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AbortJob, (
+ NJobTrackerClient::TJobId jobId,
+ const TAbortJobOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TClusterMeta>, GetClusterMeta, (
+ const TGetClusterMetaOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, CheckClusterLiveness, (
+ const TCheckClusterLivenessOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TDisableChunkLocationsResult>, DisableChunkLocations, (
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDisableChunkLocationsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TDestroyChunkLocationsResult>, DestroyChunkLocations, (
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TDestroyChunkLocationsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TResurrectChunkLocationsResult>, ResurrectChunkLocations, (
+ const TString& nodeAddress,
+ const std::vector<TGuid>& locationUuids,
+ const TResurrectChunkLocationsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TRequestRebootResult>, RequestReboot, (
+ const TString& nodeAddress,
+ const TRequestRebootOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SetUserPassword, (
+ const TString& user,
+ const TString& currentPasswordSha256,
+ const TString& newPasswordSha256,
+ const TSetUserPasswordOptions& options),
+ (override));
+
+ MOCK_METHOD(TFuture<TIssueTokenResult>, IssueToken, (
+ const TString& user,
+ const TString& passwordSha256,
+ const TIssueTokenOptions& options),
+ (override));
+
+ MOCK_METHOD(TFuture<void>, RevokeToken, (
+ const TString& user,
+ const TString& passwordSha256,
+ const TString& tokenSha256,
+ const TRevokeTokenOptions& options),
+ (override));
+
+ MOCK_METHOD(TFuture<TListUserTokensResult>, ListUserTokens, (
+ const TString& user,
+ const TString& passwordSha256,
+ const TListUserTokensOptions& options),
+ (override));
+
+ MOCK_METHOD(TFuture<NQueryTrackerClient::TQueryId>, StartQuery, (
+ NQueryTrackerClient::EQueryEngine engine,
+ const TString& query,
+ const TStartQueryOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AbortQuery, (
+ NQueryTrackerClient::TQueryId queryId,
+ const TAbortQueryOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TQueryResult>, GetQueryResult, (
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex,
+ const TGetQueryResultOptions& options), (override));
+
+ MOCK_METHOD(TFuture<IUnversionedRowsetPtr>, ReadQueryResult, (
+ NQueryTrackerClient::TQueryId queryId,
+ i64 resultIndex,
+ const TReadQueryResultOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TQuery>, GetQuery, (
+ NQueryTrackerClient::TQueryId queryId,
+ const TGetQueryOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TListQueriesResult>, ListQueries, (
+ const TListQueriesOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, AlterQuery, (
+ NQueryTrackerClient::TQueryId queryId, const TAlterQueryOptions& options), (override));
+};
+
+DEFINE_REFCOUNTED_TYPE(TMockClient)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/unittests/mock/connection.h b/yt/yt/client/unittests/mock/connection.h
new file mode 100644
index 0000000000..ff328081a2
--- /dev/null
+++ b/yt/yt/client/unittests/mock/connection.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <yt/yt/client/api/connection.h>
+
+#include <yt/yt/client/hive/transaction_participant.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <library/cpp/testing/gtest_extensions/gtest_extensions.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMockConnection
+ : public IConnection
+{
+public:
+ MOCK_METHOD(TClusterTag, GetClusterTag, (), (const, override));
+ MOCK_METHOD(const TString&, GetLoggingTag, (), (const, override));
+ MOCK_METHOD(const TString&, GetClusterId, (), (const, override));
+ MOCK_METHOD(const std::optional<TString>&, GetClusterName, (), (const, override));
+
+ MOCK_METHOD(IInvokerPtr, GetInvoker, (), (override));
+
+ MOCK_METHOD(bool, IsSameCluster, (const IConnectionPtr&), (const, override));
+
+ MOCK_METHOD(IClientPtr, CreateClient, (const TClientOptions& options), (override));
+
+ MOCK_METHOD(NHiveClient::ITransactionParticipantPtr, CreateTransactionParticipant,
+ (NHiveClient::TCellId,
+ const TTransactionParticipantOptions&), (override));
+
+ MOCK_METHOD(void, ClearMetadataCaches, (), (override));
+ MOCK_METHOD(void, Terminate, (), (override));
+
+ MOCK_METHOD(NYson::TYsonString, GetConfigYson, (), (const, override));
+};
+
+DEFINE_REFCOUNTED_TYPE(TMockConnection)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/unittests/mock/table_value_consumer.h b/yt/yt/client/unittests/mock/table_value_consumer.h
new file mode 100644
index 0000000000..0ad0ab5fb5
--- /dev/null
+++ b/yt/yt/client/unittests/mock/table_value_consumer.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <library/cpp/testing/gtest_extensions/gtest_extensions.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/value_consumer.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMockValueConsumer
+ : public TValueConsumerBase
+{
+public:
+ TMockValueConsumer(
+ TNameTablePtr nameTable,
+ bool allowUnknownColumns,
+ TTableSchemaPtr schema = New<TTableSchema>(),
+ TTypeConversionConfigPtr typeConversionConfig = New<TTypeConversionConfig>())
+ : TValueConsumerBase(std::move(schema), std::move(typeConversionConfig))
+ , NameTable_(nameTable)
+ , AllowUnknowsColumns_(allowUnknownColumns)
+ {
+ InitializeIdToTypeMapping();
+ }
+
+ MOCK_METHOD(void, OnBeginRow, (), (override));
+ MOCK_METHOD(void, OnMyValue, (const TUnversionedValue& value), (override));
+ MOCK_METHOD(void, OnEndRow, (), (override));
+
+ const TNameTablePtr& GetNameTable() const override
+ {
+ return NameTable_;
+ }
+
+ bool GetAllowUnknownColumns() const override
+ {
+ return AllowUnknowsColumns_;
+ }
+
+private:
+ TNameTablePtr NameTable_;
+ bool AllowUnknowsColumns_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/mock/transaction.h b/yt/yt/client/unittests/mock/transaction.h
new file mode 100644
index 0000000000..d22136846d
--- /dev/null
+++ b/yt/yt/client/unittests/mock/transaction.h
@@ -0,0 +1,209 @@
+#pragma once
+
+#include <yt/yt/client/api/file_writer.h>
+#include <yt/yt/client/api/journal_reader.h>
+#include <yt/yt/client/api/journal_writer.h>
+#include <yt/yt/client/api/transaction.h>
+
+#include <library/cpp/testing/gtest_extensions/gtest_extensions.h>
+
+namespace NYT::NApi {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TMockTransaction
+ : public ITransaction
+{
+public:
+ MOCK_METHOD(IConnectionPtr, GetConnection, (), (override));
+
+ MOCK_METHOD(TFuture<ITransactionPtr>, StartTransaction, (
+ NTransactionClient::ETransactionType type,
+ const TTransactionStartOptions& options), (override));
+
+ MOCK_METHOD(TFuture<IUnversionedRowsetPtr>, LookupRows, (
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TLookupRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<IVersionedRowsetPtr>, VersionedLookupRows, (
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ const TSharedRange<NTableClient::TLegacyKey>& keys,
+ const TVersionedLookupRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<std::vector<IUnversionedRowsetPtr>>, MultiLookup, (
+ const std::vector<TMultiLookupSubrequest>& subrequests,
+ const TMultiLookupOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TSelectRowsResult>, SelectRows, (
+ const TString& query,
+ const TSelectRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, ExplainQuery, (
+ const TString& query,
+ const TExplainQueryOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TPullRowsResult>, PullRows, (
+ const NYPath::TYPath& path,
+ const TPullRowsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<ITableReaderPtr>, CreateTableReader, (
+ const NYPath::TRichYPath& path,
+ const TTableReaderOptions& options), (override));
+
+ MOCK_METHOD(TFuture<ITableWriterPtr>, CreateTableWriter, (
+ const NYPath::TRichYPath& path,
+ const TTableWriterOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, GetNode, (
+ const NYPath::TYPath& path,
+ const TGetNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, SetNode, (
+ const NYPath::TYPath& path,
+ const NYson::TYsonString& value,
+ const TSetNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, MultisetAttributesNode, (
+ const NYPath::TYPath& path,
+ const NYTree::IMapNodePtr& attributes,
+ const TMultisetAttributesNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, RemoveNode, (
+ const NYPath::TYPath& path,
+ const TRemoveNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NYson::TYsonString>, ListNode, (
+ const NYPath::TYPath& path,
+ const TListNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, CreateNode, (
+ const NYPath::TYPath& path,
+ NObjectClient::EObjectType type,
+ const TCreateNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<TLockNodeResult>, LockNode, (
+ const NYPath::TYPath& path,
+ NCypressClient::ELockMode mode,
+ const TLockNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, UnlockNode, (
+ const NYPath::TYPath& path,
+ const TUnlockNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, CopyNode, (
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TCopyNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, MoveNode, (
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TMoveNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NCypressClient::TNodeId>, LinkNode, (
+ const NYPath::TYPath& srcPath,
+ const NYPath::TYPath& dstPath,
+ const TLinkNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ConcatenateNodes, (
+ const std::vector<NYPath::TRichYPath>& srcPaths,
+ const NYPath::TRichYPath& dstPath,
+ const TConcatenateNodesOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, ExternalizeNode, (
+ const NYPath::TYPath& path,
+ NObjectClient::TCellTag cellTag,
+ const TExternalizeNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<void>, InternalizeNode, (
+ const NYPath::TYPath& path,
+ const TInternalizeNodeOptions& options), (override));
+
+ MOCK_METHOD(TFuture<bool>, NodeExists, (
+ const NYPath::TYPath& path,
+ const TNodeExistsOptions& options), (override));
+
+ MOCK_METHOD(TFuture<NObjectClient::TObjectId>, CreateObject, (
+ NObjectClient::EObjectType type,
+ const TCreateObjectOptions& options), (override));
+
+ MOCK_METHOD(TFuture<IFileReaderPtr>, CreateFileReader, (
+ const NYPath::TYPath& path,
+ const TFileReaderOptions& options), (override));
+
+ MOCK_METHOD(IFileWriterPtr, CreateFileWriter, (
+ const NYPath::TRichYPath& path,
+ const TFileWriterOptions& options), (override));
+
+ MOCK_METHOD(IJournalReaderPtr, CreateJournalReader, (
+ const NYPath::TYPath& path,
+ const TJournalReaderOptions& options), (override));
+
+ MOCK_METHOD(IJournalWriterPtr, CreateJournalWriter, (
+ const NYPath::TYPath& path,
+ const TJournalWriterOptions& options), (override));
+
+ // ITransaction
+ IClientPtr Client;
+ NTransactionClient::ETransactionType Type;
+ NTransactionClient::TTransactionId Id;
+ NTransactionClient::TTimestamp StartTimestamp;
+ NTransactionClient::EAtomicity Atomicity;
+ NTransactionClient::EDurability Durability;
+ TDuration Timeout;
+
+ IClientPtr GetClient() const override
+ {
+ return Client;
+ }
+ NTransactionClient::ETransactionType GetType() const override
+ {
+ return Type;
+ }
+ MOCK_METHOD(NTransactionClient::TTransactionId, GetId, (), (const, override));
+
+ NTransactionClient::TTimestamp GetStartTimestamp() const override
+ {
+ return StartTimestamp;
+ }
+ NTransactionClient::EAtomicity GetAtomicity() const override
+ {
+ return Atomicity;
+ }
+ NTransactionClient::EDurability GetDurability() const override
+ {
+ return Durability;
+ }
+ TDuration GetTimeout() const override
+ {
+ return Timeout;
+ }
+
+ MOCK_METHOD(TFuture<void>, Ping, (const NApi::TTransactionPingOptions& options), (override));
+ MOCK_METHOD(TFuture<TTransactionCommitResult>, Commit, (const TTransactionCommitOptions& options), (override));
+ MOCK_METHOD(TFuture<void>, Abort, (const TTransactionAbortOptions& options), (override));
+ MOCK_METHOD(void, Detach, (), (override));
+ MOCK_METHOD(TFuture<TTransactionFlushResult>, Flush, (), (override));
+
+ MOCK_METHOD(void, RegisterAlienTransaction, (const NApi::ITransactionPtr& transaction), (override));
+
+ MOCK_METHOD(void, SubscribeCommitted, (const TCommittedHandler& callback), (override));
+ MOCK_METHOD(void, UnsubscribeCommitted, (const TCommittedHandler& callback), (override));
+ MOCK_METHOD(void, SubscribeAborted, (const TAbortedHandler& callback), (override));
+ MOCK_METHOD(void, UnsubscribeAborted, (const TAbortedHandler& callback), (override));
+
+ MOCK_METHOD(void, ModifyRows, (
+ const NYPath::TYPath& path,
+ NTableClient::TNameTablePtr nameTable,
+ TSharedRange<TRowModification> modifications,
+ const TModifyRowsOptions& options), (override));
+};
+
+DEFINE_REFCOUNTED_TYPE(TMockTransaction)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NApi
diff --git a/yt/yt/client/unittests/mock/ya.make b/yt/yt/client/unittests/mock/ya.make
new file mode 100644
index 0000000000..483405dac0
--- /dev/null
+++ b/yt/yt/client/unittests/mock/ya.make
@@ -0,0 +1,18 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ client.h
+ connection.h
+ transaction.h
+ table_value_consumer.h
+)
+
+PEERDIR(
+ library/cpp/testing/gtest_extensions
+ yt/yt/build
+ yt/yt/core
+)
+
+END()
diff --git a/yt/yt/client/unittests/named_yson_token_ut.cpp b/yt/yt/client/unittests/named_yson_token_ut.cpp
new file mode 100644
index 0000000000..c330595c62
--- /dev/null
+++ b/yt/yt/client/unittests/named_yson_token_ut.cpp
@@ -0,0 +1,301 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/complex_types/yson_format_conversion.h>
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/yson/writer.h>
+#include <yt/yt/core/yson/parser.h>
+
+#include <library/cpp/yt/misc/variant.h>
+
+#include <util/stream/mem.h>
+
+namespace NYT::NComplexTypes {
+
+using NFormats::EComplexTypeMode;
+using namespace NYson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const auto KeyValueStruct = StructLogicalType({
+ {"key", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"value", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+});
+
+const auto IntStringVariant = VariantStructLogicalType({
+ {"int", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"string", SimpleLogicalType(ESimpleLogicalValueType::String)},
+});
+
+thread_local TYsonConverterConfig PositionalToNamedConfigInstance;
+
+class TWithConfig
+{
+public:
+ TWithConfig(const TYsonConverterConfig& config)
+ : OldConfig_(PositionalToNamedConfigInstance)
+ {
+ PositionalToNamedConfigInstance = config;
+ }
+
+ ~TWithConfig()
+ {
+ PositionalToNamedConfigInstance = OldConfig_;
+ }
+private:
+ TYsonConverterConfig OldConfig_;
+};
+
+TString CanonizeYson(TStringBuf yson)
+{
+ TString result;
+ {
+ TStringOutput out(result);
+ TYsonWriter writer(&out, EYsonFormat::Pretty, EYsonType::Node);
+ ParseYsonStringBuffer(yson, EYsonType::Node, &writer);
+ }
+ return result;
+}
+
+TString ConvertYson(
+ bool namedToPositional,
+ const TLogicalTypePtr& type,
+ TStringBuf sourceYson)
+{
+ TComplexTypeFieldDescriptor descriptor("<test-field>", type);
+ std::variant<TYsonServerToClientConverter, TYsonClientToServerConverter> converter;
+ try {
+ if (namedToPositional) {
+ TYsonConverterConfig config{
+ .ComplexTypeMode = EComplexTypeMode::Named,
+ };
+ converter = CreateYsonClientToServerConverter(descriptor, config);
+ } else {
+ converter = CreateYsonServerToClientConverter(descriptor, PositionalToNamedConfigInstance);
+ }
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "cannot create converter: " << ex.what();
+ return "";
+ }
+ TString convertedYson;
+ Visit(
+ converter,
+ [&] (const TYsonServerToClientConverter& serverToClientConverter) {
+ if (serverToClientConverter) {
+ TStringOutput out(convertedYson);
+ TYsonWriter writer(&out, EYsonFormat::Pretty);
+ serverToClientConverter(MakeUnversionedStringValue(sourceYson), &writer);
+ } else {
+ convertedYson = CanonizeYson(sourceYson);
+ }
+ },
+ [&] (const TYsonClientToServerConverter& clientToServerConverter) {
+ if (clientToServerConverter) {
+ auto value = clientToServerConverter(MakeUnversionedStringValue(sourceYson));
+ convertedYson = value.AsStringBuf();
+ } else {
+ convertedYson = CanonizeYson(sourceYson);
+ }
+ });
+ return convertedYson;
+}
+
+void CheckYsonConversion(
+ bool namedToPositional,
+ const TLogicalTypePtr& type,
+ TStringBuf sourceYson,
+ TStringBuf expectedConvertedYson)
+{
+ TString convertedYson;
+ try {
+ convertedYson = ConvertYson(namedToPositional, type, sourceYson);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "conversion error: " << ex.what();
+ return;
+ }
+
+ EXPECT_EQ(CanonizeYson(convertedYson), CanonizeYson(expectedConvertedYson));
+}
+
+#define CHECK_POSITIONAL_TO_NAMED(type, positionalYson, namedYson) \
+ do { \
+ SCOPED_TRACE("positional -> named error"); \
+ CheckYsonConversion(false, type, positionalYson, namedYson); \
+ } while (0)
+
+#define CHECK_NAMED_TO_POSITIONAL(type, namedYson, positionalYson) \
+ do { \
+ SCOPED_TRACE("named -> positional error"); \
+ CheckYsonConversion(true, type, namedYson, positionalYson); \
+ } while (0)
+
+#define CHECK_NAMED_TO_POSITIONAL_THROWS(type, namedYson, exceptionSubstring) \
+ do { \
+ TString tmp; \
+ EXPECT_THROW_WITH_SUBSTRING(ConvertYson(true, type, namedYson), exceptionSubstring); \
+ } while (0)
+
+#define CHECK_POSITIONAL_TO_NAMED_THROWS(type, namedYson, exceptionSubstring) \
+ do { \
+ TString tmp; \
+ EXPECT_THROW_WITH_SUBSTRING(ConvertYson(false, type, namedYson), exceptionSubstring); \
+ } while (0)
+
+#define CHECK_BIDIRECTIONAL(type, positionalYson, namedYson) \
+ do { \
+ CHECK_POSITIONAL_TO_NAMED(type, positionalYson, namedYson); \
+ CHECK_NAMED_TO_POSITIONAL(type, namedYson, positionalYson); \
+ } while (0)
+
+TEST(TNamedPositionalYsonConverter, TestSimpleTypes)
+{
+ CHECK_BIDIRECTIONAL(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ "-42",
+ "-42");
+
+ CHECK_BIDIRECTIONAL(
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ "foo",
+ "foo");
+}
+
+TEST(TNamedPositionalYsonConverter, TestStruct)
+{
+ CHECK_BIDIRECTIONAL(KeyValueStruct, "[foo; bar]", "{key=foo; value=bar}");
+ CHECK_BIDIRECTIONAL(KeyValueStruct, "[qux; #]", "{key=qux; value=#}");
+
+ CHECK_POSITIONAL_TO_NAMED(KeyValueStruct, "[baz]", "{key=baz; value=#}");
+ CHECK_NAMED_TO_POSITIONAL(KeyValueStruct, "{key=baz}", "[baz; #]");
+
+ CHECK_NAMED_TO_POSITIONAL_THROWS(KeyValueStruct, "{}", "is missing while parsing");
+ CHECK_NAMED_TO_POSITIONAL_THROWS(KeyValueStruct, "{value=baz}", "is missing while parsing");
+}
+
+TEST(TNamedPositionalYsonConverter, TestStructSkipNullValues)
+{
+ TYsonConverterConfig config{
+ .SkipNullValues = true,
+ };
+ TWithConfig g(config);
+
+ CHECK_POSITIONAL_TO_NAMED(KeyValueStruct, "[foo; bar]", "{key=foo; value=bar}");
+ CHECK_POSITIONAL_TO_NAMED(KeyValueStruct, "[foo; #]", "{key=foo}");
+ CHECK_POSITIONAL_TO_NAMED(KeyValueStruct, "[foo]", "{key=foo}");
+
+ auto type2 = StructLogicalType({
+ {"opt_int", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"opt_opt_int", OptionalLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64)))},
+ {"list_null", ListLogicalType(NullLogicalType())},
+ {"null", NullLogicalType()},
+ });
+ CHECK_POSITIONAL_TO_NAMED(type2, "[42; [#]; []; #]", "{opt_int=42; opt_opt_int=[#]; list_null=[]}");
+ CHECK_POSITIONAL_TO_NAMED(type2, "[#; #; [#; #;]]", "{list_null=[#; #;]}");
+}
+
+TEST(TNamedPositionalYsonConverter, TestVariantStruct)
+{
+ CHECK_BIDIRECTIONAL(IntStringVariant, "[0; 42]", "[int; 42]");
+ CHECK_BIDIRECTIONAL(IntStringVariant, "[1; foo]", "[string; foo]");
+
+ CHECK_NAMED_TO_POSITIONAL_THROWS(IntStringVariant, "[str; foo]", "Unknown variant field");
+}
+
+TEST(TNamedPositionalYsonConverter, TestOptional)
+{
+ CHECK_BIDIRECTIONAL(
+ OptionalLogicalType(KeyValueStruct),
+ "[foo; bar]",
+ "{key=foo; value=bar}");
+
+ CHECK_BIDIRECTIONAL(
+ OptionalLogicalType(KeyValueStruct),
+ "#",
+ "#");
+
+ CHECK_BIDIRECTIONAL(
+ OptionalLogicalType(OptionalLogicalType(KeyValueStruct)),
+ "[[foo; bar]]",
+ "[{key=foo; value=bar}]");
+
+ CHECK_BIDIRECTIONAL(
+ OptionalLogicalType(OptionalLogicalType(KeyValueStruct)),
+ "#",
+ "#");
+
+ CHECK_BIDIRECTIONAL(
+ OptionalLogicalType(OptionalLogicalType(KeyValueStruct)),
+ "[#]",
+ "[#]");
+}
+
+TEST(TNamedPositionalYsonConverter, TestList)
+{
+ CHECK_BIDIRECTIONAL(
+ ListLogicalType(KeyValueStruct),
+ "[[foo; bar]; [qux; #]]",
+ "[{key=foo; value=bar}; {key=qux; value=#};]");
+}
+
+TEST(TNamedPositionalYsonConverter, TestTuple)
+{
+ CHECK_BIDIRECTIONAL(
+ TupleLogicalType({KeyValueStruct, IntStringVariant, SimpleLogicalType(ESimpleLogicalValueType::Utf8)}),
+ "[[foo; bar]; [0; 5]; foo]",
+ "[{key=foo; value=bar}; [int; 5]; foo;]");
+}
+
+TEST(TNamedPositionalYsonConverter, TestVariantTuple)
+{
+ auto type = VariantTupleLogicalType({KeyValueStruct, IntStringVariant, SimpleLogicalType(ESimpleLogicalValueType::Utf8)});
+ CHECK_BIDIRECTIONAL(
+ type,
+ "[0; [foo; #]]",
+ "[0; {key=foo; value=#}]");
+
+ CHECK_BIDIRECTIONAL(
+ type,
+ "[1; [1; bar]]",
+ "[1; [string; bar]]");
+
+ CHECK_BIDIRECTIONAL(
+ type,
+ "[2; qux]",
+ "[2; qux]");
+}
+
+TEST(TNamedPositionalYsonConverter, TestDict)
+{
+ CHECK_BIDIRECTIONAL(
+ DictLogicalType(KeyValueStruct, IntStringVariant),
+ "[ [[foo; #]; [0; 0]] ; [[bar; qux;]; [1; baz;]]; ]",
+ "[ [{key=foo; value=#}; [int; 0]] ; [{key=bar; value=qux;}; [string; baz;]] ]");
+}
+
+TEST(TNamedPositionalYsonConverter, TestStringDictAsYsonMap)
+{
+ TYsonConverterConfig config{
+ .StringKeyedDictMode = NFormats::EDictMode::Named,
+ };
+ TWithConfig g(config);
+
+ CHECK_POSITIONAL_TO_NAMED(
+ DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ "[[key1; 1]; [key2; 2]]", "{key1=1; key2=2}");
+}
+
+TEST(TNamedPositionalYsonConverter, TestTagged)
+{
+ CHECK_BIDIRECTIONAL(
+ TaggedLogicalType("foo", KeyValueStruct),
+ "[foo; bar]",
+ "{key=foo; value=bar}");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/unittests/node_directory_ut.cpp b/yt/yt/client/unittests/node_directory_ut.cpp
new file mode 100644
index 0000000000..f1842b1491
--- /dev/null
+++ b/yt/yt/client/unittests/node_directory_ut.cpp
@@ -0,0 +1,33 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/client/node_tracker_client/node_directory.h>
+
+namespace NYT::NNodeTrackerClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TGetAddress, Test)
+{
+ const TAddressMap map{{"ipv4", "127.0.0.1:8081"}, {"ipv6", "::1:8081"}, {"default", "localhost:8081"}};
+
+ EXPECT_EQ("::1:8081", GetAddressOrThrow(map, { "ipv6", "ipv4" }));
+ EXPECT_EQ("127.0.0.1:8081", GetAddressOrThrow(map, { "ipv4", "ipv6" }));
+ EXPECT_THROW(GetAddressOrThrow(map, { "wrong" }), TErrorException);
+}
+
+TEST(TFindAddress, Test)
+{
+ const TAddressMap map{{"ipv4", "127.0.0.1:8081"}, {"ipv6", "::1:8081"}, {"default", "localhost:8081"}};
+
+ EXPECT_EQ("::1:8081", *FindAddress(map, {"ipv6", "ipv4"}));
+ EXPECT_EQ("127.0.0.1:8081", *FindAddress(map, {"ipv4", "ipv6"}));
+ EXPECT_FALSE(FindAddress(map, {"wrong"}));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NNodeTrackerClient
+
diff --git a/yt/yt/client/unittests/protobuf_format_ut.cpp b/yt/yt/client/unittests/protobuf_format_ut.cpp
new file mode 100644
index 0000000000..af9d2a0155
--- /dev/null
+++ b/yt/yt/client/unittests/protobuf_format_ut.cpp
@@ -0,0 +1,4657 @@
+#include "row_helpers.h"
+#include "yson_helpers.h"
+#include "yt/yt/client/table_client/public.h"
+
+#include <yt/yt/client/unittests/protobuf_format_ut.pb.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+#include <yt/yt/core/json/json_parser.h>
+#include <yt/yt/core/yson/string.h>
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/client/formats/config.h>
+#include <yt/yt/client/formats/parser.h>
+#include <yt/yt/client/formats/lenval_control_constants.h>
+#include <yt/yt/client/formats/protobuf_writer.h>
+#include <yt/yt/client/formats/protobuf_parser.h>
+#include <yt/yt/client/formats/protobuf.h>
+#include <yt/yt/client/formats/format.h>
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/value_consumer.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/library/named_value/named_value.h>
+
+#include <util/random/fast.h>
+
+#include <google/protobuf/text_format.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
+
+using namespace std::string_view_literals;
+
+
+namespace NYT {
+namespace {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NFormats;
+using namespace NTableClient;
+using namespace NConcurrency;
+using namespace NProtobufFormatTest;
+
+using ::google::protobuf::FileDescriptor;
+using NNamedValue::MakeRow;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EProtoFormatType,
+ (FileDescriptorLegacy)
+ (FileDescriptor)
+ (Structured)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define EXPECT_NODES_EQUAL(a, b) \
+ EXPECT_TRUE(AreNodesEqual((a), (b))) \
+ << #a ": " << ConvertToYsonString((a), EYsonFormat::Text).ToString() \
+ << "\n\n" #b ": " << ConvertToYsonString((b), EYsonFormat::Text).ToString();
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ConvertToTextYson(const INodePtr& node)
+{
+ return ConvertToYsonString(node, EYsonFormat::Text).ToString();
+}
+
+// Hardcoded serialization of file descriptor used in old format description.
+TString FileDescriptorLegacy = "\x0a\xb6\x03\x0a\x29\x6a\x75\x6e\x6b\x2f\x65\x72\x6d\x6f\x6c\x6f\x76\x64\x2f\x74\x65\x73\x74\x2d\x70\x72\x6f\x74\x6f\x62"
+ "\x75\x66\x2f\x6d\x65\x73\x73\x61\x67\x65\x2e\x70\x72\x6f\x74\x6f\x22\x2d\x0a\x0f\x54\x45\x6d\x62\x65\x64\x65\x64\x4d\x65\x73\x73\x61\x67\x65\x12"
+ "\x0b\x0a\x03\x4b\x65\x79\x18\x01\x20\x01\x28\x09\x12\x0d\x0a\x05\x56\x61\x6c\x75\x65\x18\x02\x20\x01\x28\x09\x22\xb3\x02\x0a\x08\x54\x4d\x65\x73"
+ "\x73\x61\x67\x65\x12\x0e\x0a\x06\x44\x6f\x75\x62\x6c\x65\x18\x01\x20\x01\x28\x01\x12\x0d\x0a\x05\x46\x6c\x6f\x61\x74\x18\x02\x20\x01\x28\x02\x12"
+ "\x0d\x0a\x05\x49\x6e\x74\x36\x34\x18\x03\x20\x01\x28\x03\x12\x0e\x0a\x06\x55\x49\x6e\x74\x36\x34\x18\x04\x20\x01\x28\x04\x12\x0e\x0a\x06\x53\x49"
+ "\x6e\x74\x36\x34\x18\x05\x20\x01\x28\x12\x12\x0f\x0a\x07\x46\x69\x78\x65\x64\x36\x34\x18\x06\x20\x01\x28\x06\x12\x10\x0a\x08\x53\x46\x69\x78\x65"
+ "\x64\x36\x34\x18\x07\x20\x01\x28\x10\x12\x0d\x0a\x05\x49\x6e\x74\x33\x32\x18\x08\x20\x01\x28\x05\x12\x0e\x0a\x06\x55\x49\x6e\x74\x33\x32\x18\x09"
+ "\x20\x01\x28\x0d\x12\x0e\x0a\x06\x53\x49\x6e\x74\x33\x32\x18\x0a\x20\x01\x28\x11\x12\x0f\x0a\x07\x46\x69\x78\x65\x64\x33\x32\x18\x0b\x20\x01\x28"
+ "\x07\x12\x10\x0a\x08\x53\x46\x69\x78\x65\x64\x33\x32\x18\x0c\x20\x01\x28\x0f\x12\x0c\x0a\x04\x42\x6f\x6f\x6c\x18\x0d\x20\x01\x28\x08\x12\x0e\x0a"
+ "\x06\x53\x74\x72\x69\x6e\x67\x18\x0e\x20\x01\x28\x09\x12\x0d\x0a\x05\x42\x79\x74\x65\x73\x18\x0f\x20\x01\x28\x0c\x12\x14\x0a\x04\x45\x6e\x75\x6d"
+ "\x18\x10\x20\x01\x28\x0e\x32\x06\x2e\x45\x45\x6e\x75\x6d\x12\x21\x0a\x07\x4d\x65\x73\x73\x61\x67\x65\x18\x11\x20\x01\x28\x0b\x32\x10\x2e\x54\x45"
+ "\x6d\x62\x65\x64\x65\x64\x4d\x65\x73\x73\x61\x67\x65\x2a\x24\x0a\x05\x45\x45\x6e\x75\x6d\x12\x07\x0a\x03\x4f\x6e\x65\x10\x01\x12\x07\x0a\x03\x54"
+ "\x77\x6f\x10\x02\x12\x09\x0a\x05\x54\x68\x72\x65\x65\x10\x03";
+
+TString GenerateRandomLenvalString(TFastRng64& rng, ui32 size)
+{
+ TString result;
+ result.append(reinterpret_cast<const char*>(&size), sizeof(size));
+
+ size += sizeof(ui32);
+
+ while (result.size() < size) {
+ ui64 num = rng.GenRand();
+ result.append(reinterpret_cast<const char*>(&num), sizeof(num));
+ }
+ if (result.size() > size) {
+ result.resize(size);
+ }
+ return result;
+}
+
+static TProtobufFormatConfigPtr MakeProtobufFormatConfig(const std::vector<const ::google::protobuf::Descriptor*>& descriptorList)
+{
+ ::google::protobuf::FileDescriptorSet fileDescriptorSet;
+ THashSet<const ::google::protobuf::FileDescriptor*> files;
+
+ std::function<void(const ::google::protobuf::FileDescriptor*)> addFile;
+ addFile = [&] (const ::google::protobuf::FileDescriptor* fileDescriptor) {
+ if (!files.insert(fileDescriptor).second) {
+ return;
+ }
+
+ // N.B. We want to write dependencies in fileDescriptorSet in topological order
+ // so we traverse dependencies first and the add current fileDescriptor.
+ for (int i = 0; i < fileDescriptor->dependency_count(); ++i) {
+ addFile(fileDescriptor->dependency(i));
+ }
+ fileDescriptor->CopyTo(fileDescriptorSet.add_file());
+ };
+ std::vector<TString> typeNames;
+
+ for (const auto* descriptor : descriptorList) {
+ addFile(descriptor->file());
+ typeNames.push_back(descriptor->full_name());
+ }
+
+ auto formatConfigYsonString = BuildYsonStringFluently()
+ .BeginMap()
+ .Item("file_descriptor_set_text").Value(fileDescriptorSet.ShortDebugString())
+ .Item("type_names").Value(typeNames)
+ .EndMap();
+
+ return ConvertTo<TProtobufFormatConfigPtr>(formatConfigYsonString);
+}
+
+INodePtr ParseYson(TStringBuf data)
+{
+ return ConvertToNode(NYson::TYsonString(TString{data}));
+}
+
+TString LenvalBytes(const ::google::protobuf::Message& message)
+{
+ TStringStream out;
+ ui32 messageSize = static_cast<ui32>(message.ByteSizeLong());
+ out.Write(&messageSize, sizeof(messageSize));
+ if (!message.SerializeToArcadiaStream(&out)) {
+ THROW_ERROR_EXCEPTION("Can not serialize message");
+ }
+ return out.Str();
+}
+
+void EnsureTypesMatch(EValueType expected, EValueType actual)
+{
+ if (expected != actual) {
+ THROW_ERROR_EXCEPTION("Mismatching type: expected %Qlv, actual %Qlv",
+ expected,
+ actual);
+ }
+}
+
+double GetDouble(const TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::Double, row.Type);
+ return row.Data.Double;
+}
+
+template <typename TMessage>
+TCollectingValueConsumer ParseRows(
+ const TMessage& message,
+ const TProtobufFormatConfigPtr& config,
+ const TTableSchemaPtr& schema = New<TTableSchema>(),
+ int count = 1)
+{
+ TString lenvalBytes;
+ TStringOutput out(lenvalBytes);
+ auto messageSize = static_cast<ui32>(message.ByteSize());
+ for (int i = 0; i < count; ++i) {
+ out.Write(&messageSize, sizeof(messageSize));
+ if (!message.SerializeToArcadiaStream(&out)) {
+ THROW_ERROR_EXCEPTION("Failed to serialize message");
+ }
+ }
+
+ TCollectingValueConsumer rowCollector(schema);
+ auto parser = CreateParserForProtobuf(&rowCollector, config, 0);
+ parser->Read(lenvalBytes);
+ parser->Finish();
+ if (static_cast<ssize_t>(rowCollector.Size()) != count) {
+ THROW_ERROR_EXCEPTION("rowCollector has wrong size: expected %v, actual %v",
+ count,
+ rowCollector.Size());
+ }
+ return rowCollector;
+}
+
+template <typename TMessage>
+TCollectingValueConsumer ParseRows(
+ const TMessage& message,
+ const INodePtr& config,
+ const TTableSchemaPtr& schema = New<TTableSchema>(),
+ int count = 1)
+{
+ return ParseRows(message, ConvertTo<TProtobufFormatConfigPtr>(config->Attributes().ToMap()), schema, count);
+}
+
+
+void AddDependencies(
+ const FileDescriptor* fileDescriptor,
+ std::vector<const FileDescriptor*>& fileDescriptors,
+ THashSet<const FileDescriptor*>& fileDescriptorSet)
+{
+ if (fileDescriptorSet.contains(fileDescriptor)) {
+ return;
+ }
+ fileDescriptorSet.insert(fileDescriptor);
+ for (int i = 0; i < fileDescriptor->dependency_count(); ++i) {
+ AddDependencies(fileDescriptor->dependency(i), fileDescriptors, fileDescriptorSet);
+ }
+ fileDescriptors.push_back(fileDescriptor);
+}
+
+template <typename ... Ts>
+INodePtr CreateFileDescriptorConfig(std::optional<EComplexTypeMode> complexTypeMode = {})
+{
+ std::vector<const FileDescriptor*> fileDescriptors;
+ THashSet<const FileDescriptor*> fileDescriptorSet;
+ std::vector<const FileDescriptor*> originalFileDescriptors = {Ts::descriptor()->file()...};
+
+ for (auto d : originalFileDescriptors) {
+ AddDependencies(d, fileDescriptors, fileDescriptorSet);
+ }
+
+ ::google::protobuf::FileDescriptorSet fileDescriptorSetProto;
+ for (auto fileDescriptor : fileDescriptors) {
+ fileDescriptor->CopyTo(fileDescriptorSetProto.add_file());
+ }
+ TString fileDescriptorSetText;
+ ::google::protobuf::TextFormat::Printer().PrintToString(fileDescriptorSetProto, &fileDescriptorSetText);
+ std::vector<TString> typeNames = {Ts::descriptor()->full_name()...};
+ return BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("file_descriptor_set_text").Value(fileDescriptorSetText)
+ .Item("type_names").Value(typeNames)
+ .OptionalItem("complex_type_mode", complexTypeMode)
+ .EndAttributes()
+ .Value("protobuf");
+}
+
+static const auto EnumerationsConfig = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("EEnum")
+ .BeginMap()
+ .Item("One").Value(1)
+ .Item("Two").Value(2)
+ .Item("Three").Value(3)
+ .Item("MinusFortyTwo").Value(-42)
+ .Item("MaxInt32").Value(std::numeric_limits<int>::max())
+ .Item("MinInt32").Value(std::numeric_limits<int>::min())
+ .EndMap()
+ .EndMap();
+
+INodePtr CreateAllFieldsConfig(EProtoFormatType protoFormatType)
+{
+ switch (protoFormatType) {
+ case EProtoFormatType::FileDescriptor:
+ return CreateFileDescriptorConfig<TMessage>();
+ case EProtoFormatType::FileDescriptorLegacy:
+ return BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("file_descriptor_set")
+ .Value(FileDescriptorLegacy)
+ .Item("file_indices")
+ .BeginList()
+ .Item().Value(0)
+ .EndList()
+ .Item("message_indices")
+ .BeginList()
+ .Item().Value(1)
+ .EndList()
+ .EndAttributes()
+ .Value("protobuf");
+ case EProtoFormatType::Structured:
+ return BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("enumerations").Value(EnumerationsConfig)
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Double")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("double")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Float")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("float")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Int64")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("UInt64")
+ .Item("field_number").Value(4)
+ .Item("proto_type").Value("uint64")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SInt64")
+ .Item("field_number").Value(5)
+ .Item("proto_type").Value("sint64")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Fixed64")
+ .Item("field_number").Value(6)
+ .Item("proto_type").Value("fixed64")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SFixed64")
+ .Item("field_number").Value(7)
+ .Item("proto_type").Value("sfixed64")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Int32")
+ .Item("field_number").Value(8)
+ .Item("proto_type").Value("int32")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("UInt32")
+ .Item("field_number").Value(9)
+ .Item("proto_type").Value("uint32")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SInt32")
+ .Item("field_number").Value(10)
+ .Item("proto_type").Value("sint32")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Fixed32")
+ .Item("field_number").Value(11)
+ .Item("proto_type").Value("fixed32")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SFixed32")
+ .Item("field_number").Value(12)
+ .Item("proto_type").Value("sfixed32")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Bool")
+ .Item("field_number").Value(13)
+ .Item("proto_type").Value("bool")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("String")
+ .Item("field_number").Value(14)
+ .Item("proto_type").Value("string")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Bytes")
+ .Item("field_number").Value(15)
+ .Item("proto_type").Value("bytes")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Enum")
+ .Item("field_number").Value(16)
+ .Item("proto_type").Value("enum_string")
+ .Item("enumeration_name").Value("EEnum")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Message")
+ .Item("field_number").Value(17)
+ .Item("proto_type").Value("message")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("AnyWithMap")
+ .Item("field_number").Value(18)
+ .Item("proto_type").Value("any")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("AnyWithInt64")
+ .Item("field_number").Value(19)
+ .Item("proto_type").Value("any")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("AnyWithString")
+ .Item("field_number").Value(20)
+ .Item("proto_type").Value("any")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("OtherColumns")
+ .Item("field_number").Value(21)
+ .Item("proto_type").Value("other_columns")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("MissingInt64")
+ .Item("field_number").Value(22)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndAttributes()
+ .Value("protobuf");
+ }
+ Y_FAIL();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TLenvalEntry
+{
+ TString RowData;
+ ui32 TableIndex;
+ ui64 TabletIndex;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TLenvalParser
+{
+public:
+ explicit TLenvalParser(IInputStream* input)
+ : Input_(input)
+ { }
+
+ explicit TLenvalParser(TStringBuf input)
+ : StreamHolder_(std::make_unique<TMemoryInput>(input))
+ , Input_(StreamHolder_.get())
+ { }
+
+ std::optional<TLenvalEntry> Next()
+ {
+ ui32 rowSize;
+ size_t read = Input_->Load(&rowSize, sizeof(rowSize));
+ if (read == 0) {
+ return std::nullopt;
+ } else if (read < sizeof(rowSize)) {
+ THROW_ERROR_EXCEPTION("corrupted lenval: can't read row length");
+ }
+ switch (rowSize) {
+ case LenvalTableIndexMarker: {
+ ui32 tableIndex;
+ read = Input_->Load(&tableIndex, sizeof(tableIndex));
+ if (read != sizeof(tableIndex)) {
+ THROW_ERROR_EXCEPTION("corrupted lenval: can't read table index");
+ }
+ CurrentTableIndex_ = tableIndex;
+ return Next();
+ }
+ case LenvalTabletIndexMarker: {
+ ui64 tabletIndex;
+ read = Input_->Load(&tabletIndex, sizeof(tabletIndex));
+ if (read != sizeof(tabletIndex)) {
+ THROW_ERROR_EXCEPTION("corrupted lenval: can't read tablet index");
+ }
+ CurrentTabletIndex_ = tabletIndex;
+ return Next();
+ }
+ case LenvalEndOfStream:
+ EndOfStream_ = true;
+ return std::nullopt;
+ case LenvalKeySwitch:
+ case LenvalRangeIndexMarker:
+ case LenvalRowIndexMarker:
+ THROW_ERROR_EXCEPTION("marker is unsupported");
+ default: {
+ TLenvalEntry result;
+ result.RowData.resize(rowSize);
+ result.TableIndex = CurrentTableIndex_;
+ result.TabletIndex = CurrentTabletIndex_;
+ Input_->Load(result.RowData.Detach(), rowSize);
+
+ return result;
+ }
+ }
+ }
+
+ bool IsEndOfStream() const
+ {
+ return EndOfStream_;
+ }
+
+private:
+ std::unique_ptr<IInputStream> StreamHolder_;
+ IInputStream* Input_;
+ ui32 CurrentTableIndex_ = 0;
+ ui64 CurrentTabletIndex_ = 0;
+ bool EndOfStream_ = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+TProtobufFormatConfigPtr ParseAndValidateConfig(const INodePtr& node, std::vector<TTableSchemaPtr> schemas = {})
+{
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(node);
+ if (schemas.empty()) {
+ schemas.assign(config->Tables.size(), New<TTableSchema>());
+ }
+ New<TProtobufParserFormatDescription>()->Init(config, schemas);
+ New<TProtobufWriterFormatDescription>()->Init(config, schemas);
+ return config;
+}
+
+} // namespace
+
+INodePtr BuildEmbeddedConfig(EComplexTypeMode complexTypeMode, EProtoFormatType formatType) {
+ if (formatType == EProtoFormatType::FileDescriptor) {
+ return CreateFileDescriptorConfig<NYT::TEmbeddingMessage>(complexTypeMode);
+ }
+
+ auto config = BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("tables").BeginList()
+ .Item().BeginMap()
+ .Item("columns").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("*")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("embedded_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("other_columns_field")
+ .Item("field_number").Value(15)
+ .Item("proto_type").Value("other_columns")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("embedded_num")
+ .Item("field_number").Value(10)
+ .Item("proto_type").Value("uint64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("embedded_extra_field")
+ .Item("field_number").Value(11)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("variant")
+ .Item("proto_type").Value("oneof")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("str_variant")
+ .Item("field_number").Value(101)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("uint_variant")
+ .Item("field_number").Value(102)
+ .Item("proto_type").Value("uint64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("*")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("embedded_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("embedded2_num")
+ .Item("field_number").Value(10)
+ .Item("proto_type").Value("uint64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("embedded2_struct")
+ .Item("field_number").Value(17)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("float1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("float")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("string1")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("embedded2_repeated")
+ .Item("field_number").Value(42)
+ .Item("proto_type").Value("string")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("num")
+ .Item("field_number").Value(12)
+ .Item("proto_type").Value("uint64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("extra_field")
+ .Item("field_number").Value(13)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .Item("complex_type_mode").Value(complexTypeMode)
+ .EndAttributes()
+ .Value("protobuf");
+ return config;
+}
+
+TTableSchemaPtr BuildEmbeddedSchema() {
+ auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"num", SimpleLogicalType(ESimpleLogicalValueType::Uint64)},
+ {"embedded_num", SimpleLogicalType(ESimpleLogicalValueType::Uint64)},
+ {"variant", VariantStructLogicalType({
+ {"str_variant", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"uint_variant", SimpleLogicalType(ESimpleLogicalValueType::Uint64)},
+ })},
+ {"extra_column", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint64))},
+ {"embedded2_num", SimpleLogicalType(ESimpleLogicalValueType::Uint64)},
+ {"embedded2_struct", StructLogicalType({
+ {"float1", SimpleLogicalType(ESimpleLogicalValueType::Float)},
+ {"string1", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ })},
+ {"embedded2_repeated", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"other_complex_field", StructLogicalType({
+ {"one", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"two", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"three", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ })},
+ {"extra_int", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+
+ });
+ return schema;
+}
+
+TEST(TProtobufFormat, TestConfigParsingEmbedded) {
+ auto config = BuildEmbeddedConfig(EComplexTypeMode::Positional, EProtoFormatType::Structured);
+ auto schema = BuildEmbeddedSchema();
+
+ EXPECT_NO_THROW(
+ ParseAndValidateConfig(config->Attributes().ToMap(), {schema})
+ );
+}
+
+TEST(TProtobufFormat, TestConfigParsing)
+{
+ // Empty config.
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(ParseYson("{}")),
+ "one of \"tables\", \"file_descriptor_set\" and \"file_descriptor_set_text\" must be specified");
+
+ // Broken protobuf.
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(ParseYson(R"({file_descriptor_set="dfgxx"; file_indices=[0]; message_indices=[0]})")),
+ "Error parsing \"file_descriptor_set\" in protobuf config");
+
+ EXPECT_NO_THROW(ParseAndValidateConfig(
+ CreateAllFieldsConfig(EProtoFormatType::Structured)->Attributes().ToMap()
+ ));
+
+ EXPECT_NO_THROW(ParseAndValidateConfig(
+ CreateAllFieldsConfig(EProtoFormatType::FileDescriptorLegacy)->Attributes().ToMap()
+ ));
+
+ EXPECT_NO_THROW(ParseAndValidateConfig(
+ CreateAllFieldsConfig(EProtoFormatType::FileDescriptor)->Attributes().ToMap()
+ ));
+
+ auto embeddedInsideNonembeddedConfig = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables").BeginList()
+ .Item().BeginMap()
+ .Item("columns").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("embedded_message1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("embedded_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("field1")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("embedded_message2")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("embedded_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("field2")
+ .Item("field_number").Value(4)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto schemaForEmbedded = New<TTableSchema>(std::vector{
+ TColumnSchema("field1", StructLogicalType({
+ {"embedded_message2", StructLogicalType({
+ {"field2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ })},
+ }))
+ });
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(embeddedInsideNonembeddedConfig, {schemaForEmbedded}),
+ "embedded_message inside of structured_message is not allowed");
+
+ auto repeatedEmbeddedConfig = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("*")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("embedded_message")
+ .Item("repeated").Value(true)
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("field1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("uint64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(repeatedEmbeddedConfig),
+ R"(type "embedded_message" can not be repeated)");
+
+ auto multipleOtherColumnsConfig = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Other1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("other_columns")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Other2")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("other_columns")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(multipleOtherColumnsConfig),
+ "Multiple \"other_columns\" in protobuf config are not allowed");
+
+ auto duplicateColumnNamesConfig = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SomeColumn")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SomeColumn")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(duplicateColumnNamesConfig),
+ "Multiple fields with same column name \"SomeColumn\" are forbidden in protobuf format");
+
+ auto anyCorrespondsToStruct = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SomeColumn")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("any")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto schema = New<TTableSchema>(std::vector{
+ TColumnSchema("SomeColumn", StructLogicalType({})),
+ });
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(anyCorrespondsToStruct, {schema}),
+ "Table schema and protobuf format config mismatch");
+
+ auto configWithBytes = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SomeColumn")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("bytes")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto schemaWithUtf8 = New<TTableSchema>(std::vector{
+ TColumnSchema("SomeColumn", SimpleLogicalType(ESimpleLogicalValueType::Utf8)),
+ });
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(configWithBytes, {schemaWithUtf8}),
+ "mismatch: expected logical type to be one of");
+
+ auto configWithPackedNonRepeated = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SomeColumn")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .Item("packed").Value(true)
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto schemaWithInt64List = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"SomeColumn", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ });
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(configWithPackedNonRepeated, {schemaWithInt64List}),
+ "Field \"SomeColumn\" is marked \"packed\" but is not marked \"repeated\"");
+
+ auto configWithPackedRepeatedString = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SomeColumn")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("string")
+ .Item("packed").Value(true)
+ .Item("repeated").Value(true)
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto schemaWithStringList = New<TTableSchema>(std::vector{
+ TColumnSchema("SomeColumn", ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::String)))
+ });
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(configWithPackedRepeatedString, {schemaWithStringList}),
+ "packed protobuf field must have primitive numeric type, got \"string\"");
+
+ auto configWithMissingFieldNumber = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("SomeColumn")
+ .Item("proto_type").Value("string")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseAndValidateConfig(configWithMissingFieldNumber, {schemaWithStringList}),
+ "\"field_number\" is required");
+}
+
+TEST(TProtobufFormat, TestParseBigZigZag)
+{
+ constexpr i32 value = Min<i32>();
+ TMessage message;
+ message.set_int32_field(value);
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(CreateAllFieldsConfig(EProtoFormatType::Structured)->Attributes().ToMap());
+ auto rowCollector = ParseRows(message, config);
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(0, "Int32")), value);
+}
+
+TEST(TProtobufFormat, TestParseEnumerationString)
+{
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(CreateAllFieldsConfig(EProtoFormatType::Structured)->Attributes().ToMap());
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::One);
+ auto rowCollector = ParseRows(message, config);
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(0, "Enum")), "One");
+ }
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::Two);
+ auto rowCollector = ParseRows(message, config);
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(0, "Enum")), "Two");
+ }
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::Three);
+ auto rowCollector = ParseRows(message, config);
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(0, "Enum")), "Three");
+ }
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::MinusFortyTwo);
+ auto rowCollector = ParseRows(message, config);
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(0, "Enum")), "MinusFortyTwo");
+ }
+}
+
+TEST(TProtobufFormat, TestParseWrongEnumeration)
+{
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(CreateAllFieldsConfig(EProtoFormatType::Structured)->Attributes().ToMap());
+ TMessage message;
+ auto enumTag = TMessage::descriptor()->FindFieldByName("enum_field")->number();
+ message.mutable_unknown_fields()->AddVarint(enumTag, 30);
+ EXPECT_ANY_THROW(ParseRows(message, config));
+}
+
+TEST(TProtobufFormat, TestParseEnumerationInt)
+{
+ TCollectingValueConsumer rowCollector;
+
+ auto config = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Enum")
+ .Item("field_number").Value(16)
+ .Item("proto_type").Value("enum_int")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto parser = CreateParserForProtobuf(&rowCollector, ConvertTo<TProtobufFormatConfigPtr>(config), 0);
+
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::One);
+ parser->Read(LenvalBytes(message));
+ }
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::Two);
+ parser->Read(LenvalBytes(message));
+ }
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::Three);
+ parser->Read(LenvalBytes(message));
+ }
+ {
+ TMessage message;
+ message.set_enum_field(EEnum::MinusFortyTwo);
+ parser->Read(LenvalBytes(message));
+ }
+ {
+ TMessage message;
+ auto enumTag = TMessage::descriptor()->FindFieldByName("enum_field")->number();
+ message.mutable_unknown_fields()->AddVarint(enumTag, 100500);
+ parser->Read(LenvalBytes(message));
+ }
+
+ parser->Finish();
+
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(0, "Enum")), 1);
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(1, "Enum")), 2);
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(2, "Enum")), 3);
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(3, "Enum")), -42);
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(4, "Enum")), 100500);
+}
+
+TEST(TProtobufFormat, TestParseRandomGarbage)
+{
+ // Check that we never crash.
+
+ TFastRng64 rng(42);
+ for (int i = 0; i != 1000; ++i) {
+ auto bytes = GenerateRandomLenvalString(rng, 8);
+
+ TCollectingValueConsumer rowCollector;
+ auto parser = CreateParserForProtobuf(
+ &rowCollector,
+ ConvertTo<TProtobufFormatConfigPtr>(CreateAllFieldsConfig(EProtoFormatType::Structured)->Attributes().ToMap()),
+ 0);
+ try {
+ parser->Read(bytes);
+ parser->Finish();
+ } catch (...) {
+ }
+ }
+}
+
+TEST(TProtobufFormat, TestParseZeroColumns)
+{
+ auto config = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ TCollectingValueConsumer rowCollector;
+ auto parser = CreateParserForProtobuf(
+ &rowCollector,
+ ConvertTo<TProtobufFormatConfigPtr>(config),
+ 0);
+
+ // Empty lenval values.
+ parser->Read("\0\0\0\0"sv);
+ parser->Read("\0\0\0\0"sv);
+
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<ssize_t>(rowCollector.Size()), 2);
+ EXPECT_EQ(static_cast<int>(rowCollector.GetRow(0).GetCount()), 0);
+ EXPECT_EQ(static_cast<int>(rowCollector.GetRow(1).GetCount()), 0);
+}
+
+TEST(TProtobufFormat, TestWriteEnumerationString)
+{
+ auto config = CreateAllFieldsConfig(EProtoFormatType::Structured);
+
+ auto nameTable = New<TNameTable>();
+
+ TString result;
+ TStringOutput resultStream(result);
+ auto writer = CreateWriterForProtobuf(
+ config->Attributes(),
+ {New<TTableSchema>()},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {"Enum", "MinusFortyTwo"}
+ }).Get()
+ });
+ writer->Write({
+ MakeRow(nameTable, {
+ {"Enum", "Three"},
+ }).Get()
+ });
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput si(result);
+ TLenvalParser parser(&si);
+ {
+ auto row = parser.Next();
+ ASSERT_TRUE(row);
+ NYT::TMessage message;
+ ASSERT_TRUE(message.ParseFromString(row->RowData));
+ ASSERT_EQ(message.enum_field(), NYT::EEnum::MinusFortyTwo);
+ }
+ {
+ auto row = parser.Next();
+ ASSERT_TRUE(row);
+ NYT::TMessage message;
+ ASSERT_TRUE(message.ParseFromString(row->RowData));
+ ASSERT_EQ(message.enum_field(), NYT::EEnum::Three);
+ }
+ {
+ auto row = parser.Next();
+ ASSERT_FALSE(row);
+ }
+}
+
+TEST(TProtobufFormat, TestWriteEnumerationInt)
+{
+ auto config = BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("Enum")
+ .Item("field_number").Value(16)
+ .Item("proto_type").Value("enum_int")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndAttributes()
+ .Value("protobuf");
+
+ auto nameTable = New<TNameTable>();
+
+ auto writeAndParseRow = [&] (TUnversionedRow row, TMessage* message) {
+ TString result;
+ TStringOutput resultStream(result);
+ auto writer = CreateWriterForProtobuf(
+ config->Attributes(),
+ {New<TTableSchema>()},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+ writer->Write({row});
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput si(result);
+ TLenvalParser parser(&si);
+ auto protoRow = parser.Next();
+ ASSERT_TRUE(protoRow);
+
+ ASSERT_TRUE(message->ParseFromString(protoRow->RowData));
+
+ auto nextProtoRow = parser.Next();
+ ASSERT_FALSE(nextProtoRow);
+ };
+
+ {
+ TMessage message;
+ writeAndParseRow(
+ MakeRow(nameTable, {
+ {"Enum", -42},
+ }).Get(),
+ &message);
+ ASSERT_EQ(message.enum_field(), EEnum::MinusFortyTwo);
+ }
+ {
+ TMessage message;
+ writeAndParseRow(
+ MakeRow(nameTable, {
+ {"Enum", static_cast<ui64>(std::numeric_limits<i32>::max())},
+ }).Get(),
+ &message);
+ ASSERT_EQ(message.enum_field(), EEnum::MaxInt32);
+ }
+ {
+ TMessage message;
+ writeAndParseRow(
+ MakeRow(nameTable, {
+ {"Enum", std::numeric_limits<i32>::max()},
+ }).Get(),
+ &message);
+ ASSERT_EQ(message.enum_field(), EEnum::MaxInt32);
+ }
+ {
+ TMessage message;
+ writeAndParseRow(
+ MakeRow(nameTable, {
+ {"Enum", std::numeric_limits<i32>::min()},
+ }).Get(),
+ &message);
+ ASSERT_EQ(message.enum_field(), EEnum::MinInt32);
+ }
+
+ TMessage message;
+ ASSERT_THROW(
+ writeAndParseRow(
+ MakeRow(nameTable, {
+ {"Enum", static_cast<i64>(std::numeric_limits<i32>::max()) + 1},
+ }).Get(),
+ &message),
+ TErrorException);
+
+ ASSERT_THROW(
+ writeAndParseRow(
+ MakeRow(nameTable, {
+ {"Enum", static_cast<i64>(std::numeric_limits<i32>::min()) - 1},
+ }).Get(),
+ &message),
+ TErrorException);
+
+ ASSERT_THROW(
+ writeAndParseRow(
+ MakeRow(nameTable, {
+ {"Enum", static_cast<ui64>(std::numeric_limits<i32>::max()) + 1},
+ }).Get(),
+ &message),
+ TErrorException);
+}
+
+
+TEST(TProtobufFormat, TestWriteZeroColumns)
+{
+ auto config = BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndAttributes()
+ .Value("protobuf");
+
+ auto nameTable = New<TNameTable>();
+
+ TString result;
+ TStringOutput resultStream(result);
+ auto writer = CreateWriterForProtobuf(
+ config->Attributes(),
+ {New<TTableSchema>()},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {"Int64", -1},
+ {"String", "this_is_string"},
+ }).Get()
+ });
+ writer->Write({MakeRow(nameTable, { }).Get()});
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ ASSERT_EQ(result, "\0\0\0\0\0\0\0\0"sv);
+}
+
+TEST(TProtobufFormat, TestTabletIndex)
+{
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap());
+
+ auto nameTable = New<TNameTable>();
+
+ TString result;
+ TStringOutput resultStream(result);
+ auto controlAttributesConfig = New<TControlAttributesConfig>();
+ controlAttributesConfig->EnableTabletIndex = true;
+
+ auto writer = CreateWriterForProtobuf(
+ config,
+ {New<TTableSchema>()},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ controlAttributesConfig,
+ 0);
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TabletIndexColumnName, 1LL << 50},
+ {"int64_field", -2345},
+ }).Get(),
+ MakeRow(nameTable, {
+ {TabletIndexColumnName, 12},
+ {"int64_field", 2345},
+ }).Get(),
+ });
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput si(result);
+ TLenvalParser parser(&si);
+ {
+ auto row = parser.Next();
+ ASSERT_TRUE(row);
+ ASSERT_EQ(row->TabletIndex, 1ULL << 50);
+ NYT::TMessage message;
+ ASSERT_TRUE(message.ParseFromString(row->RowData));
+ ASSERT_EQ(message.int64_field(), -2345);
+ }
+ {
+ auto row = parser.Next();
+ ASSERT_TRUE(row);
+ ASSERT_EQ(static_cast<int>(row->TabletIndex), 12);
+ NYT::TMessage message;
+ ASSERT_TRUE(message.ParseFromString(row->RowData));
+ ASSERT_EQ(message.int64_field(), 2345);
+ }
+ {
+ auto row = parser.Next();
+ ASSERT_FALSE(row);
+ }
+}
+
+TEST(TProtobufFormat, TestContext)
+{
+ auto config = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ TCollectingValueConsumer rowCollector;
+ auto parser = CreateParserForProtobuf(
+ &rowCollector,
+ ConvertTo<TProtobufFormatConfigPtr>(config),
+ 0);
+
+ TString context;
+ try {
+ TMessage message;
+ message.set_string_field("PYSHCH-PYSHCH");
+ parser->Read(LenvalBytes(message));
+ parser->Finish();
+ GTEST_FATAL_FAILURE_("expected to throw");
+ } catch (const NYT::TErrorException& e) {
+ context = *e.Error().Attributes().Find<TString>("context");
+ }
+ ASSERT_NE(context.find("PYSHCH-PYSHCH"), TString::npos);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TTableSchemaPtr CreateSchemaWithStructuredMessage()
+{
+ auto keyValueStruct = StructLogicalType({
+ {"key", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"value", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ });
+
+ return New<TTableSchema>(std::vector<TColumnSchema>{
+ {"first", StructLogicalType({
+ {"field_missing_from_proto1", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int32))},
+ {"enum_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"int64_field", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"repeated_int64_field", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"another_repeated_int64_field", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"message_field", keyValueStruct},
+ {"repeated_message_field", ListLogicalType(keyValueStruct)},
+ {"any_int64_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"any_map_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))},
+ {"optional_int64_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"repeated_optional_any_field", ListLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any)))},
+ {"packed_repeated_enum_field", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"optional_repeated_bool_field", OptionalLogicalType(ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Boolean)))},
+ {"oneof_field", VariantStructLogicalType({
+ {"oneof_string_field_1", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_string_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_message_field", keyValueStruct},
+ })},
+ {"optional_oneof_field", OptionalLogicalType(VariantStructLogicalType({
+ {"oneof_string_field_1", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_string_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_message_field", keyValueStruct},
+ }))},
+ {"map_field", DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ OptionalLogicalType(keyValueStruct))
+ },
+ {"field_missing_from_proto2", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int32))},
+ })},
+ {"repeated_int64_field", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"another_repeated_int64_field", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"repeated_message_field", ListLogicalType(keyValueStruct)},
+ {"second", StructLogicalType({
+ {"one", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"two", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"three", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ })},
+ {"any_field", SimpleLogicalType(ESimpleLogicalValueType::Any)},
+
+ {"int64_field", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"uint64_field", SimpleLogicalType(ESimpleLogicalValueType::Uint64)},
+ {"int32_field", SimpleLogicalType(ESimpleLogicalValueType::Int32)},
+ {"uint32_field", SimpleLogicalType(ESimpleLogicalValueType::Uint32)},
+
+ {"enum_int_field", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"enum_string_string_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"enum_string_int64_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+
+ {"repeated_optional_any_field", ListLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any)))},
+
+ {"other_complex_field", StructLogicalType({
+ {"one", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"two", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"three", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ })},
+
+ {"utf8_field", SimpleLogicalType(ESimpleLogicalValueType::Utf8)},
+
+ {"packed_repeated_int64_field", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+
+ {"optional_repeated_int64_field", OptionalLogicalType(ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64)))},
+
+ {"oneof_field", VariantStructLogicalType({
+ {"oneof_string_field_1", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_string_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_message_field", keyValueStruct},
+ })},
+
+ {"optional_oneof_field", OptionalLogicalType(VariantStructLogicalType({
+ {"oneof_string_field_1", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_string_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"oneof_message_field", keyValueStruct},
+ }))},
+
+ {"map_field", DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ OptionalLogicalType(keyValueStruct))
+ },
+ });
+}
+
+INodePtr CreateConfigWithStructuredMessage(EComplexTypeMode complexTypeMode, EProtoFormatType formatType)
+{
+ if (formatType == EProtoFormatType::FileDescriptor) {
+ return CreateFileDescriptorConfig<TMessageWithStructuredEmbedded>(complexTypeMode);
+ }
+ YT_VERIFY(formatType == EProtoFormatType::Structured);
+
+ auto buildOneofConfig = [] (TString prefix, int fieldNumberOffset) {
+ return BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("name").Value(prefix + "oneof_field")
+ .Item("proto_type").Value("oneof")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value(prefix + "oneof_string_field_1")
+ .Item("field_number").Value(101 + fieldNumberOffset)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value(prefix + "oneof_string_field")
+ .Item("field_number").Value(102 + fieldNumberOffset)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value(prefix + "oneof_message_field")
+ .Item("field_number").Value(1000 + fieldNumberOffset)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("key")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("value")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+ };
+ auto oneofConfig = buildOneofConfig("", 0);
+ auto optionalOneofConfig = buildOneofConfig("optional_", 1000);
+
+ auto keyValueFields = BuildYsonStringFluently()
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("key")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("value")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList();
+
+ return BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("enumerations").Value(EnumerationsConfig)
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("first")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("enum_field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("enum_string")
+ .Item("enumeration_name").Value("EEnum")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("packed_repeated_enum_field")
+ .Item("field_number").Value(11)
+ .Item("proto_type").Value("enum_string")
+ .Item("enumeration_name").Value("EEnum")
+ .Item("repeated").Value(true)
+ .Item("packed").Value(true)
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("message_field")
+ .Item("field_number").Value(4)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").Value(keyValueFields)
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("repeated_int64_field")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("int64")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("another_repeated_int64_field")
+ .Item("field_number").Value(9)
+ .Item("proto_type").Value("int64")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("repeated_message_field")
+ .Item("field_number").Value(5)
+ .Item("proto_type").Value("structured_message")
+ .Item("repeated").Value(true)
+ .Item("fields").Value(keyValueFields)
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("any_int64_field")
+ .Item("field_number").Value(6)
+ .Item("proto_type").Value("any")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("any_map_field")
+ .Item("field_number").Value(7)
+ .Item("proto_type").Value("any")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("optional_int64_field")
+ .Item("field_number").Value(8)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("repeated_optional_any_field")
+ .Item("field_number").Value(10)
+ .Item("proto_type").Value("any")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("optional_repeated_bool_field")
+ .Item("field_number").Value(12)
+ .Item("proto_type").Value("bool")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .Item().Value(oneofConfig)
+ .Item().Value(optionalOneofConfig)
+ .Item()
+ .BeginMap()
+ .Item("name").Value("map_field")
+ .Item("field_number").Value(13)
+ .Item("proto_type").Value("structured_message")
+ .Item("repeated").Value(true)
+ .Item("fields")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("key")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("value")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").Value(keyValueFields)
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("second")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("one")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("two")
+ .Item("field_number").Value(500000000)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("three")
+ .Item("field_number").Value(100500)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("repeated_message_field")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("structured_message")
+ .Item("repeated").Value(true)
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("key")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("value")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("repeated_int64_field")
+ .Item("field_number").Value(4)
+ .Item("proto_type").Value("int64")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("another_repeated_int64_field")
+ .Item("field_number").Value(13)
+ .Item("proto_type").Value("int64")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .Item()
+ .BeginMap()
+ // In schema it is of type "any".
+ .Item("name").Value("any_field")
+ .Item("field_number").Value(5)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ // The next fields are for type casting testing
+ .Item()
+ .BeginMap()
+ // In schema it is of type "int64".
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(6)
+ .Item("proto_type").Value("int32")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ // In schema it is of type "uint64".
+ .Item("name").Value("uint64_field")
+ .Item("field_number").Value(7)
+ .Item("proto_type").Value("uint32")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ // In schema it is of type "int32".
+ .Item("name").Value("int32_field")
+ .Item("field_number").Value(8)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ // In schema it is of type "uint32".
+ .Item("name").Value("uint32_field")
+ .Item("field_number").Value(9)
+ .Item("proto_type").Value("uint64")
+ .EndMap()
+
+ // Enums.
+ .Item()
+ .BeginMap()
+ .Item("name").Value("enum_int_field")
+ .Item("field_number").Value(10)
+ .Item("proto_type").Value("enum_int")
+ .Item("enumeration_name").Value("EEnum")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("enum_string_string_field")
+ .Item("field_number").Value(11)
+ .Item("proto_type").Value("enum_string")
+ .Item("enumeration_name").Value("EEnum")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("enum_string_int64_field")
+ .Item("field_number").Value(12)
+ .Item("proto_type").Value("enum_string")
+ .Item("enumeration_name").Value("EEnum")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("utf8_field")
+ .Item("field_number").Value(16)
+ .Item("proto_type").Value("string")
+ .EndMap()
+
+ // list<optional<any>>.
+ .Item()
+ .BeginMap()
+ .Item("name").Value("repeated_optional_any_field")
+ .Item("field_number").Value(14)
+ .Item("proto_type").Value("any")
+ .Item("repeated").Value(true)
+ .EndMap()
+
+ // Other columns.
+ .Item()
+ .BeginMap()
+ .Item("name").Value("other_columns_field")
+ .Item("field_number").Value(15)
+ .Item("proto_type").Value("other_columns")
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("packed_repeated_int64_field")
+ .Item("field_number").Value(17)
+ .Item("proto_type").Value("int64")
+ .Item("repeated").Value(true)
+ .Item("packed").Value(true)
+ .EndMap()
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("optional_repeated_int64_field")
+ .Item("field_number").Value(18)
+ .Item("proto_type").Value("int64")
+ .Item("repeated").Value(true)
+ .EndMap()
+
+ .Item().Value(oneofConfig)
+ .Item().Value(optionalOneofConfig)
+
+ .Item()
+ .BeginMap()
+ .Item("name").Value("map_field")
+ .Item("field_number").Value(19)
+ .Item("proto_type").Value("structured_message")
+ .Item("repeated").Value(true)
+ .Item("fields")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("key")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("value")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").Value(keyValueFields)
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .Item("complex_type_mode").Value(complexTypeMode)
+ .EndAttributes()
+ .Value("protobuf");
+}
+
+using TProtobufFormatStructuredMessageParameter = std::tuple<EComplexTypeMode, int, EProtoFormatType>;
+
+class TProtobufFormatStructuredMessage
+ : public ::testing::TestWithParam<TProtobufFormatStructuredMessageParameter>
+{ };
+
+INSTANTIATE_TEST_SUITE_P(
+ FileDescriptor,
+ TProtobufFormatStructuredMessage,
+ ::testing::Values(TProtobufFormatStructuredMessageParameter{
+ EComplexTypeMode::Positional,
+ 1,
+ EProtoFormatType::FileDescriptor}));
+
+INSTANTIATE_TEST_SUITE_P(
+ Positional,
+ TProtobufFormatStructuredMessage,
+ ::testing::Values(TProtobufFormatStructuredMessageParameter{
+ EComplexTypeMode::Positional,
+ 1,
+ EProtoFormatType::Structured}));
+
+INSTANTIATE_TEST_SUITE_P(
+ Named,
+ TProtobufFormatStructuredMessage,
+ ::testing::Values(TProtobufFormatStructuredMessageParameter{
+ EComplexTypeMode::Named,
+ 1,
+ EProtoFormatType::Structured}));
+
+INSTANTIATE_TEST_SUITE_P(
+ ManyRows,
+ TProtobufFormatStructuredMessage,
+ ::testing::Values(TProtobufFormatStructuredMessageParameter{
+ EComplexTypeMode::Named,
+ 30000,
+ EProtoFormatType::Structured}));
+
+TEST_P(TProtobufFormatStructuredMessage, EmbeddedWrite)
+{
+ auto [complexTypeMode, rowCount, protoFormatType] = GetParam();
+
+ auto nameTable = New<TNameTable>();
+ auto numId = nameTable->RegisterName("num");
+ auto embeddedNumId = nameTable->RegisterName("embedded_num");
+ auto variantId = nameTable->RegisterName("variant");
+ auto embedded2NumId = nameTable->RegisterName("embedded2_num");
+ auto embedded2StructId = nameTable->RegisterName("embedded2_struct");
+ auto embedded2RepeatedId = nameTable->RegisterName("embedded2_repeated");
+ auto extraIntId = nameTable->RegisterName("extra_int");
+ auto otherComplexFieldId = nameTable->RegisterName("other_complex_field");
+
+ //message T2 {
+ // optional ui64 embedded2_num;
+ //};
+ //message T1 {
+ // required T2 t2 [embedded];
+ // optional ui64 embedded_num;
+ //};
+ //
+ //message T {
+ // required T1 t1 [embedded];
+ // optional ui64 num;
+ //};
+
+ auto schema = BuildEmbeddedSchema();
+ auto config = BuildEmbeddedConfig(complexTypeMode, protoFormatType);
+
+ TString result;
+ TStringOutput resultStream(result);
+ auto writer = CreateWriterForProtobuf(
+ ConvertTo<TProtobufFormatConfigPtr>(config->Attributes()),
+ {schema},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+
+ TUnversionedRowBuilder builder;
+ builder.AddValue(MakeUnversionedUint64Value(789, numId));
+ builder.AddValue(MakeUnversionedUint64Value(123, embeddedNumId));
+ builder.AddValue(MakeUnversionedUint64Value(456, embedded2NumId));
+ builder.AddValue(MakeUnversionedCompositeValue("[1; 555u]", variantId));
+ auto embeddedYson = BuildYsonStringFluently()
+ .BeginList()
+ // float1
+ .Item().Value(1.5f)
+ // string1
+ .Item().Value("abc")
+ .EndList();
+ auto embeddedYsonStr = embeddedYson.ToString();
+ builder.AddValue(MakeUnversionedCompositeValue(embeddedYsonStr, embedded2StructId));
+ auto repeatedYsonStr = BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value("a")
+ .Item().Value("b")
+ .EndList()
+ .ToString();
+ builder.AddValue(MakeUnversionedCompositeValue(repeatedYsonStr, embedded2RepeatedId));
+ builder.AddValue(MakeUnversionedInt64Value(111, extraIntId));
+ auto otherComplexFieldYson = BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value(22)
+ .Item().Value(23)
+ .Item().Value(24)
+ .EndList();
+ auto otherComplexFieldYsonStr = otherComplexFieldYson.ToString();
+ builder.AddValue(MakeUnversionedCompositeValue(otherComplexFieldYsonStr, otherComplexFieldId));
+
+
+ auto rows = std::vector<TUnversionedRow>(rowCount, builder.GetRow());
+ writer->Write(rows);
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput input(result);
+ TLenvalParser lenvalParser(&input);
+
+ for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
+ auto entry = lenvalParser.Next();
+ ASSERT_TRUE(entry);
+
+ NYT::TEmbeddingMessage message;
+ ASSERT_TRUE(message.ParseFromString(entry->RowData));
+
+ EXPECT_EQ(message.num(), 789UL);
+ EXPECT_EQ(message.t1().embedded_num(), 123UL);
+ EXPECT_EQ(message.t1().t2().embedded2_num(), 456UL);
+
+ EXPECT_FALSE(message.t1().has_str_variant());
+ EXPECT_TRUE(message.t1().has_uint_variant());
+ EXPECT_EQ(message.t1().uint_variant(), 555UL);
+
+ EXPECT_EQ(message.t1().t2().embedded2_struct().float1(), 1.5f);
+ EXPECT_EQ(message.t1().t2().embedded2_struct().string1(), "abc");
+
+ ASSERT_EQ(message.t1().t2().embedded2_repeated_size(), 2);
+ EXPECT_EQ(message.t1().t2().embedded2_repeated(0), "a");
+ EXPECT_EQ(message.t1().t2().embedded2_repeated(1), "b");
+
+ {
+ auto otherColumns = ConvertToNode(TYsonString(message.other_columns_field()))->AsMap();
+ auto mode = complexTypeMode;
+ auto expected = ([&] {
+ switch (mode) {
+ case EComplexTypeMode::Named:
+ return BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("one").Value(22)
+ .Item("two").Value(23)
+ .Item("three").Value(24)
+ .EndMap();
+ case EComplexTypeMode::Positional:
+ return ConvertToNode(otherComplexFieldYson);
+ }
+ YT_ABORT();
+ })();
+
+ EXPECT_NODES_EQUAL(expected, otherColumns->GetChildOrThrow("other_complex_field"));
+ EXPECT_EQ(ConvertTo<i64>(otherColumns->GetChildOrThrow("extra_int")), 111);
+ }
+
+ ASSERT_FALSE(message.has_extra_field());
+ ASSERT_FALSE(message.t1().has_embedded_extra_field());
+ }
+
+ ASSERT_FALSE(lenvalParser.Next());
+}
+
+TEST_P(TProtobufFormatStructuredMessage, Write)
+{
+ auto [complexTypeMode, rowCount, protoFormatType] = GetParam();
+
+ auto nameTable = New<TNameTable>();
+ auto firstId = nameTable->RegisterName("first");
+ auto secondId = nameTable->RegisterName("second");
+ auto repeatedMessageId = nameTable->RegisterName("repeated_message_field");
+ auto repeatedInt64Id = nameTable->RegisterName("repeated_int64_field");
+ auto anotherRepeatedInt64Id = nameTable->RegisterName("another_repeated_int64_field");
+ auto anyFieldId = nameTable->RegisterName("any_field");
+ auto int64FieldId = nameTable->RegisterName("int64_field");
+ auto uint64FieldId = nameTable->RegisterName("uint64_field");
+ auto int32FieldId = nameTable->RegisterName("int32_field");
+ auto uint32FieldId = nameTable->RegisterName("uint32_field");
+ auto enumIntFieldId = nameTable->RegisterName("enum_int_field");
+ auto enumStringStringFieldId = nameTable->RegisterName("enum_string_string_field");
+ auto enumStringInt64FieldId = nameTable->RegisterName("enum_string_int64_field");
+ auto utf8FieldId = nameTable->RegisterName("utf8_field");
+ auto repeatedOptionalAnyFieldId = nameTable->RegisterName("repeated_optional_any_field");
+ auto otherComplexFieldId = nameTable->RegisterName("other_complex_field");
+ auto packedRepeatedInt64FieldId = nameTable->RegisterName("packed_repeated_int64_field");
+ auto optionalRepeatedInt64FieldId = nameTable->RegisterName("optional_repeated_int64_field");
+ auto oneofFieldId = nameTable->RegisterName("oneof_field");
+ auto optionalOneofFieldId = nameTable->RegisterName("optional_oneof_field");
+ auto mapFieldId = nameTable->RegisterName("map_field");
+
+ auto schema = CreateSchemaWithStructuredMessage();
+ auto config = CreateConfigWithStructuredMessage(complexTypeMode, protoFormatType);
+
+ TString result;
+ TStringOutput resultStream(result);
+ auto writer = CreateWriterForProtobuf(
+ ConvertTo<TProtobufFormatConfigPtr>(config->Attributes()),
+ {schema},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+
+ auto firstYsonStr = BuildYsonStringFluently()
+ .BeginList()
+ // field_missing_from_proto1
+ .Item().Value(11111)
+ // enum_field
+ .Item().Value("Two")
+ // int64_field
+ .Item().Value(44)
+ // repeated_int64_field
+ .Item()
+ .BeginList()
+ .Item().Value(55)
+ .Item().Value(56)
+ .Item().Value(57)
+ .EndList()
+ // another_repeated_int64_field
+ .Item()
+ .BeginList()
+ .EndList()
+ // message_field
+ .Item()
+ .BeginList()
+ .Item().Value("key")
+ .Item().Value("value")
+ .EndList()
+ // repeated_message_field
+ .Item()
+ .BeginList()
+ .Item()
+ .BeginList()
+ .Item().Value("key1")
+ .Item().Value("value1")
+ .EndList()
+ .Item()
+ .BeginList()
+ .Item().Value("key2")
+ .Item().Value("value2")
+ .EndList()
+ .EndList()
+ // any_int64_field
+ .Item().Value(45)
+ // any_map_field
+ .Item()
+ .BeginMap()
+ .Item("key").Value("value")
+ .EndMap()
+ // optional_int64_field
+ .Item().Entity()
+ // repeated_optional_any_field
+ .Item()
+ .BeginList()
+ .Item().Value(2)
+ .Item().Entity()
+ .Item().Value("foo")
+ .EndList()
+ // packed_repeated_enum_field
+ .Item()
+ .BeginList()
+ .Item().Value("MinusFortyTwo")
+ .Item().Value("Two")
+ .EndList()
+ // optional_repeated_bool_field
+ .Item()
+ .BeginList()
+ .Item().Value(false)
+ .Item().Value(true)
+ .Item().Value(false)
+ .EndList()
+ // oneof_field
+ .Item()
+ .BeginList()
+ // message_field
+ .Item().Value(2)
+ .Item().BeginList()
+ .Item().Value("foo")
+ .Item().Entity()
+ .EndList()
+ .EndList()
+ // optional_oneof_field
+ .Item()
+ .Entity()
+ // map_field
+ .Item()
+ .BeginList()
+ .Item().BeginList()
+ .Item().Value(13)
+ .Item().BeginList()
+ .Item().Value("bac")
+ .Item().Value("cab")
+ .EndList()
+ .EndList()
+ .Item().BeginList()
+ .Item().Value(15)
+ .Item().BeginList()
+ .Item().Value("ya")
+ .Item().Value("make")
+ .EndList()
+ .EndList()
+ .EndList()
+ .EndList()
+ .ToString();
+
+ auto secondYsonStr = BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value(101)
+ .Item().Value(102)
+ .Item().Value(103)
+ .EndList()
+ .ToString();
+
+ auto repeatedMessageYsonStr = BuildYsonStringFluently()
+ .BeginList()
+ .Item()
+ .BeginList()
+ .Item().Value("key11")
+ .Item().Value("value11")
+ .EndList()
+ .Item()
+ .BeginList()
+ .Item().Value("key21")
+ .Item().Value("value21")
+ .EndList()
+ .EndList()
+ .ToString();
+
+ auto repeatedInt64Yson = BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value(31)
+ .Item().Value(32)
+ .Item().Value(33)
+ .EndList();
+ auto repeatedInt64YsonStr = repeatedInt64Yson.ToString();
+
+ auto anotherRepeatedInt64YsonStr = BuildYsonStringFluently()
+ .BeginList()
+ .EndList()
+ .ToString();
+
+ auto repeatedOptionalAnyYson = BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value(1)
+ .Item().Value("abc")
+ .Item().Entity()
+ .Item().Value(true)
+ .EndList();
+ auto repeatedOptionalAnyYsonStr = repeatedOptionalAnyYson.ToString();
+
+ auto otherComplexFieldYson = BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value(22)
+ .Item().Value(23)
+ .Item().Value(24)
+ .EndList();
+ auto otherComplexFieldYsonStr = otherComplexFieldYson.ToString();
+
+ TUnversionedRowBuilder builder;
+ builder.AddValue(MakeUnversionedCompositeValue(firstYsonStr, firstId));
+ builder.AddValue(MakeUnversionedCompositeValue(secondYsonStr, secondId));
+ builder.AddValue(MakeUnversionedCompositeValue(repeatedMessageYsonStr, repeatedMessageId));
+ builder.AddValue(MakeUnversionedCompositeValue(repeatedInt64YsonStr, repeatedInt64Id));
+ builder.AddValue(MakeUnversionedCompositeValue(anotherRepeatedInt64YsonStr, anotherRepeatedInt64Id));
+ builder.AddValue(MakeUnversionedInt64Value(4321, anyFieldId));
+
+ builder.AddValue(MakeUnversionedInt64Value(-64, int64FieldId));
+ builder.AddValue(MakeUnversionedUint64Value(64, uint64FieldId));
+ builder.AddValue(MakeUnversionedInt64Value(-32, int32FieldId));
+ builder.AddValue(MakeUnversionedUint64Value(32, uint32FieldId));
+
+ builder.AddValue(MakeUnversionedInt64Value(-42, enumIntFieldId));
+ builder.AddValue(MakeUnversionedStringValue("Three", enumStringStringFieldId));
+ builder.AddValue(MakeUnversionedInt64Value(1, enumStringInt64FieldId));
+
+ const auto HelloWorldInRussian = "\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, \xd0\xbc\xd0\xb8\xd1\x80!";
+ builder.AddValue(MakeUnversionedStringValue(HelloWorldInRussian, utf8FieldId));
+
+ builder.AddValue(MakeUnversionedCompositeValue(repeatedOptionalAnyYsonStr, repeatedOptionalAnyFieldId));
+
+ builder.AddValue(MakeUnversionedCompositeValue(otherComplexFieldYsonStr, otherComplexFieldId));
+
+ builder.AddValue(MakeUnversionedCompositeValue("[12;-10;123456789000;]", packedRepeatedInt64FieldId));
+
+ builder.AddValue(MakeUnversionedCompositeValue("[1;2;3]", optionalRepeatedInt64FieldId));
+
+ builder.AddValue(MakeUnversionedCompositeValue("[0; foobaz]", oneofFieldId));
+ builder.AddValue(MakeUnversionedNullValue(optionalOneofFieldId));
+
+ builder.AddValue(MakeUnversionedCompositeValue("[[2; [x; y]]; [5; [z; w]]]", mapFieldId));
+
+ auto rows = std::vector<TUnversionedRow>(rowCount, builder.GetRow());
+ writer->Write(rows);
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput input(result);
+ TLenvalParser lenvalParser(&input);
+
+ for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
+ auto entry = lenvalParser.Next();
+ ASSERT_TRUE(entry);
+
+ NYT::TMessageWithStructuredEmbedded message;
+ ASSERT_TRUE(message.ParseFromString(entry->RowData));
+
+ const auto& first = message.first();
+ EXPECT_EQ(first.enum_field(), EEnum::Two);
+ EXPECT_EQ(first.int64_field(), 44);
+ std::vector<i64> firstRepeatedInt64Field(
+ first.repeated_int64_field().begin(),
+ first.repeated_int64_field().end());
+ EXPECT_EQ(firstRepeatedInt64Field, (std::vector<i64>{55, 56, 57}));
+ std::vector<i64> firstAnotherRepeatedInt64Field(
+ first.another_repeated_int64_field().begin(),
+ first.another_repeated_int64_field().end());
+ EXPECT_EQ(firstAnotherRepeatedInt64Field, (std::vector<i64>{}));
+ EXPECT_EQ(first.message_field().key(), "key");
+ EXPECT_EQ(first.message_field().value(), "value");
+ ASSERT_EQ(first.repeated_message_field_size(), 2);
+ EXPECT_EQ(first.repeated_message_field(0).key(), "key1");
+ EXPECT_EQ(first.repeated_message_field(0).value(), "value1");
+ EXPECT_EQ(first.repeated_message_field(1).key(), "key2");
+ EXPECT_EQ(first.repeated_message_field(1).value(), "value2");
+
+ EXPECT_NODES_EQUAL(
+ ConvertToNode(TYsonString(first.any_int64_field())),
+ BuildYsonNodeFluently().Value(45));
+
+ EXPECT_NODES_EQUAL(
+ ConvertToNode(TYsonString(first.any_map_field())),
+ BuildYsonNodeFluently().BeginMap()
+ .Item("key").Value("value")
+ .EndMap());
+
+ std::vector<TYsonString> firstRepeatedOptionalAnyField(
+ first.repeated_optional_any_field().begin(),
+ first.repeated_optional_any_field().end());
+
+ EXPECT_NODES_EQUAL(
+ ConvertToNode(firstRepeatedOptionalAnyField),
+ BuildYsonNodeFluently()
+ .BeginList()
+ .Item().Value(2)
+ .Item().Entity()
+ .Item().Value("foo")
+ .EndList());
+
+ EXPECT_FALSE(first.has_optional_int64_field());
+
+ std::vector<EEnum> actualFirstPackedRepeatedEnumField;
+ for (auto x : first.packed_repeated_enum_field()) {
+ actualFirstPackedRepeatedEnumField.push_back(static_cast<EEnum>(x));
+ }
+ auto expectedFirstPackedRepeatedEnumField = std::vector<EEnum>{EEnum::MinusFortyTwo, EEnum::Two};
+ EXPECT_EQ(expectedFirstPackedRepeatedEnumField, actualFirstPackedRepeatedEnumField);
+
+ std::vector<bool> firstOptionalRepeatedBoolField(
+ first.optional_repeated_bool_field().begin(),
+ first.optional_repeated_bool_field().end());
+ auto expectedFirstOptionalRepeatedBoolField = std::vector<bool>{false, true, false};
+ EXPECT_EQ(expectedFirstOptionalRepeatedBoolField, firstOptionalRepeatedBoolField);
+
+ EXPECT_FALSE(first.has_oneof_string_field_1());
+ EXPECT_FALSE(first.has_oneof_string_field());
+ EXPECT_TRUE(first.has_oneof_message_field());
+ EXPECT_EQ(first.oneof_message_field().key(), "foo");
+ EXPECT_FALSE(first.oneof_message_field().has_value());
+
+ EXPECT_FALSE(first.has_optional_oneof_string_field_1());
+ EXPECT_FALSE(first.has_optional_oneof_string_field());
+ EXPECT_FALSE(first.has_optional_oneof_message_field());
+
+ EXPECT_EQ(std::ssize(first.map_field()), 2);
+ ASSERT_EQ(static_cast<int>(first.map_field().count(13)), 1);
+ EXPECT_EQ(first.map_field().at(13).key(), "bac");
+ EXPECT_EQ(first.map_field().at(13).value(), "cab");
+ ASSERT_EQ(static_cast<int>(first.map_field().count(15)), 1);
+ EXPECT_EQ(first.map_field().at(15).key(), "ya");
+ EXPECT_EQ(first.map_field().at(15).value(), "make");
+
+ const auto& second = message.second();
+ EXPECT_EQ(second.one(), 101);
+ EXPECT_EQ(second.two(), 102);
+ EXPECT_EQ(second.three(), 103);
+
+ ASSERT_EQ(message.repeated_message_field_size(), 2);
+ EXPECT_EQ(message.repeated_message_field(0).key(), "key11");
+ EXPECT_EQ(message.repeated_message_field(0).value(), "value11");
+ EXPECT_EQ(message.repeated_message_field(1).key(), "key21");
+ EXPECT_EQ(message.repeated_message_field(1).value(), "value21");
+
+ std::vector<i64> repeatedInt64Field(
+ message.repeated_int64_field().begin(),
+ message.repeated_int64_field().end());
+ EXPECT_EQ(repeatedInt64Field, (std::vector<i64>{31, 32, 33}));
+
+ std::vector<i64> anotherRepeatedInt64Field(
+ message.another_repeated_int64_field().begin(),
+ message.another_repeated_int64_field().end());
+ EXPECT_EQ(anotherRepeatedInt64Field, (std::vector<i64>{}));
+
+ EXPECT_EQ(message.int64_any_field(), 4321);
+
+ // Note the reversal of 32 <-> 64.
+ EXPECT_EQ(message.int32_field(), -64);
+ EXPECT_EQ(message.uint32_field(), 64u);
+ EXPECT_EQ(message.int64_field(), -32);
+ EXPECT_EQ(message.uint64_field(), 32u);
+
+ EXPECT_EQ(message.enum_int_field(), EEnum::MinusFortyTwo);
+ EXPECT_EQ(message.enum_string_string_field(), EEnum::Three);
+ EXPECT_EQ(message.enum_string_int64_field(), EEnum::One);
+
+ EXPECT_EQ(message.utf8_field(), HelloWorldInRussian);
+
+ std::vector<TYsonString> repeatedOptionalAnyField(
+ message.repeated_optional_any_field().begin(),
+ message.repeated_optional_any_field().end());
+ EXPECT_NODES_EQUAL(ConvertToNode(repeatedOptionalAnyField), ConvertToNode(repeatedOptionalAnyYson));
+
+ {
+ auto otherColumns = ConvertToNode(TYsonString(message.other_columns_field()))->AsMap();
+ auto mode = complexTypeMode;
+ auto expected = ([&] {
+ switch (mode) {
+ case EComplexTypeMode::Named:
+ return BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("one").Value(22)
+ .Item("two").Value(23)
+ .Item("three").Value(24)
+ .EndMap();
+ case EComplexTypeMode::Positional:
+ return ConvertToNode(otherComplexFieldYson);
+ }
+ YT_ABORT();
+ })();
+
+ EXPECT_NODES_EQUAL(expected, otherColumns->GetChildOrThrow("other_complex_field"));
+ }
+
+ std::vector<i64> actualPackedRepeatedInt64Field(
+ message.packed_repeated_int64_field().begin(),
+ message.packed_repeated_int64_field().end());
+ auto expectedPackedRepeatedInt64Field = std::vector<i64>{12, -10, 123456789000LL};
+ EXPECT_EQ(expectedPackedRepeatedInt64Field, actualPackedRepeatedInt64Field);
+
+ std::vector<i64> actualOptionalRepeatedInt64Field(
+ message.optional_repeated_int64_field().begin(),
+ message.optional_repeated_int64_field().end());
+ auto expectedOptionalRepeatedInt64Field = std::vector<i64>{1, 2, 3};
+ EXPECT_EQ(expectedOptionalRepeatedInt64Field, actualOptionalRepeatedInt64Field);
+
+ EXPECT_TRUE(message.has_oneof_string_field_1());
+ EXPECT_EQ(message.oneof_string_field_1(), "foobaz");
+ EXPECT_FALSE(message.has_oneof_string_field());
+ EXPECT_FALSE(message.has_oneof_message_field());
+
+ EXPECT_FALSE(message.has_optional_oneof_string_field_1());
+ EXPECT_FALSE(message.has_optional_oneof_string_field());
+ EXPECT_FALSE(message.has_optional_oneof_message_field());
+
+ EXPECT_EQ(std::ssize(message.map_field()), 2);
+ ASSERT_EQ(static_cast<int>(message.map_field().count(2)), 1);
+ EXPECT_EQ(message.map_field().at(2).key(), "x");
+ EXPECT_EQ(message.map_field().at(2).value(), "y");
+ ASSERT_EQ(static_cast<int>(message.map_field().count(5)), 1);
+ EXPECT_EQ(message.map_field().at(5).key(), "z");
+ EXPECT_EQ(message.map_field().at(5).value(), "w");
+ }
+
+ ASSERT_FALSE(lenvalParser.Next());
+}
+
+INodePtr SortMapByKey(const INodePtr& node)
+{
+ auto keyValuePairs = ConvertTo<std::vector<std::pair<i64, INodePtr>>>(node);
+ std::sort(std::begin(keyValuePairs), std::end(keyValuePairs));
+ return ConvertTo<INodePtr>(keyValuePairs);
+}
+
+TEST_P(TProtobufFormatStructuredMessage, EmbeddedParse)
+{
+ auto [complexTypeMode, rowCount, protoFormatType] = GetParam();
+
+ auto schema = BuildEmbeddedSchema();
+ auto config = BuildEmbeddedConfig(complexTypeMode, protoFormatType);
+
+ NYT::TEmbeddingMessage message;
+
+ message.set_num(789);
+ auto* t1 = message.mutable_t1();
+ t1->set_embedded_num(123);
+ auto* t2 = t1->mutable_t2();
+ t2->set_embedded2_num(456);
+ t1->set_uint_variant(555);
+ t2->add_embedded2_repeated("a");
+ t2->add_embedded2_repeated("b");
+ t2->add_embedded2_repeated("c");
+ auto* embedded2_struct = t2->mutable_embedded2_struct();
+ embedded2_struct->set_float1(1.5f);
+ embedded2_struct->set_string1("abc");
+
+ //message.set_extra_field("*");
+ //t1->set_embedded_extra_field("*");
+
+ auto rowCollector = ParseRows(message, config, schema, rowCount);
+ for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
+ EXPECT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "num")), 789u);
+ EXPECT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "embedded_num")), 123u);
+ EXPECT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "embedded2_num")), 456u);
+ EXPECT_NODES_EQUAL(
+ GetComposite(rowCollector.GetRowValue(rowIndex, "variant")),
+ ConvertToNode(TYsonString(TStringBuf("[1; 555u]"))));
+
+ auto embedded2_repeatedNode = GetComposite(rowCollector.GetRowValue(rowIndex, "embedded2_repeated"));
+ ASSERT_EQ(embedded2_repeatedNode->GetType(), ENodeType::List);
+ const auto& embedded2_repeatedList = embedded2_repeatedNode->AsList();
+ ASSERT_EQ(embedded2_repeatedList->GetChildCount(), 3);
+ EXPECT_EQ(embedded2_repeatedList->GetChildValueOrThrow<TString>(0), "a");
+ EXPECT_EQ(embedded2_repeatedList->GetChildValueOrThrow<TString>(1), "b");
+ EXPECT_EQ(embedded2_repeatedList->GetChildValueOrThrow<TString>(2), "c");
+
+ auto embedded2_structNode = GetComposite(rowCollector.GetRowValue(rowIndex, "embedded2_struct"));
+ ASSERT_EQ(embedded2_structNode->GetType(), ENodeType::List);
+ const auto& embedded2_structList = embedded2_structNode->AsList();
+ ASSERT_EQ(embedded2_structList->GetChildCount(), 2);
+ EXPECT_EQ(embedded2_structList->GetChildValueOrThrow<double>(0), 1.5f);
+ EXPECT_EQ(embedded2_structList->GetChildValueOrThrow<TString>(1), "abc");
+ }
+}
+
+TEST_P(TProtobufFormatStructuredMessage, Parse)
+{
+ auto [complexTypeMode, rowCount, protoFormatType] = GetParam();
+
+ auto schema = CreateSchemaWithStructuredMessage();
+ auto config = CreateConfigWithStructuredMessage(complexTypeMode, protoFormatType);
+
+ NYT::TMessageWithStructuredEmbedded message;
+
+ auto* first = message.mutable_first();
+ first->set_enum_field(EEnum::Two);
+ first->set_int64_field(44);
+
+ first->add_repeated_int64_field(55);
+ first->add_repeated_int64_field(56);
+ first->add_repeated_int64_field(57);
+
+ // another_repeated_int64_field is intentionally empty.
+
+ first->mutable_message_field()->set_key("key");
+ first->mutable_message_field()->set_value("value");
+ auto* firstSubfield1 = first->add_repeated_message_field();
+ firstSubfield1->set_key("key1");
+ firstSubfield1->set_value("value1");
+ auto* firstSubfield2 = first->add_repeated_message_field();
+ firstSubfield2->set_key("key2");
+ firstSubfield2->set_value("value2");
+
+ first->set_any_int64_field(BuildYsonStringFluently().Value(4422).ToString());
+ first->set_any_map_field(
+ BuildYsonStringFluently()
+ .BeginMap()
+ .Item("key").Value("value")
+ .EndMap()
+ .ToString());
+
+ first->add_repeated_optional_any_field("%false");
+ first->add_repeated_optional_any_field("42");
+ first->add_repeated_optional_any_field("#");
+
+ first->add_packed_repeated_enum_field(EEnum::MaxInt32);
+ first->add_packed_repeated_enum_field(EEnum::MinusFortyTwo);
+
+ // optional_repeated_bool_field is intentionally empty.
+
+ first->mutable_oneof_message_field()->set_key("KEY");
+
+ // optional_oneof_field is intentionally empty.
+
+ (*first->mutable_map_field())[111].set_key("key111");
+ (*first->mutable_map_field())[111].set_value("value111");
+ (*first->mutable_map_field())[222].set_key("key222");
+ (*first->mutable_map_field())[222].set_value("value222");
+
+ auto* second = message.mutable_second();
+ second->set_one(101);
+ second->set_two(102);
+ second->set_three(103);
+
+ message.add_repeated_int64_field(31);
+ message.add_repeated_int64_field(32);
+ message.add_repeated_int64_field(33);
+
+ // another_repeated_int64_field is intentionally empty.
+
+ auto* subfield1 = message.add_repeated_message_field();
+ subfield1->set_key("key11");
+ subfield1->set_value("value11");
+ auto* subfield2 = message.add_repeated_message_field();
+ subfield2->set_key("key21");
+ subfield2->set_value("value21");
+
+ message.set_int64_any_field(4321);
+
+ // Note the reversal of 32 <-> 64.
+ message.set_int64_field(-32);
+ message.set_uint64_field(32);
+ message.set_int32_field(-64);
+ message.set_uint32_field(64);
+
+ // Note that we don't set the "enum_string_int64_field" as it would fail during parsing.
+ message.set_enum_int_field(EEnum::MinusFortyTwo);
+ message.set_enum_string_string_field(EEnum::Three);
+
+ const auto HelloWorldInChinese = "\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c";
+ message.set_utf8_field(HelloWorldInChinese);
+
+ message.add_repeated_optional_any_field("#");
+ message.add_repeated_optional_any_field("1");
+ message.add_repeated_optional_any_field("\"qwe\"");
+ message.add_repeated_optional_any_field("%true");
+
+ auto otherComplexFieldPositional = BuildYsonNodeFluently()
+ .BeginList()
+ .Item().Value(301)
+ .Item().Value(302)
+ .Item().Value(303)
+ .EndList();
+
+ auto mode = complexTypeMode;
+ auto otherComplexField = ([&] {
+ switch (mode) {
+ case EComplexTypeMode::Named:
+ return BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("one").Value(301)
+ .Item("two").Value(302)
+ .Item("three").Value(303)
+ .EndMap();
+ case EComplexTypeMode::Positional:
+ return otherComplexFieldPositional;
+ }
+ YT_ABORT();
+ })();
+ auto otherColumnsYson = BuildYsonStringFluently()
+ .BeginMap()
+ .Item("other_complex_field").Value(otherComplexField)
+ .EndMap();
+ message.set_other_columns_field(otherColumnsYson.ToString());
+
+ message.add_packed_repeated_int64_field(-123456789000LL);
+ message.add_packed_repeated_int64_field(0);
+
+ message.add_optional_repeated_int64_field(-4242);
+
+ // optional_oneof_field is intentionally empty.
+
+ message.set_oneof_string_field("spam");
+
+ (*message.mutable_map_field())[777].set_key("key777");
+ (*message.mutable_map_field())[777].set_value("value777");
+ (*message.mutable_map_field())[888].set_key("key888");
+ (*message.mutable_map_field())[888].set_value("value888");
+
+ auto rowCollector = ParseRows(message, config, schema, rowCount);
+ for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
+ auto firstNode = GetComposite(rowCollector.GetRowValue(rowIndex, "first"));
+ ASSERT_EQ(firstNode->GetType(), ENodeType::List);
+ const auto& firstList = firstNode->AsList();
+ ASSERT_EQ(firstList->GetChildCount(), 17);
+
+ EXPECT_EQ(firstList->GetChildOrThrow(0)->GetType(), ENodeType::Entity);
+ EXPECT_EQ(firstList->GetChildValueOrThrow<TString>(1), "Two");
+ EXPECT_EQ(firstList->GetChildValueOrThrow<i64>(2), 44);
+
+ ASSERT_EQ(firstList->GetChildOrThrow(3)->GetType(), ENodeType::List);
+ EXPECT_EQ(ConvertTo<std::vector<i64>>(firstList->GetChildOrThrow(3)), (std::vector<i64>{55, 56, 57}));
+
+ ASSERT_EQ(firstList->GetChildOrThrow(4)->GetType(), ENodeType::List);
+ EXPECT_EQ(ConvertTo<std::vector<i64>>(firstList->GetChildOrThrow(4)), (std::vector<i64>{}));
+
+ ASSERT_EQ(firstList->GetChildOrThrow(5)->GetType(), ENodeType::List);
+ EXPECT_EQ(firstList->GetChildOrThrow(5)->AsList()->GetChildValueOrThrow<TString>(0), "key");
+ EXPECT_EQ(firstList->GetChildOrThrow(5)->AsList()->GetChildValueOrThrow<TString>(1), "value");
+
+ ASSERT_EQ(firstList->GetChildOrThrow(6)->GetType(), ENodeType::List);
+ ASSERT_EQ(firstList->GetChildOrThrow(6)->AsList()->GetChildCount(), 2);
+
+ const auto& firstSubNode1 = firstList->GetChildOrThrow(6)->AsList()->GetChildOrThrow(0);
+ ASSERT_EQ(firstSubNode1->GetType(), ENodeType::List);
+ ASSERT_EQ(firstSubNode1->AsList()->GetChildCount(), 2);
+ EXPECT_EQ(firstSubNode1->AsList()->GetChildValueOrThrow<TString>(0), "key1");
+ EXPECT_EQ(firstSubNode1->AsList()->GetChildValueOrThrow<TString>(1), "value1");
+
+ const auto& firstSubNode2 = firstList->GetChildOrThrow(6)->AsList()->GetChildOrThrow(1);
+ ASSERT_EQ(firstSubNode2->GetType(), ENodeType::List);
+ ASSERT_EQ(firstSubNode2->AsList()->GetChildCount(), 2);
+ EXPECT_EQ(firstSubNode2->AsList()->GetChildValueOrThrow<TString>(0), "key2");
+ EXPECT_EQ(firstSubNode2->AsList()->GetChildValueOrThrow<TString>(1), "value2");
+
+ ASSERT_EQ(firstList->GetChildOrThrow(7)->GetType(), ENodeType::Int64);
+ EXPECT_EQ(firstList->GetChildValueOrThrow<i64>(7), 4422);
+
+ ASSERT_EQ(firstList->GetChildOrThrow(8)->GetType(), ENodeType::Map);
+ EXPECT_NODES_EQUAL(
+ firstList->GetChildOrThrow(8),
+ BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("key").Value("value")
+ .EndMap());
+
+ ASSERT_EQ(firstList->GetChildOrThrow(9)->GetType(), ENodeType::Entity);
+
+ EXPECT_NODES_EQUAL(
+ firstList->GetChildOrThrow(10),
+ BuildYsonNodeFluently()
+ .BeginList()
+ .Item().Value(false)
+ .Item().Value(42)
+ .Item().Entity()
+ .EndList());
+
+ EXPECT_NODES_EQUAL(
+ firstList->GetChildOrThrow(11),
+ BuildYsonNodeFluently()
+ .BeginList()
+ .Item().Value("MaxInt32")
+ .Item().Value("MinusFortyTwo")
+ .EndList());
+
+ // optional_repeated_bool_field.
+ ASSERT_EQ(firstList->GetChildOrThrow(12)->GetType(), ENodeType::Entity);
+
+ // oneof_field.
+ EXPECT_NODES_EQUAL(
+ firstList->GetChildOrThrow(13),
+ BuildYsonNodeFluently()
+ .BeginList()
+ .Item().Value(2)
+ .Item().BeginList()
+ .Item().Value("KEY")
+ .Item().Entity()
+ .EndList()
+ .EndList());
+
+ // optional_oneof_field.
+ ASSERT_EQ(firstList->GetChildOrThrow(14)->GetType(), ENodeType::Entity);
+
+ // map_field.
+ EXPECT_NODES_EQUAL(
+ SortMapByKey(firstList->GetChildOrThrow(15)),
+ BuildYsonNodeFluently()
+ .BeginList()
+ .Item().BeginList()
+ .Item().Value(111)
+ .Item().BeginList()
+ .Item().Value("key111")
+ .Item().Value("value111")
+ .EndList()
+ .EndList()
+ .Item().BeginList()
+ .Item().Value(222)
+ .Item().BeginList()
+ .Item().Value("key222")
+ .Item().Value("value222")
+ .EndList()
+ .EndList()
+ .EndList());
+
+ // field_missing_from_proto2.
+ ASSERT_EQ(firstList->GetChildOrThrow(16)->GetType(), ENodeType::Entity);
+
+ auto secondNode = GetComposite(rowCollector.GetRowValue(rowIndex, "second"));
+ ASSERT_EQ(secondNode->GetType(), ENodeType::List);
+ EXPECT_EQ(ConvertTo<std::vector<i64>>(secondNode), (std::vector<i64>{101, 102, 103}));
+
+ auto repeatedMessageNode = GetComposite(rowCollector.GetRowValue(rowIndex, "repeated_message_field"));
+ ASSERT_EQ(repeatedMessageNode->GetType(), ENodeType::List);
+ ASSERT_EQ(repeatedMessageNode->AsList()->GetChildCount(), 2);
+
+ const auto& subNode1 = repeatedMessageNode->AsList()->GetChildOrThrow(0);
+ ASSERT_EQ(subNode1->GetType(), ENodeType::List);
+ ASSERT_EQ(subNode1->AsList()->GetChildCount(), 2);
+ EXPECT_EQ(subNode1->AsList()->GetChildValueOrThrow<TString>(0), "key11");
+ EXPECT_EQ(subNode1->AsList()->GetChildValueOrThrow<TString>(1), "value11");
+
+ const auto& subNode2 = repeatedMessageNode->AsList()->GetChildOrThrow(1);
+ ASSERT_EQ(subNode2->GetType(), ENodeType::List);
+ ASSERT_EQ(subNode2->AsList()->GetChildCount(), 2);
+ EXPECT_EQ(subNode2->AsList()->GetChildValueOrThrow<TString>(0), "key21");
+ EXPECT_EQ(subNode2->AsList()->GetChildValueOrThrow<TString>(1), "value21");
+
+ auto repeatedInt64Node = GetComposite(rowCollector.GetRowValue(rowIndex, "repeated_int64_field"));
+ EXPECT_EQ(ConvertTo<std::vector<i64>>(repeatedInt64Node), (std::vector<i64>{31, 32, 33}));
+
+ auto anotherRepeatedInt64Node = GetComposite(rowCollector.GetRowValue(rowIndex, "another_repeated_int64_field"));
+ EXPECT_EQ(ConvertTo<std::vector<i64>>(anotherRepeatedInt64Node), (std::vector<i64>{}));
+
+ auto anyValue = rowCollector.GetRowValue(rowIndex, "any_field");
+ ASSERT_EQ(anyValue.Type, EValueType::Int64);
+ EXPECT_EQ(anyValue.Data.Int64, 4321);
+
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "int64_field")), -64);
+ EXPECT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "uint64_field")), 64u);
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "int32_field")), -32);
+ EXPECT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "uint32_field")), 32u);
+
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "enum_int_field")), -42);
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(rowIndex, "enum_string_string_field")), "Three");
+
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(rowIndex, "utf8_field")), HelloWorldInChinese);
+
+ auto repeatedRepeatedOptionalAnyNode = GetComposite(rowCollector.GetRowValue(rowIndex, "repeated_optional_any_field"));
+ auto expectedRepeatedOptionalAnyNode = BuildYsonNodeFluently()
+ .BeginList()
+ .Item().Entity()
+ .Item().Value(1)
+ .Item().Value("qwe")
+ .Item().Value(true)
+ .EndList();
+ EXPECT_NODES_EQUAL(repeatedRepeatedOptionalAnyNode, expectedRepeatedOptionalAnyNode);
+
+ auto actualOtherComplexField = GetComposite(rowCollector.GetRowValue(rowIndex, "other_complex_field"));
+ EXPECT_NODES_EQUAL(actualOtherComplexField, otherComplexFieldPositional);
+
+ EXPECT_NODES_EQUAL(
+ GetComposite(rowCollector.GetRowValue(rowIndex, "packed_repeated_int64_field")),
+ ConvertToNode(TYsonString(TStringBuf("[-123456789000;0]"))));
+
+ EXPECT_NODES_EQUAL(
+ GetComposite(rowCollector.GetRowValue(rowIndex, "optional_repeated_int64_field")),
+ ConvertToNode(TYsonString(TStringBuf("[-4242]"))));
+
+ EXPECT_NODES_EQUAL(
+ GetComposite(rowCollector.GetRowValue(rowIndex, "oneof_field")),
+ ConvertToNode(TYsonString(TStringBuf("[1; \"spam\"]"))));
+
+ EXPECT_FALSE(rowCollector.FindRowValue(rowIndex, "optional_oneof_field"));
+
+ // map_field.
+ EXPECT_NODES_EQUAL(
+ SortMapByKey(GetComposite(rowCollector.GetRowValue(rowIndex, "map_field"))),
+ ConvertToNode(TYsonString(TStringBuf("[[777; [key777; value777]]; [888; [key888; value888]]]"))));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::vector<TTableSchemaPtr> CreateSeveralTablesSchemas()
+{
+ return {
+ New<TTableSchema>(std::vector<TColumnSchema>{
+ {"embedded", StructLogicalType({
+ {"enum_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"int64_field", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ })},
+ {"repeated_int64_field", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ {"any_field", SimpleLogicalType(ESimpleLogicalValueType::Any)},
+ }),
+ New<TTableSchema>(std::vector<TColumnSchema>{
+ {"enum_field", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"int64_field", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ }),
+ // Empty schema.
+ New<TTableSchema>(),
+ };
+}
+
+INodePtr CreateSeveralTablesConfig(EProtoFormatType protoFormatType)
+{
+ if (protoFormatType == EProtoFormatType::FileDescriptor) {
+ return CreateFileDescriptorConfig<TSeveralTablesMessageFirst, TSeveralTablesMessageSecond, TSeveralTablesMessageThird>();
+ }
+ YT_VERIFY(protoFormatType == EProtoFormatType::Structured);
+
+ return BuildYsonNodeFluently()
+ .BeginAttributes()
+ .Item("enumerations").Value(EnumerationsConfig)
+ .Item("tables")
+ .BeginList()
+ // Table #1.
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("embedded")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("enum_field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("enum_string")
+ .Item("enumeration_name").Value("EEnum")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("repeated_int64_field")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .Item("repeated").Value(true)
+ .EndMap()
+ .Item()
+ .BeginMap()
+ // In schema it is of type "any".
+ .Item("name").Value("any_field")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+
+ // Table #2.
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("enum_field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("enum_string")
+ .Item("enumeration_name").Value("EEnum")
+ .EndMap()
+ .EndList()
+ .EndMap()
+
+ // Table #3.
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("string_field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndAttributes()
+ .Value("protobuf");
+}
+
+using TProtobufFormatSeveralTablesParam = std::tuple<EProtoFormatType>;
+
+class TProtobufFormatSeveralTables
+ : public ::testing::TestWithParam<TProtobufFormatSeveralTablesParam>
+{ };
+
+INSTANTIATE_TEST_SUITE_P(
+ FileDescriptor,
+ TProtobufFormatSeveralTables,
+ ::testing::Values(TProtobufFormatSeveralTablesParam{
+ EProtoFormatType::FileDescriptor}));
+
+INSTANTIATE_TEST_SUITE_P(
+ Structured,
+ TProtobufFormatSeveralTables,
+ ::testing::Values(TProtobufFormatSeveralTablesParam{
+ EProtoFormatType::Structured}));
+
+TEST_P(TProtobufFormatSeveralTables, Write)
+{
+ auto [protoFormatType] = GetParam();
+
+ auto schemas = CreateSeveralTablesSchemas();
+ auto configNode = CreateSeveralTablesConfig(protoFormatType);
+
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(configNode->Attributes().ToMap());
+
+ auto nameTable = New<TNameTable>();
+ auto embeddedId = nameTable->RegisterName("embedded");
+ auto anyFieldId = nameTable->RegisterName("any_field");
+ auto int64FieldId = nameTable->RegisterName("int64_field");
+ auto repeatedInt64Id = nameTable->RegisterName("repeated_int64_field");
+ auto enumFieldId = nameTable->RegisterName("enum_field");
+ auto stringFieldId = nameTable->RegisterName("string_field");
+ auto tableIndexId = nameTable->RegisterName(TableIndexColumnName);
+
+ TString result;
+ TStringOutput resultStream(result);
+ auto controlAttributesConfig = New<TControlAttributesConfig>();
+ controlAttributesConfig->EnableTableIndex = true;
+ controlAttributesConfig->EnableEndOfStream = true;
+ auto writer = CreateWriterForProtobuf(
+ std::move(config),
+ schemas,
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ std::move(controlAttributesConfig),
+ 0);
+
+ auto embeddedYson = BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value("Two")
+ .Item().Value(44)
+ .EndList()
+ .ToString();
+
+ auto repeatedInt64Yson = ConvertToYsonString(std::vector<i64>{31, 32, 33}).ToString();
+
+ {
+ TUnversionedRowBuilder builder;
+ builder.AddValue(MakeUnversionedCompositeValue(embeddedYson, embeddedId));
+ builder.AddValue(MakeUnversionedCompositeValue(repeatedInt64Yson, repeatedInt64Id));
+ builder.AddValue(MakeUnversionedInt64Value(4321, anyFieldId));
+ writer->Write({builder.GetRow()});
+ }
+ {
+ TUnversionedRowBuilder builder;
+ builder.AddValue(MakeUnversionedStringValue("Two", enumFieldId));
+ builder.AddValue(MakeUnversionedInt64Value(999, int64FieldId));
+ builder.AddValue(MakeUnversionedInt64Value(1, tableIndexId));
+ writer->Write({builder.GetRow()});
+ }
+ {
+ TUnversionedRowBuilder builder;
+ builder.AddValue(MakeUnversionedStringValue("blah", stringFieldId));
+ builder.AddValue(MakeUnversionedInt64Value(2, tableIndexId));
+ writer->Write({builder.GetRow()});
+ }
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput input(result);
+ TLenvalParser lenvalParser(&input);
+
+ {
+ auto entry = lenvalParser.Next();
+ ASSERT_TRUE(entry);
+
+ NYT::TSeveralTablesMessageFirst message;
+ ASSERT_TRUE(message.ParseFromString(entry->RowData));
+
+ const auto& embedded = message.embedded();
+ EXPECT_EQ(embedded.enum_field(), EEnum::Two);
+ EXPECT_EQ(embedded.int64_field(), 44);
+
+ std::vector<i64> repeatedInt64Field(
+ message.repeated_int64_field().begin(),
+ message.repeated_int64_field().end());
+ EXPECT_EQ(repeatedInt64Field, (std::vector<i64>{31, 32, 33}));
+ EXPECT_EQ(message.int64_field(), 4321);
+ }
+ {
+ auto entry = lenvalParser.Next();
+ ASSERT_TRUE(entry);
+
+ NYT::TSeveralTablesMessageSecond message;
+ ASSERT_TRUE(message.ParseFromString(entry->RowData));
+
+ EXPECT_EQ(message.enum_field(), EEnum::Two);
+ EXPECT_EQ(message.int64_field(), 999);
+ }
+ {
+ auto entry = lenvalParser.Next();
+ ASSERT_TRUE(entry);
+
+ NYT::TSeveralTablesMessageThird message;
+ ASSERT_TRUE(message.ParseFromString(entry->RowData));
+
+ EXPECT_EQ(message.string_field(), "blah");
+ }
+ ASSERT_FALSE(lenvalParser.IsEndOfStream());
+ ASSERT_FALSE(lenvalParser.Next());
+ ASSERT_TRUE(lenvalParser.IsEndOfStream());
+ ASSERT_FALSE(lenvalParser.Next());
+}
+
+TEST_P(TProtobufFormatSeveralTables, Parse)
+{
+ auto [protoFormatType] = GetParam();
+
+ auto schemas = CreateSeveralTablesSchemas();
+ auto configNode = CreateSeveralTablesConfig(protoFormatType);
+ auto config = ConvertTo<TProtobufFormatConfigPtr>(configNode->Attributes().ToMap());
+
+ std::vector<TCollectingValueConsumer> rowCollectors;
+ std::vector<std::unique_ptr<IParser>> parsers;
+ for (const auto& schema : schemas) {
+ rowCollectors.emplace_back(schema);
+ }
+ for (int tableIndex = 0; tableIndex < static_cast<int>(schemas.size()); ++tableIndex) {
+ parsers.push_back(CreateParserForProtobuf(
+ &rowCollectors[tableIndex],
+ config,
+ tableIndex));
+ }
+
+ NYT::TSeveralTablesMessageFirst firstMessage;
+ auto* embedded = firstMessage.mutable_embedded();
+ embedded->set_enum_field(EEnum::Two);
+ embedded->set_int64_field(44);
+
+ firstMessage.add_repeated_int64_field(55);
+ firstMessage.add_repeated_int64_field(56);
+ firstMessage.add_repeated_int64_field(57);
+
+ firstMessage.set_int64_field(4444);
+
+ NYT::TSeveralTablesMessageSecond secondMessage;
+ secondMessage.set_enum_field(EEnum::Two);
+ secondMessage.set_int64_field(44);
+
+ NYT::TSeveralTablesMessageThird thirdMessage;
+ thirdMessage.set_string_field("blah");
+
+ auto parse = [] (auto& parser, const auto& message) {
+ TString lenvalBytes;
+ {
+ TStringOutput out(lenvalBytes);
+ auto messageSize = static_cast<ui32>(message.ByteSizeLong());
+ out.Write(&messageSize, sizeof(messageSize));
+ ASSERT_TRUE(message.SerializeToArcadiaStream(&out));
+ }
+ parser->Read(lenvalBytes);
+ parser->Finish();
+ };
+
+ parse(parsers[0], firstMessage);
+ parse(parsers[1], secondMessage);
+ parse(parsers[2], thirdMessage);
+
+ {
+ const auto& rowCollector = rowCollectors[0];
+ ASSERT_EQ(static_cast<int>(rowCollector.Size()), 1);
+
+ auto embeddedNode = GetComposite(rowCollector.GetRowValue(0, "embedded"));
+ ASSERT_EQ(ConvertToTextYson(embeddedNode), "[\"Two\";44;]");
+
+ auto repeatedInt64Node = GetComposite(rowCollector.GetRowValue(0, "repeated_int64_field"));
+ ASSERT_EQ(ConvertToTextYson(repeatedInt64Node), "[55;56;57;]");
+
+ auto int64Field = GetInt64(rowCollector.GetRowValue(0, "any_field"));
+ EXPECT_EQ(int64Field, 4444);
+ }
+
+ {
+ const auto& rowCollector = rowCollectors[1];
+ ASSERT_EQ(static_cast<int>(rowCollector.Size()), 1);
+
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(0, "enum_field")), "Two");
+ EXPECT_EQ(GetInt64(rowCollector.GetRowValue(0, "int64_field")), 44);
+ }
+
+ {
+ const auto& rowCollector = rowCollectors[2];
+ ASSERT_EQ(static_cast<int>(rowCollector.Size()), 1);
+
+ EXPECT_EQ(GetString(rowCollector.GetRowValue(0, "string_field")), "blah");
+ }
+}
+
+TEST(TProtobufFormat, SchemaConfigMismatch)
+{
+ auto createParser = [] (const TTableSchemaPtr& schema, const INodePtr& configNode) {
+ TCollectingValueConsumer rowCollector(schema);
+ return CreateParserForProtobuf(
+ &rowCollector,
+ ConvertTo<TProtobufFormatConfigPtr>(configNode),
+ 0);
+ };
+ auto createSeveralTableWriter = [] (const std::vector<TTableSchemaPtr>& schemas, const INodePtr& configNode) {
+ TString result;
+ TStringOutput resultStream(result);
+ return CreateWriterForProtobuf(
+ ConvertTo<TProtobufFormatConfigPtr>(configNode),
+ schemas,
+ New<TNameTable>(),
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+ };
+ auto createWriter = [&] (const TTableSchemaPtr& schema, const INodePtr& configNode) {
+ createSeveralTableWriter({schema}, configNode);
+ };
+
+ auto schema_struct_with_int64 = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"struct", StructLogicalType({
+ {"int64_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ })},
+ });
+
+ auto schema_struct_with_uint64 = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"struct", StructLogicalType({
+ {"int64_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint64))},
+ })},
+ });
+
+ auto config_struct_with_int64 = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("struct")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(2)
+ // Wrong type.
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ // OK.
+ EXPECT_NO_THROW(createParser(schema_struct_with_int64, config_struct_with_int64));
+ EXPECT_NO_THROW(createWriter(schema_struct_with_int64, config_struct_with_int64));
+
+ // Types mismatch.
+ EXPECT_THROW_WITH_SUBSTRING(
+ createParser(schema_struct_with_uint64, config_struct_with_int64),
+ "signedness of both types must be the same");
+ EXPECT_THROW_WITH_SUBSTRING(
+ createWriter(schema_struct_with_uint64, config_struct_with_int64),
+ "signedness of both types must be the same");
+
+ // No schema for structured field is Ok.
+ EXPECT_NO_THROW(createParser(New<TTableSchema>(), config_struct_with_int64));
+ EXPECT_NO_THROW(createWriter(New<TTableSchema>(), config_struct_with_int64));
+
+ auto schema_list_int64 = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"repeated", ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ )},
+ });
+
+ auto schema_list_optional_int64 = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"repeated", ListLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))
+ )},
+ });
+
+ auto config_repeated_int64 = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("repeated")
+ .Item("field_number").Value(1)
+ .Item("repeated").Value(true)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ // OK.
+ EXPECT_NO_THROW(createParser(schema_list_int64, config_repeated_int64));
+ EXPECT_NO_THROW(createWriter(schema_list_int64, config_repeated_int64));
+
+ // No schema for repeated field is Ok.
+ EXPECT_NO_THROW(createParser(New<TTableSchema>(), config_repeated_int64));
+ EXPECT_NO_THROW(createWriter(New<TTableSchema>(), config_repeated_int64));
+
+ // List of optional is not allowed.
+ EXPECT_THROW_WITH_SUBSTRING(
+ createParser(schema_list_optional_int64, config_repeated_int64),
+ "unexpected logical metatype \"optional\"");
+ EXPECT_THROW_WITH_SUBSTRING(
+ createWriter(schema_list_optional_int64, config_repeated_int64),
+ "unexpected logical metatype \"optional\"");
+
+ auto schema_optional_list_int64 = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"repeated", OptionalLogicalType(
+ ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))
+ )},
+ });
+
+ // Optional list is OK.
+ EXPECT_NO_THROW(createParser(schema_optional_list_int64, config_repeated_int64));
+ EXPECT_NO_THROW(createWriter(schema_optional_list_int64, config_repeated_int64));
+
+ auto schema_optional_optional_int64 = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"field", OptionalLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))
+ )},
+ });
+
+ auto config_int64 = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ // Optional of optional is not allowed.
+ EXPECT_THROW_WITH_SUBSTRING(
+ createParser(schema_optional_optional_int64, config_int64),
+ "unexpected logical metatype \"optional\"");
+ EXPECT_THROW_WITH_SUBSTRING(
+ createWriter(schema_optional_optional_int64, config_int64),
+ "unexpected logical metatype \"optional\"");
+
+ auto schema_struct_with_both = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"struct", StructLogicalType({
+ {"required_field", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"optional_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ })},
+ });
+
+ auto config_struct_with_required = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("struct")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("required_field")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto config_struct_with_optional = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("struct")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("optional_field")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ auto config_struct_with_unknown = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("struct")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("required_field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("optional_field")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("unknown_field")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ // Schema has more fields, non-optional field is missing in protobuf config.
+ // Parser should fail.
+ EXPECT_THROW_WITH_SUBSTRING(
+ createParser(schema_struct_with_both, config_struct_with_optional),
+ "non-optional field \"required_field\" in schema is missing from protobuf config");
+ // Writer feels OK.
+ EXPECT_NO_THROW(createWriter(schema_struct_with_both, config_struct_with_optional));
+
+ // Schema has more fields, optional field is missing in protobuf config.
+ // It's OK for both the writer and the parser.
+ EXPECT_NO_THROW(createParser(schema_struct_with_both, config_struct_with_required));
+ EXPECT_NO_THROW(createWriter(schema_struct_with_both, config_struct_with_required));
+
+ // Protobuf config has more fields, it is always OK.
+ EXPECT_NO_THROW(createParser(schema_struct_with_both, config_struct_with_unknown));
+ EXPECT_NO_THROW(createWriter(schema_struct_with_both, config_struct_with_unknown));
+
+ auto schema_int64 = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"int64_field", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ });
+
+ auto config_two_tables = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("int64_field")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ EXPECT_NO_THROW(createWriter(schema_int64, config_two_tables));
+ EXPECT_THROW_WITH_SUBSTRING(
+ createSeveralTableWriter({schema_int64, schema_int64, schema_int64}, config_two_tables),
+ "Number of schemas is greater than number of tables in protobuf config: 3 > 2");
+
+ auto schema_variant_with_int = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"variant", VariantStructLogicalType({
+ {"a", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ })},
+ });
+ auto schema_variant_with_optional_int = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"variant", VariantStructLogicalType({
+ {"a", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ })},
+ });
+
+ auto config_with_oneof = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("tables")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("columns")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("variant")
+ .Item("proto_type").Value("oneof")
+ .Item("fields").BeginList()
+ .Item()
+ .BeginMap()
+ .Item("name").Value("a")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap();
+
+ // Oneof fields require schematized columns.
+ EXPECT_THROW_WITH_SUBSTRING(
+ createParser(New<TTableSchema>(), config_with_oneof),
+ "requires a corresponding schematized column");
+ EXPECT_THROW_WITH_SUBSTRING(
+ createWriter(New<TTableSchema>(), config_with_oneof),
+ "requires a corresponding schematized column");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ createParser(schema_variant_with_optional_int, config_with_oneof),
+ "Optional variant field \"variant.a\"");
+ EXPECT_THROW_WITH_SUBSTRING(
+ createWriter(schema_variant_with_optional_int, config_with_oneof),
+ "Optional variant field \"variant.a\"");
+ EXPECT_NO_THROW(createParser(schema_variant_with_int, config_with_oneof));
+ EXPECT_NO_THROW(createWriter(schema_variant_with_int, config_with_oneof));
+}
+
+TEST(TProtobufFormat, MultipleOtherColumns)
+{
+ auto nameTable = New<TNameTable>();
+
+ TString data;
+ TStringOutput resultStream(data);
+
+ auto controlAttributesConfig = New<TControlAttributesConfig>();
+ controlAttributesConfig->EnableTableIndex = true;
+ controlAttributesConfig->EnableEndOfStream = true;
+
+ auto protoWriter = CreateWriterForProtobuf(
+ MakeProtobufFormatConfig({TOtherColumnsMessage::descriptor(), TOtherColumnsMessage::descriptor()}),
+ std::vector<TTableSchemaPtr>(2, New<TTableSchema>()),
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ controlAttributesConfig,
+ 0);
+
+ protoWriter->Write(
+ std::vector<TUnversionedRow>{
+ NNamedValue::MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"field1", "foo"},
+ }),
+ NNamedValue::MakeRow(nameTable, {
+ {TableIndexColumnName, 1},
+ {"field2", "bar"},
+ }),
+ }
+ );
+ WaitFor(protoWriter->Close())
+ .ThrowOnError();
+
+ std::vector<TString> otherColumnsValue;
+ auto parser = TLenvalParser(data);
+ while (auto item = parser.Next()) {
+ TOtherColumnsMessage message;
+ bool parsed = message.ParseFromString(item->RowData);
+ EXPECT_TRUE(parsed);
+ otherColumnsValue.push_back(CanonizeYson(message.other_columns_field()));
+ }
+
+ EXPECT_EQ(
+ otherColumnsValue,
+ std::vector<TString>({
+ CanonizeYson("{field1=foo}"),
+ CanonizeYson("{field2=bar}"),
+ }));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TProtobufFormatAllFieldsParameter = std::tuple<int, EProtoFormatType>;
+class TProtobufFormatAllFields
+ : public ::testing::TestWithParam<TProtobufFormatAllFieldsParameter>
+{
+public:
+ bool IsLegacyFormat() const
+ {
+ auto [rowCount, protoFormatType] = GetParam();
+ return protoFormatType == EProtoFormatType::FileDescriptorLegacy;
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ Specification,
+ TProtobufFormatAllFields,
+ ::testing::Values(TProtobufFormatAllFieldsParameter{1, EProtoFormatType::Structured}));
+
+INSTANTIATE_TEST_SUITE_P(
+ FileDescriptorLegacy,
+ TProtobufFormatAllFields,
+ ::testing::Values(TProtobufFormatAllFieldsParameter{1, EProtoFormatType::FileDescriptorLegacy}));
+
+INSTANTIATE_TEST_SUITE_P(
+ FileDescriptor,
+ TProtobufFormatAllFields,
+ ::testing::Values(TProtobufFormatAllFieldsParameter{1, EProtoFormatType::FileDescriptor}));
+
+INSTANTIATE_TEST_SUITE_P(
+ ManyRows,
+ TProtobufFormatAllFields,
+ ::testing::Values(TProtobufFormatAllFieldsParameter{50000, EProtoFormatType::Structured}));
+
+TEST_P(TProtobufFormatAllFields, Writer)
+{
+ auto [rowCount, protoFormatType] = GetParam();
+ auto config = CreateAllFieldsConfig(protoFormatType);
+
+ auto nameTable = New<TNameTable>();
+
+ auto doubleId = nameTable->RegisterName("Double");
+ auto floatId = nameTable->RegisterName("Float");
+
+ auto int64Id = nameTable->RegisterName("Int64");
+ auto uint64Id = nameTable->RegisterName("UInt64");
+ auto sint64Id = nameTable->RegisterName("SInt64");
+ auto fixed64Id = nameTable->RegisterName("Fixed64");
+ auto sfixed64Id = nameTable->RegisterName("SFixed64");
+
+ auto int32Id = nameTable->RegisterName("Int32");
+ auto uint32Id = nameTable->RegisterName("UInt32");
+ auto sint32Id = nameTable->RegisterName("SInt32");
+ auto fixed32Id = nameTable->RegisterName("Fixed32");
+ auto sfixed32Id = nameTable->RegisterName("SFixed32");
+
+ auto boolId = nameTable->RegisterName("Bool");
+ auto stringId = nameTable->RegisterName("String");
+ auto bytesId = nameTable->RegisterName("Bytes");
+
+ auto enumId = nameTable->RegisterName("Enum");
+
+ auto messageId = nameTable->RegisterName("Message");
+
+ auto anyWithMapId = nameTable->RegisterName("AnyWithMap");
+ auto anyWithInt64Id = nameTable->RegisterName("AnyWithInt64");
+ auto anyWithStringId = nameTable->RegisterName("AnyWithString");
+
+ auto otherInt64ColumnId = nameTable->RegisterName("OtherInt64Column");
+ auto otherDoubleColumnId = nameTable->RegisterName("OtherDoubleColumn");
+ auto otherStringColumnId = nameTable->RegisterName("OtherStringColumn");
+ auto otherNullColumnId = nameTable->RegisterName("OtherNullColumn");
+ auto otherBooleanColumnId = nameTable->RegisterName("OtherBooleanColumn");
+ auto otherAnyColumnId = nameTable->RegisterName("OtherAnyColumn");
+
+ auto tableIndexColumnId = nameTable->RegisterName(TableIndexColumnName);
+ auto rowIndexColumnId = nameTable->RegisterName(RowIndexColumnName);
+ auto rangeIndexColumnId = nameTable->RegisterName(RangeIndexColumnName);
+
+ auto missintInt64Id = nameTable->RegisterName("MissingInt64");
+
+ TString result;
+ TStringOutput resultStream(result);
+ auto writer = CreateWriterForProtobuf(
+ config->Attributes(),
+ {New<TTableSchema>()},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+
+ TEmbeddedMessage embeddedMessage;
+ embeddedMessage.set_key("embedded_key");
+ embeddedMessage.set_value("embedded_value");
+ TString embeddedMessageBytes;
+ ASSERT_TRUE(embeddedMessage.SerializeToString(&embeddedMessageBytes));
+
+ auto mapNode = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("Key").Value("Value")
+ .Item("Another")
+ .BeginList()
+ .Item().Value(1)
+ .Item().Value("two")
+ .EndList()
+ .EndMap();
+ auto ysonString = ConvertToYsonString(mapNode).ToString();
+
+ TUnversionedRowBuilder builder;
+ for (const auto& value : {
+ MakeUnversionedDoubleValue(3.14159, doubleId),
+ MakeUnversionedDoubleValue(2.71828, floatId),
+
+ MakeUnversionedInt64Value(-1, int64Id),
+ MakeUnversionedUint64Value(2, uint64Id),
+ MakeUnversionedInt64Value(-3, sint64Id),
+ MakeUnversionedUint64Value(4, fixed64Id),
+ MakeUnversionedInt64Value(-5, sfixed64Id),
+
+ MakeUnversionedInt64Value(-6, int32Id),
+ MakeUnversionedUint64Value(7, uint32Id),
+ MakeUnversionedInt64Value(-8, sint32Id),
+ MakeUnversionedUint64Value(9, fixed32Id),
+ MakeUnversionedInt64Value(-10, sfixed32Id),
+
+ MakeUnversionedBooleanValue(true, boolId),
+ MakeUnversionedStringValue("this_is_string", stringId),
+ MakeUnversionedStringValue("this_is_bytes", bytesId),
+
+ MakeUnversionedStringValue("Two", enumId),
+
+ MakeUnversionedStringValue(embeddedMessageBytes, messageId),
+
+ MakeUnversionedNullValue(missintInt64Id),
+
+ MakeUnversionedInt64Value(12, tableIndexColumnId),
+ MakeUnversionedInt64Value(42, rowIndexColumnId),
+ MakeUnversionedInt64Value(333, rangeIndexColumnId),
+ }) {
+ builder.AddValue(value);
+ }
+
+ if (!IsLegacyFormat()) {
+ builder.AddValue(MakeUnversionedAnyValue(ysonString, anyWithMapId));
+ builder.AddValue(MakeUnversionedInt64Value(22, anyWithInt64Id));
+ builder.AddValue(MakeUnversionedStringValue("some_string", anyWithStringId));
+
+ builder.AddValue(MakeUnversionedInt64Value(-123, otherInt64ColumnId));
+ builder.AddValue(MakeUnversionedDoubleValue(-123.456, otherDoubleColumnId));
+ builder.AddValue(MakeUnversionedStringValue("some_string", otherStringColumnId));
+ builder.AddValue(MakeUnversionedBooleanValue(true, otherBooleanColumnId));
+ builder.AddValue(MakeUnversionedAnyValue(ysonString, otherAnyColumnId));
+ builder.AddValue(MakeUnversionedNullValue(otherNullColumnId));
+ }
+
+ auto row = builder.GetRow();
+ std::vector<TUnversionedRow> rows(rowCount, row);
+ writer->Write(rows);
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput input(result);
+ TLenvalParser lenvalParser(&input);
+
+ for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
+ auto entry = lenvalParser.Next();
+ ASSERT_TRUE(entry);
+
+ NYT::TMessage message;
+ ASSERT_TRUE(message.ParseFromString(entry->RowData));
+
+ EXPECT_DOUBLE_EQ(message.double_field(), 3.14159);
+ EXPECT_FLOAT_EQ(message.float_field(), 2.71828);
+ EXPECT_EQ(message.int64_field(), -1);
+ EXPECT_EQ(message.uint64_field(), 2u);
+ EXPECT_EQ(message.sint64_field(), -3);
+ EXPECT_EQ(message.fixed64_field(), 4u);
+ EXPECT_EQ(message.sfixed64_field(), -5);
+
+ EXPECT_EQ(message.int32_field(), -6);
+ EXPECT_EQ(message.uint32_field(), 7u);
+ EXPECT_EQ(message.sint32_field(), -8);
+ EXPECT_EQ(message.fixed32_field(), 9u);
+ EXPECT_EQ(message.sfixed32_field(), -10);
+
+ EXPECT_EQ(message.bool_field(), true);
+ EXPECT_EQ(message.string_field(), "this_is_string");
+ EXPECT_EQ(message.bytes_field(), "this_is_bytes");
+
+ EXPECT_EQ(message.enum_field(), EEnum::Two);
+
+ EXPECT_EQ(message.message_field().key(), "embedded_key");
+ EXPECT_EQ(message.message_field().value(), "embedded_value");
+
+ if (!IsLegacyFormat()) {
+ EXPECT_TRUE(AreNodesEqual(ConvertToNode(TYsonString(message.any_field_with_map())), mapNode));
+ EXPECT_TRUE(AreNodesEqual(
+ ConvertToNode(TYsonString(message.any_field_with_int64())),
+ BuildYsonNodeFluently().Value(22)));
+ EXPECT_TRUE(AreNodesEqual(
+ ConvertToNode(TYsonString(message.any_field_with_string())),
+ BuildYsonNodeFluently().Value("some_string")));
+
+ auto otherColumnsMap = ConvertToNode(TYsonString(message.other_columns_field()))->AsMap();
+ EXPECT_EQ(otherColumnsMap->GetChildValueOrThrow<i64>("OtherInt64Column"), -123);
+ EXPECT_DOUBLE_EQ(otherColumnsMap->GetChildValueOrThrow<double>("OtherDoubleColumn"), -123.456);
+ EXPECT_EQ(otherColumnsMap->GetChildValueOrThrow<TString>("OtherStringColumn"), "some_string");
+ EXPECT_EQ(otherColumnsMap->GetChildValueOrThrow<bool>("OtherBooleanColumn"), true);
+ EXPECT_TRUE(AreNodesEqual(otherColumnsMap->GetChildOrThrow("OtherAnyColumn"), mapNode));
+ EXPECT_EQ(otherColumnsMap->GetChildOrThrow("OtherNullColumn")->GetType(), ENodeType::Entity);
+
+ auto keys = otherColumnsMap->GetKeys();
+ std::sort(keys.begin(), keys.end());
+ std::vector<TString> expectedKeys = {
+ "OtherInt64Column",
+ "OtherDoubleColumn",
+ "OtherStringColumn",
+ "OtherBooleanColumn",
+ "OtherAnyColumn",
+ "OtherNullColumn"};
+ std::sort(expectedKeys.begin(), expectedKeys.end());
+ EXPECT_EQ(expectedKeys, keys);
+ }
+ }
+
+ ASSERT_FALSE(lenvalParser.Next());
+}
+
+TEST_P(TProtobufFormatAllFields, Parser)
+{
+ auto [rowCount, protoFormatType] = GetParam();
+
+ auto config = CreateAllFieldsConfig(protoFormatType);
+
+ TMessage message;
+ message.set_double_field(3.14159);
+ message.set_float_field(2.71828);
+
+ message.set_int64_field(-1);
+ message.set_uint64_field(2);
+ message.set_sint64_field(-3);
+ message.set_fixed64_field(4);
+ message.set_sfixed64_field(-5);
+
+ message.set_int32_field(-6);
+ message.set_uint32_field(7);
+ message.set_sint32_field(-8);
+ message.set_fixed32_field(9);
+ message.set_sfixed32_field(-10);
+
+ message.set_bool_field(true);
+ message.set_string_field("this_is_string");
+ message.set_bytes_field("this_is_bytes");
+ message.set_enum_field(EEnum::Three);
+
+ message.mutable_message_field()->set_key("embedded_key");
+ message.mutable_message_field()->set_value("embedded_value");
+
+ auto mapNode = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("Key").Value("Value")
+ .Item("Another")
+ .BeginList()
+ .Item().Value(1)
+ .Item().Value("two")
+ .EndList()
+ .EndMap();
+
+ auto otherColumnsNode = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("OtherInt64Column").Value(-123)
+ .Item("OtherDoubleColumn").Value(-123.456)
+ .Item("OtherStringColumn").Value("some_string")
+ .Item("OtherBooleanColumn").Value(true)
+ .Item("OtherAnyColumn").Value(mapNode)
+ .Item("OtherNullColumn").Entity()
+ .EndMap();
+
+ if (!IsLegacyFormat()) {
+ message.set_any_field_with_map(ConvertToYsonString(mapNode).ToString());
+ message.set_any_field_with_int64(BuildYsonStringFluently().Value(22).ToString());
+ message.set_any_field_with_string(BuildYsonStringFluently().Value("some_string").ToString());
+ message.set_other_columns_field(ConvertToYsonString(otherColumnsNode).ToString());
+ }
+
+ auto rowCollector = ParseRows(
+ message,
+ ConvertTo<TProtobufFormatConfigPtr>(config->Attributes().ToMap()),
+ New<TTableSchema>(),
+ rowCount);
+
+ for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
+ int expectedSize = IsLegacyFormat() ? 17 : 26;
+ ASSERT_EQ(static_cast<int>(rowCollector.GetRow(rowIndex).GetCount()), expectedSize);
+
+ ASSERT_DOUBLE_EQ(GetDouble(rowCollector.GetRowValue(rowIndex, "Double")), 3.14159);
+ ASSERT_NEAR(GetDouble(rowCollector.GetRowValue(rowIndex, "Float")), 2.71828, 1e-5);
+
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "Int64")), -1);
+ ASSERT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "UInt64")), 2u);
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "SInt64")), -3);
+ ASSERT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "Fixed64")), 4u);
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "SFixed64")), -5);
+
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "Int32")), -6);
+ ASSERT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "UInt32")), 7u);
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "SInt32")), -8);
+ ASSERT_EQ(GetUint64(rowCollector.GetRowValue(rowIndex, "Fixed32")), 9u);
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "SFixed32")), -10);
+
+ ASSERT_EQ(GetBoolean(rowCollector.GetRowValue(rowIndex, "Bool")), true);
+ ASSERT_EQ(GetString(rowCollector.GetRowValue(rowIndex, "String")), "this_is_string");
+ ASSERT_EQ(GetString(rowCollector.GetRowValue(rowIndex, "Bytes")), "this_is_bytes");
+
+ if (IsLegacyFormat()) {
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "Enum")), 3);
+ } else {
+ ASSERT_EQ(GetString(rowCollector.GetRowValue(rowIndex, "Enum")), "Three");
+ }
+
+ TEmbeddedMessage embeddedMessage;
+ ASSERT_TRUE(embeddedMessage.ParseFromString(GetString(rowCollector.GetRowValue(rowIndex, "Message"))));
+ ASSERT_EQ(embeddedMessage.key(), "embedded_key");
+ ASSERT_EQ(embeddedMessage.value(), "embedded_value");
+
+ if (!IsLegacyFormat()) {
+ ASSERT_TRUE(AreNodesEqual(GetAny(rowCollector.GetRowValue(rowIndex, "AnyWithMap")), mapNode));
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "AnyWithInt64")), 22);
+ ASSERT_EQ(GetString(rowCollector.GetRowValue(rowIndex, "AnyWithString")), "some_string");
+
+ ASSERT_EQ(GetInt64(rowCollector.GetRowValue(rowIndex, "OtherInt64Column")), -123);
+ ASSERT_DOUBLE_EQ(GetDouble(rowCollector.GetRowValue(rowIndex, "OtherDoubleColumn")), -123.456);
+ ASSERT_EQ(GetString(rowCollector.GetRowValue(rowIndex, "OtherStringColumn")), "some_string");
+ ASSERT_EQ(GetBoolean(rowCollector.GetRowValue(rowIndex, "OtherBooleanColumn")), true);
+ ASSERT_TRUE(AreNodesEqual(GetAny(rowCollector.GetRowValue(rowIndex, "OtherAnyColumn")), mapNode));
+ ASSERT_EQ(rowCollector.GetRowValue(rowIndex, "OtherNullColumn").Type, EValueType::Null);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufFormatCompat
+ : public ::testing::Test
+{
+public:
+ static TTableSchemaPtr GetEarlySchema()
+ {
+ static const auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"a", OptionalLogicalType(VariantStructLogicalType({
+ {"f1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ }))},
+ });
+ return schema;
+ }
+
+ static TTableSchemaPtr GetFirstMiddleSchema()
+ {
+ static const auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"a", OptionalLogicalType(VariantStructLogicalType({
+ {"f1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"f2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }))},
+ {"b", OptionalLogicalType(StructLogicalType({
+ {"x", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }))},
+ });
+ return schema;
+ }
+
+ static TTableSchemaPtr GetSecondMiddleSchema()
+ {
+ static const auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"a", OptionalLogicalType(VariantStructLogicalType({
+ {"f1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"f2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }))},
+ {"b", OptionalLogicalType(StructLogicalType({
+ {"x", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"y", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ }))},
+ });
+ return schema;
+ }
+
+ static TTableSchemaPtr GetThirdMiddleSchema()
+ {
+ static const auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"a", OptionalLogicalType(VariantStructLogicalType({
+ {"f1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"f2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }))},
+ {"b", OptionalLogicalType(StructLogicalType({
+ {"x", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"y", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"z", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ }))},
+ });
+ return schema;
+ }
+
+ static TTableSchemaPtr GetLateSchema()
+ {
+ static const auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"a", OptionalLogicalType(VariantStructLogicalType({
+ {"f1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"f2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"f3", SimpleLogicalType(ESimpleLogicalValueType::Boolean)},
+ }))},
+ {"c", OptionalLogicalType(ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Boolean)))},
+ {"b", OptionalLogicalType(StructLogicalType({
+ {"x", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"y", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"z", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ }))},
+ });
+ return schema;
+ }
+
+ static TProtobufFormatConfigPtr GetFirstMiddleConfig()
+ {
+ static const auto config = ConvertTo<TProtobufFormatConfigPtr>(BuildYsonNodeFluently()
+ .BeginMap().Item("tables").BeginList().Item().BeginMap().Item("columns").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("a")
+ .Item("field_number").Value(0)
+ .Item("proto_type").Value("oneof")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("f1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("b")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("x")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList().EndMap().EndList().EndMap());
+ return config;
+ }
+
+ static TProtobufFormatConfigPtr GetSecondMiddleConfig()
+ {
+ static const auto config = ConvertTo<TProtobufFormatConfigPtr>(BuildYsonNodeFluently()
+ .BeginMap().Item("tables").BeginList().Item().BeginMap().Item("columns").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("a")
+ .Item("field_number").Value(0)
+ .Item("proto_type").Value("oneof")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("f1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("f2")
+ .Item("field_number").Value(101)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("b")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields")
+ .BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("x")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("y")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList().EndMap().EndList().EndMap());
+ return config;
+ }
+};
+
+template <typename TMessage>
+TMessage WriteRow(
+ TUnversionedRow row,
+ const TProtobufFormatConfigPtr& config,
+ const TTableSchemaPtr& schema,
+ const TNameTablePtr& nameTable)
+{
+ TString result;
+ TStringOutput resultStream(result);
+
+ auto writer = CreateWriterForProtobuf(
+ config,
+ {schema},
+ nameTable,
+ CreateAsyncAdapter(&resultStream),
+ true,
+ New<TControlAttributesConfig>(),
+ 0);
+ writer->Write(std::vector<TUnversionedRow>{row});
+ writer->Close().Get().ThrowOnError();
+
+ TStringInput input(result);
+ TLenvalParser lenvalParser(&input);
+ auto entry = lenvalParser.Next();
+ if (!entry) {
+ THROW_ERROR_EXCEPTION("Unexpected end of stream in lenval parser");
+ }
+ TMessage message;
+ if (!message.ParseFromString(entry->RowData)) {
+ THROW_ERROR_EXCEPTION("Failed to parse message");
+ }
+ if (lenvalParser.Next()) {
+ THROW_ERROR_EXCEPTION("Unexpected entry in lenval parser");
+ }
+ return message;
+}
+
+TEST_F(TProtobufFormatCompat, Write)
+{
+ auto nameTable = TNameTable::FromSchema(*GetLateSchema());
+ auto config = GetSecondMiddleConfig();
+
+ auto writeRow = [&] (TUnversionedRow row, const TTableSchemaPtr& schema) {
+ return WriteRow<NYT::TCompatMessage>(row, config, schema, nameTable);
+ };
+
+ {
+ auto earlyRow = MakeRow(nameTable, {
+ {"a", EValueType::Composite, "[0; -24]"}
+ });
+
+ SCOPED_TRACE("early");
+ auto message = writeRow(earlyRow, GetEarlySchema());
+ EXPECT_EQ(message.f1(), -24);
+ EXPECT_FALSE(message.has_f2());
+ EXPECT_EQ(message.has_b(), false);
+ }
+ {
+ auto firstMiddleRow = MakeRow(nameTable, {
+ {"a", EValueType::Composite, "[1; foobar]"},
+ {"b", EValueType::Composite, "[foo]"},
+ });
+
+ SCOPED_TRACE("firstMiddle");
+ auto message = writeRow(firstMiddleRow, GetFirstMiddleSchema());
+ EXPECT_FALSE(message.has_f1());
+ EXPECT_EQ(message.f2(), "foobar");
+ EXPECT_EQ(message.b().x(), "foo");
+ EXPECT_EQ(message.b().has_y(), false);
+ }
+ {
+ auto secondMiddleRow = MakeRow(nameTable, {
+ {"a", EValueType::Composite, "[1; foobar]"},
+ {"b", EValueType::Composite, "[foo; bar]"},
+ });
+
+ SCOPED_TRACE("secondMiddle");
+ auto message = writeRow(secondMiddleRow, GetSecondMiddleSchema());
+ EXPECT_FALSE(message.has_f1());
+ EXPECT_EQ(message.f2(), "foobar");
+ EXPECT_EQ(message.b().x(), "foo");
+ EXPECT_EQ(message.b().y(), "bar");
+ }
+ {
+ auto thirdMiddleRow = MakeRow(nameTable, {
+ {"a", EValueType::Composite, "[1; foobar]"},
+ {"b", EValueType::Composite, "[foo; bar; spam]"},
+ });
+
+ SCOPED_TRACE("thirdMiddle");
+ auto message = writeRow(thirdMiddleRow, GetThirdMiddleSchema());
+ EXPECT_FALSE(message.has_f1());
+ EXPECT_EQ(message.f2(), "foobar");
+ EXPECT_EQ(message.b().x(), "foo");
+ EXPECT_EQ(message.b().y(), "bar");
+ }
+ {
+ auto lateRow = MakeRow(nameTable, {
+ {"a", EValueType::Composite, "[2; %true]"},
+ {"c", EValueType::Composite, "[%false; %true; %false]"},
+ {"b", EValueType::Composite, "[foo; bar; spam]"},
+ });
+
+ SCOPED_TRACE("late");
+ auto message = writeRow(lateRow, GetLateSchema());
+ EXPECT_FALSE(message.has_f1());
+ EXPECT_FALSE(message.has_f2());
+ EXPECT_EQ(message.b().x(), "foo");
+ EXPECT_EQ(message.b().y(), "bar");
+ }
+}
+
+TEST_F(TProtobufFormatCompat, Parse)
+{
+ auto config = GetSecondMiddleConfig();
+
+ NYT::TCompatMessage message;
+ message.set_f2("Sandiego");
+ message.mutable_b()->set_x("foo");
+ message.mutable_b()->set_y("bar");
+
+ {
+ SCOPED_TRACE("early");
+ auto collector = ParseRows(message, config, GetEarlySchema());
+ EXPECT_FALSE(collector.FindRowValue(0, "a"));
+ EXPECT_FALSE(collector.GetNameTable()->FindId("b"));
+ EXPECT_FALSE(collector.GetNameTable()->FindId("c"));
+ }
+ {
+ SCOPED_TRACE("firstMiddle");
+ auto collector = ParseRows(message, config, GetFirstMiddleSchema());
+ EXPECT_NODES_EQUAL(
+ GetComposite(collector.GetRowValue(0, "a")),
+ ConvertToNode(TYsonString(TStringBuf("[1;Sandiego]"))));
+ EXPECT_NODES_EQUAL(GetComposite(collector.GetRowValue(0, "b")), ConvertToNode(TYsonString(TStringBuf("[foo]"))));
+ EXPECT_FALSE(collector.GetNameTable()->FindId("c"));
+ }
+ {
+ SCOPED_TRACE("secondMiddle");
+ auto collector = ParseRows(message, config, GetSecondMiddleSchema());
+ EXPECT_NODES_EQUAL(
+ GetComposite(collector.GetRowValue(0, "a")),
+ ConvertToNode(TYsonString(TStringBuf("[1;Sandiego]"))));
+ EXPECT_NODES_EQUAL(GetComposite(collector.GetRowValue(0, "b")), ConvertToNode(TYsonString(TStringBuf("[foo;bar]"))));
+ EXPECT_FALSE(collector.GetNameTable()->FindId("c"));
+ }
+ {
+ SCOPED_TRACE("thirdMiddle");
+ auto collector = ParseRows(message, config, GetThirdMiddleSchema());
+ EXPECT_NODES_EQUAL(
+ GetComposite(collector.GetRowValue(0, "a")),
+ ConvertToNode(TYsonString(TStringBuf("[1;Sandiego]"))));
+ EXPECT_NODES_EQUAL(GetComposite(collector.GetRowValue(0, "b")), ConvertToNode(TYsonString(TStringBuf("[foo;bar;#]"))));
+ EXPECT_FALSE(collector.GetNameTable()->FindId("c"));
+ }
+ {
+ SCOPED_TRACE("late");
+ auto collector = ParseRows(message, config, GetLateSchema());
+ EXPECT_NODES_EQUAL(
+ GetComposite(collector.GetRowValue(0, "a")),
+ ConvertToNode(TYsonString(TStringBuf("[1;Sandiego]"))));
+ EXPECT_NODES_EQUAL(GetComposite(collector.GetRowValue(0, "b")), ConvertToNode(TYsonString(TStringBuf("[foo;bar;#]"))));
+ EXPECT_TRUE(collector.GetNameTable()->FindId("c"));
+ }
+}
+
+TEST_F(TProtobufFormatCompat, ParseWrong)
+{
+ NYT::TCompatMessage message;
+ message.set_f1(42);
+ message.mutable_b()->set_x("foo");
+ message.mutable_b()->set_y("bar");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseRows(message, GetFirstMiddleConfig(), GetFirstMiddleSchema()),
+ "Unexpected field number 2");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufFormatEnumCompat
+ : public ::testing::Test
+{
+public:
+ static TTableSchemaPtr CreateTableSchema()
+ {
+ static const auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"optional_enum", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"required_enum", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"repeated_enum", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"packed_repeated_enum", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"inner", OptionalLogicalType(StructLogicalType({
+ {"optional_enum", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"required_enum", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"repeated_enum", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"packed_repeated_enum", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ }))},
+ });
+ return schema;
+ }
+ static TProtobufFormatConfigPtr CreateProtobufFormatConfig()
+ {
+ static const auto config = ConvertTo<TProtobufFormatConfigPtr>(BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("enumerations").BeginMap()
+ .Item("ECompatEnum")
+ .BeginMap()
+ .Item("One").Value(1)
+ .Item("Two").Value(2)
+ .Item("Three").Value(3)
+ .EndMap()
+ .EndMap()
+ .Item("tables").BeginList().Item().BeginMap().Item("columns").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("optional_enum")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("enum_string")
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("required_enum")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("enum_string")
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("repeated_enum")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("enum_string")
+ .Item("repeated").Value(true)
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("packed_repeated_enum")
+ .Item("field_number").Value(4)
+ .Item("proto_type").Value("enum_string")
+ .Item("repeated").Value(true)
+ .Item("packed").Value(true)
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("inner")
+ .Item("field_number").Value(100)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("optional_enum")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("enum_string")
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("required_enum")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("enum_string")
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("repeated_enum")
+ .Item("field_number").Value(3)
+ .Item("proto_type").Value("enum_string")
+ .Item("repeated").Value(true)
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("packed_repeated_enum")
+ .Item("field_number").Value(4)
+ .Item("proto_type").Value("enum_string")
+ .Item("repeated").Value(true)
+ .Item("packed").Value(true)
+ .Item("enum_writing_mode").Value("skip_unknown_values")
+ .Item("enumeration_name").Value("ECompatEnum")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList().EndMap().EndList().EndMap());
+ return config;
+ }
+
+};
+
+TEST_F(TProtobufFormatEnumCompat, WriteCanSkipUnknownEnumValues)
+{
+ auto schema = CreateTableSchema();
+ auto config = CreateProtobufFormatConfig();
+
+ auto nameTable = TNameTable::FromSchema(*schema);
+
+ auto row = MakeRow(nameTable, {
+ {"optional_enum", "MinusFortyTwo"},
+ {"required_enum", "One"},
+ {"repeated_enum", EValueType::Composite, "[MinusFortyTwo;One;MinusFortyTwo]"},
+ {"packed_repeated_enum", EValueType::Composite, "[MinusFortyTwo;Two;MinusFortyTwo]"},
+ {"inner", EValueType::Composite, "[MinusFortyTwo;Two;[MinusFortyTwo;Two];[One;MinusFortyTwo]]"},
+ });
+
+ auto collectRepeated = [](const auto& repeated) {
+ std::vector<TEnumCompat::ECompatEnum> values;
+ for (auto value : repeated) {
+ values.push_back(static_cast<TEnumCompat::ECompatEnum>(value));
+ }
+ return values;
+ };
+
+ auto message = WriteRow<TEnumCompat>(row, config, schema, nameTable);
+
+ EXPECT_FALSE(message.has_optional_enum());
+ EXPECT_EQ(message.required_enum(), TEnumCompat::One);
+ EXPECT_EQ(collectRepeated(message.repeated_enum()), std::vector{TEnumCompat::One});
+ EXPECT_EQ(collectRepeated(message.packed_repeated_enum()), std::vector{TEnumCompat::Two});
+
+ ASSERT_TRUE(message.has_inner());
+ EXPECT_FALSE(message.inner().has_optional_enum());
+ EXPECT_EQ(message.inner().required_enum(), TEnumCompat::Two);
+ EXPECT_EQ(collectRepeated(message.inner().repeated_enum()), std::vector{TEnumCompat::Two});
+ EXPECT_EQ(collectRepeated(message.inner().packed_repeated_enum()), std::vector{TEnumCompat::One});
+}
+
+TEST_F(TProtobufFormatEnumCompat, WriteDoesntSkipRequiredFields)
+{
+ auto schema = CreateTableSchema();
+ auto config = CreateProtobufFormatConfig();
+
+ auto nameTable = TNameTable::FromSchema(*schema);
+
+ {
+ auto row = MakeRow(nameTable, {{"required_enum", "MinusFortyTwo"}});
+ EXPECT_THROW_WITH_SUBSTRING(WriteRow<TEnumCompat>(row, config, schema, nameTable), "Invalid value for enum");
+ }
+ {
+ auto row = MakeRow(nameTable, {{"inner", EValueType::Composite, "[#;MinusFortyTwo;#;#]"},});
+ EXPECT_THROW_WITH_SUBSTRING(WriteRow<TEnumCompat>(row, config, schema, nameTable), "Invalid value for enum");
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TProtobufFormatRuntimeErrors
+ : public ::testing::Test
+{
+public:
+ static TTableSchemaPtr GetSchemaWithVariant(bool optional = false)
+ {
+ auto variantType = VariantStructLogicalType({
+ {"f1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"f2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ });
+ return New<TTableSchema>(std::vector<TColumnSchema>{
+ {"a", optional ? OptionalLogicalType(variantType) : variantType},
+ });
+ }
+
+ static TTableSchemaPtr GetSchemaWithStruct(bool optional = false)
+ {
+ auto structType = StructLogicalType({
+ {"f1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"f2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ });
+ return New<TTableSchema>(std::vector<TColumnSchema>{
+ {"a", optional ? OptionalLogicalType(structType) : structType},
+ });
+ }
+
+ static TProtobufFormatConfigPtr GetConfigWithVariant()
+ {
+ static const auto config = ConvertTo<TProtobufFormatConfigPtr>(BuildYsonNodeFluently()
+ .BeginMap().Item("tables").BeginList().Item().BeginMap().Item("columns").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("a")
+ .Item("proto_type").Value("oneof")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("f1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("f2")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList().EndMap().EndList().EndMap());
+ return config;
+ }
+
+ static TProtobufFormatConfigPtr GetConfigWithStruct()
+ {
+ static const auto config = ConvertTo<TProtobufFormatConfigPtr>(BuildYsonNodeFluently()
+ .BeginMap().Item("tables").BeginList().Item().BeginMap().Item("columns").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("a")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("structured_message")
+ .Item("fields").BeginList()
+ .Item().BeginMap()
+ .Item("name").Value("f1")
+ .Item("field_number").Value(1)
+ .Item("proto_type").Value("int64")
+ .EndMap()
+ .Item().BeginMap()
+ .Item("name").Value("f2")
+ .Item("field_number").Value(2)
+ .Item("proto_type").Value("string")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList().EndMap().EndList().EndMap());
+ return config;
+ }
+};
+
+TEST_F(TProtobufFormatRuntimeErrors, ParseVariant)
+{
+ {
+ SCOPED_TRACE("Optional variant, all missing");
+ TMessageWithOneof message;
+ auto collector = ParseRows(message, GetConfigWithVariant(), GetSchemaWithVariant(/* optional */ true));
+ EXPECT_FALSE(collector.FindRowValue(0, "a"));
+ }
+ {
+ SCOPED_TRACE("All missing");
+ TMessageWithOneof message;
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseRows(message, GetConfigWithVariant(), GetSchemaWithVariant()),
+ "required field \"<root>.a\" is missing");
+ }
+ {
+ SCOPED_TRACE("two alternatives");
+ TMessageWithStruct::TStruct message;
+ message.set_f1(5);
+ message.set_f2("boo");
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseRows(message, GetConfigWithVariant(), GetSchemaWithVariant()),
+ "multiple entries for oneof field \"<root>.a\"");
+ }
+}
+
+TEST_F(TProtobufFormatRuntimeErrors, ParseStruct)
+{
+ {
+ SCOPED_TRACE("Optional submessage missing");
+ TMessageWithStruct message;
+ auto collector = ParseRows(message, GetConfigWithStruct(), GetSchemaWithStruct(/* optional */ true));
+ EXPECT_FALSE(collector.FindRowValue(0, "a"));
+ }
+ {
+ SCOPED_TRACE("Required submessage missing");
+ TMessageWithStruct message;
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseRows(message, GetConfigWithStruct(), GetSchemaWithStruct()),
+ "required field \"<root>.a\" is missing");
+ }
+ {
+ SCOPED_TRACE("All fields missing");
+ TMessageWithStruct message;
+ message.mutable_a();
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseRows(message, GetConfigWithStruct(), GetSchemaWithStruct()),
+ "required field \"<root>.a.f1\" is missing");
+ }
+ {
+ SCOPED_TRACE("Second field missing");
+ TMessageWithStruct message;
+ message.mutable_a()->set_f1(17);
+ EXPECT_THROW_WITH_SUBSTRING(
+ ParseRows(message, GetConfigWithStruct(), GetSchemaWithStruct()),
+ "required field \"<root>.a.f2\" is missing");
+ }
+ {
+ SCOPED_TRACE("All present");
+ TMessageWithStruct message;
+ message.mutable_a()->set_f1(17);
+ message.mutable_a()->set_f2("foobar");
+ auto collector = ParseRows(message, GetConfigWithStruct(), GetSchemaWithStruct());
+ EXPECT_NODES_EQUAL(
+ GetComposite(collector.GetRowValue(0, "a")),
+ ConvertToNode(TYsonString(TStringBuf("[17;foobar]"))));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/yt/yt/client/unittests/protobuf_format_ut.proto b/yt/yt/client/unittests/protobuf_format_ut.proto
new file mode 100644
index 0000000000..06258de619
--- /dev/null
+++ b/yt/yt/client/unittests/protobuf_format_ut.proto
@@ -0,0 +1,255 @@
+import "yt/yt_proto/yt/formats/extension.proto";
+
+package NYT.NProtobufFormatTest;
+
+enum EEnum
+{
+ One = 1;
+ Two = 2;
+ Three = 3;
+
+ MinusFortyTwo = -42;
+
+ MinInt32 = -2147483648;
+ MaxInt32 = 2147483647;
+}
+
+message TEmbeddedStruct {
+ optional float float1 = 1;
+ optional string string1 = 2;
+};
+
+message TEmbedded2Message {
+ option (NYT.default_field_flags) = SERIALIZATION_YT;
+ optional uint64 embedded2_num = 10;
+ optional TEmbeddedStruct embedded2_struct = 17;
+ repeated string embedded2_repeated = 42;
+};
+
+message TEmbedded1Message {
+ option (NYT.default_field_flags) = SERIALIZATION_YT;
+ optional TEmbedded2Message t2 = 1 [(NYT.flags) = EMBEDDED];
+ oneof variant {
+ string str_variant = 101;
+ uint64 uint_variant = 102;
+ }
+ optional uint64 embedded_num = 10; // make intentional field_num collision!
+ optional string embedded_extra_field = 11;
+};
+message TEmbeddingMessage {
+ optional bytes other_columns_field = 15 [(NYT.flags) = OTHER_COLUMNS];
+ optional TEmbedded1Message t1 = 2 [(NYT.flags) = EMBEDDED];
+ optional uint64 num = 12;
+ optional string extra_field = 13;
+};
+
+message TEmbeddedMessage
+{
+ optional string key = 1;
+ optional string value = 2;
+}
+
+message TMessageWithStructuredEmbedded
+{
+ option (NYT.default_field_flags) = SERIALIZATION_YT;
+
+ message TFirstMessage
+ {
+ option (NYT.default_field_flags) = SERIALIZATION_YT;
+
+ optional EEnum enum_field = 1 [(NYT.flags) = ENUM_STRING];
+ optional int64 int64_field = 2;
+ repeated int64 repeated_int64_field = 3;
+ optional TEmbeddedMessage message_field = 4;
+ repeated TEmbeddedMessage repeated_message_field = 5;
+ optional bytes any_int64_field = 6 [(NYT.flags) = ANY];
+ optional bytes any_map_field = 7 [(NYT.flags) = ANY];
+ optional int64 optional_int64_field = 8;
+ repeated int64 another_repeated_int64_field = 9;
+ repeated bytes repeated_optional_any_field = 10 [(NYT.flags) = ANY];
+ repeated EEnum packed_repeated_enum_field = 11 [packed=true, (NYT.flags) = ENUM_STRING];
+ repeated bool optional_repeated_bool_field = 12;
+ oneof oneof_field {
+ string oneof_string_field_1 = 101;
+ string oneof_string_field = 102;
+ TEmbeddedMessage oneof_message_field = 1000;
+ }
+ oneof optional_oneof_field {
+ string optional_oneof_string_field_1 = 201;
+ string optional_oneof_string_field = 202;
+ TEmbeddedMessage optional_oneof_message_field = 2000;
+ }
+ map<int64, TEmbeddedMessage> map_field = 13 [(NYT.flags) = MAP_AS_DICT];
+ }
+
+ message TSecondMessage
+ {
+ optional int64 one = 2;
+ optional int64 two = 500000000;
+ optional int64 three = 100500;
+ }
+
+ optional TFirstMessage first = 1;
+ optional TSecondMessage second = 2;
+ repeated TEmbeddedMessage repeated_message_field = 3;
+ repeated int64 repeated_int64_field = 4;
+ optional int64 int64_any_field = 5 [(NYT.column_name) = "any_field"];
+
+ optional int32 int32_field = 6 [(NYT.column_name) = "int64_field"];
+ optional uint32 uint32_field = 7 [(NYT.column_name) = "uint64_field"];
+ optional int64 int64_field = 8 [(NYT.column_name) = "int32_field"];
+ optional uint64 uint64_field = 9 [(NYT.column_name) = "uint32_field"];
+
+ optional EEnum enum_int_field = 10 [(NYT.flags) = ENUM_INT];
+ optional EEnum enum_string_string_field = 11 [(NYT.flags) = ENUM_STRING];
+ optional EEnum enum_string_int64_field = 12 [(NYT.flags) = ENUM_STRING];
+
+
+ repeated int64 another_repeated_int64_field = 13;
+
+ repeated bytes repeated_optional_any_field = 14 [(NYT.flags) = ANY];
+
+ optional bytes other_columns_field = 15 [(NYT.flags) = OTHER_COLUMNS];
+
+ optional string utf8_field = 16;
+
+ repeated int64 packed_repeated_int64_field = 17 [packed=true];
+
+ repeated int64 optional_repeated_int64_field = 18;
+
+ oneof oneof_field {
+ string oneof_string_field_1 = 101;
+ string oneof_string_field = 102;
+ TEmbeddedMessage oneof_message_field = 1000;
+ }
+
+ oneof optional_oneof_field {
+ string optional_oneof_string_field_1 = 201;
+ string optional_oneof_string_field = 202;
+ TEmbeddedMessage optional_oneof_message_field = 2000;
+ }
+
+ map<int64, TEmbeddedMessage> map_field = 19 [(NYT.flags) = MAP_AS_DICT];
+}
+
+message TSeveralTablesMessageFirst
+{
+ option (NYT.default_field_flags) = SERIALIZATION_YT;
+
+ message TEmbedded
+ {
+ optional EEnum enum_field = 1 [(NYT.flags) = ENUM_STRING];
+ optional int64 int64_field = 2;
+ }
+ optional TEmbedded embedded = 1;
+ repeated int64 repeated_int64_field = 2;
+ optional int64 int64_field = 3 [(NYT.column_name) = "any_field"];
+}
+
+message TSeveralTablesMessageSecond
+{
+ optional EEnum enum_field = 1 [(NYT.flags) = ENUM_STRING];
+ optional int64 int64_field = 2;
+}
+
+message TSeveralTablesMessageThird
+{
+ optional string string_field = 1;
+}
+
+message TMessage
+{
+ optional double double_field = 1 [(NYT.column_name) = "Double"];
+ optional float float_field = 2 [(NYT.column_name) = "Float"];
+
+ optional int64 int64_field = 3 [(NYT.column_name) = "Int64"];
+ optional uint64 uint64_field = 4 [(NYT.column_name) = "UInt64"];
+ optional sint64 sint64_field = 5 [(NYT.column_name) = "SInt64"];
+ optional fixed64 fixed64_field = 6 [(NYT.column_name) = "Fixed64"];
+ optional sfixed64 sfixed64_field = 7 [(NYT.column_name) = "SFixed64"];
+
+ optional int32 int32_field = 8 [(NYT.column_name) = "Int32"];
+ optional uint32 uint32_field = 9 [(NYT.column_name) = "UInt32"];
+ optional sint32 sint32_field = 10 [(NYT.column_name) = "SInt32"];
+ optional fixed32 fixed32_field = 11 [(NYT.column_name) = "Fixed32"];
+ optional sfixed32 sfixed32_field = 12 [(NYT.column_name) = "SFixed32"];
+
+ optional bool bool_field = 13 [(NYT.column_name) = "Bool"];
+ optional string string_field = 14 [(NYT.column_name) = "String"];
+ optional bytes bytes_field = 15 [(NYT.column_name) = "Bytes"];
+
+ optional EEnum enum_field = 16 [(NYT.column_name) = "Enum", (NYT.flags) = ENUM_STRING];
+ optional TEmbeddedMessage message_field = 17 [(NYT.column_name) = "Message"];
+
+ optional bytes any_field_with_map = 18 [(NYT.column_name) = "AnyWithMap", (NYT.flags) = ANY];
+ optional bytes any_field_with_int64 = 19 [(NYT.column_name) = "AnyWithInt64", (NYT.flags) = ANY];
+ optional bytes any_field_with_string = 20 [(NYT.column_name) = "AnyWithString", (NYT.flags) = ANY];
+ optional bytes other_columns_field = 21 [(NYT.flags) = OTHER_COLUMNS];
+
+ optional int64 missing_int64_field = 22 [(NYT.column_name) = "MissingInt64"];
+}
+
+message TCompatMessage
+{
+ message TEmbedded
+ {
+ optional string x = 1;
+ optional string y = 2;
+ }
+
+ oneof a {
+ int64 f1 = 1;
+ string f2 = 101;
+ }
+ optional TEmbedded b = 2;
+}
+
+message TMessageWithOneof
+{
+ oneof variant {
+ int64 f1 = 1;
+ string f2 = 2;
+ }
+}
+
+message TMessageWithStruct
+{
+ message TStruct
+ {
+ optional int64 f1 = 1;
+ optional string f2 = 2;
+ }
+ optional TStruct a = 1;
+}
+
+message TOtherColumnsMessage
+{
+ optional bytes other_columns_field = 1 [(NYT.flags) = OTHER_COLUMNS];
+}
+
+message TEnumCompat {
+ option (NYT.default_field_flags) = SERIALIZATION_YT;
+ option (NYT.default_field_flags) = ENUM_SKIP_UNKNOWN_VALUES;
+
+ enum ECompatEnum {
+ One = 1;
+ Two = 2;
+ Three = 3;
+ }
+
+
+ message TStruct
+ {
+ optional ECompatEnum optional_enum = 1;
+ required ECompatEnum required_enum = 2;
+ repeated ECompatEnum repeated_enum = 3;
+ repeated ECompatEnum packed_repeated_enum = 4 [packed=true, (NYT.flags) = ENUM_STRING];
+ }
+
+ optional ECompatEnum optional_enum = 1;
+ required ECompatEnum required_enum = 2;
+ repeated ECompatEnum repeated_enum = 3;
+ repeated ECompatEnum packed_repeated_enum = 4 [packed=true, (NYT.flags) = ENUM_STRING];
+
+ optional TStruct inner = 100;
+}
diff --git a/yt/yt/client/unittests/query_builder_ut.cpp b/yt/yt/client/unittests/query_builder_ut.cpp
new file mode 100644
index 0000000000..30b8045b5c
--- /dev/null
+++ b/yt/yt/client/unittests/query_builder_ut.cpp
@@ -0,0 +1,48 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/query_client/query_builder.h>
+
+namespace NYT::NQueryClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TQueryBuilderTest, Simple)
+{
+ TQueryBuilder b;
+ int xIndex = b.AddSelectExpression("x");
+ int yIndex = b.AddSelectExpression("y", "y_alias");
+ int zIndex = b.AddSelectExpression("z");
+
+ b.SetSource("//t");
+
+ b.AddWhereConjunct("x > y_alias");
+ b.AddWhereConjunct("y = 177 OR y % 2 = 0");
+
+ b.AddOrderByAscendingExpression("z");
+ b.AddOrderByDescendingExpression("x");
+ b.AddOrderByExpression("x + y", EOrderByDirection::Descending);
+ b.AddOrderByExpression("z - y_alias");
+
+ b.AddGroupByExpression("x + y * z", "group_expr");
+ b.AddGroupByExpression("x - 1");
+
+ b.SetLimit(43);
+
+ EXPECT_EQ(xIndex, 0);
+ EXPECT_EQ(yIndex, 1);
+ EXPECT_EQ(zIndex, 2);
+
+ EXPECT_EQ(b.Build(),
+ "(x), (y) AS y_alias, (z) "
+ "FROM [//t] "
+ "WHERE (x > y_alias) AND (y = 177 OR y % 2 = 0) "
+ "ORDER BY (z) ASC, (x) DESC, (x + y) DESC, (z - y_alias) "
+ "GROUP BY (x + y * z) AS group_expr, (x - 1) "
+ "LIMIT 43");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NQueryClient
diff --git a/yt/yt/client/unittests/read_limit_ut.cpp b/yt/yt/client/unittests/read_limit_ut.cpp
new file mode 100644
index 0000000000..1f08d4c945
--- /dev/null
+++ b/yt/yt/client/unittests/read_limit_ut.cpp
@@ -0,0 +1,196 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/chunk_client/read_limit.h>
+
+#include <yt/yt/client/table_client/key_bound.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT::NChunkClient {
+namespace {
+
+using namespace NTableClient;
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTableClient::TUnversionedOwningRow MakeRow(std::vector<int> values)
+{
+ TUnversionedOwningRowBuilder builder;
+ for (auto value : values) {
+ builder.AddValue(MakeUnversionedInt64Value(value));
+ }
+
+ return builder.FinishRow();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+TString DumpToYson(T obj)
+{
+ return ConvertToYsonString(obj, NYson::EYsonFormat::Text).ToString();
+}
+
+TEST(TLegacyReadLimitTest, Simple)
+{
+ TLegacyReadLimit limit;
+ EXPECT_EQ("{}", DumpToYson(limit));
+ EXPECT_TRUE(limit.IsTrivial());
+
+ limit.SetRowIndex(0);
+ EXPECT_EQ("{\"row_index\"=0;}", DumpToYson(limit));
+ EXPECT_FALSE(limit.IsTrivial());
+}
+
+TEST(TLegacyReadRangeTest, Simple)
+{
+ TLegacyReadRange range;
+ EXPECT_EQ("{}", DumpToYson(range));
+}
+
+TEST(TLegacyReadRangeTest, Simple2)
+{
+ auto range = ConvertTo<TLegacyReadRange>(TYsonString(TStringBuf("{lower_limit={row_index=1};upper_limit={row_index=2}}")));
+ EXPECT_EQ(1, range.LowerLimit().GetRowIndex());
+ EXPECT_EQ(2, range.UpperLimit().GetRowIndex());
+}
+
+TEST(TReadLimitTest, ProtobufConversion)
+{
+ TReadLimit readLimit;
+ auto keyBound = TOwningKeyBound::FromRow(/* prefix */MakeRow({1, 42}), /* isUpper */true, /* isInclusive */true);
+ readLimit.KeyBound() = keyBound;
+ readLimit.SetRowIndex(1);
+ readLimit.SetOffset(2);
+ readLimit.SetChunkIndex(3);
+ readLimit.SetTabletIndex(4);
+
+ NProto::TReadLimit protoReadLimit;
+ ToProto(&protoReadLimit, readLimit);
+
+ TReadLimit newReadLimit(protoReadLimit, /* isUpper */true);
+
+ EXPECT_EQ(newReadLimit.KeyBound(), keyBound);
+ EXPECT_EQ(newReadLimit.GetRowIndex(), 1);
+ EXPECT_EQ(newReadLimit.GetOffset(), 2);
+ EXPECT_EQ(newReadLimit.GetChunkIndex(), 3);
+ EXPECT_EQ(newReadLimit.GetTabletIndex(), 4);
+}
+
+TEST(TReadLimitTest, LegacyKey)
+{
+ TReadLimit readLimit;
+ auto keyBound = TOwningKeyBound::FromRow(/* prefix */MakeRow({1, 42}), /* isInclusive */true, /* isUpper */true);
+ readLimit.KeyBound() = keyBound;
+ readLimit.SetRowIndex(1);
+ readLimit.SetOffset(2);
+ readLimit.SetChunkIndex(3);
+ readLimit.SetTabletIndex(4);
+ EXPECT_FALSE(readLimit.IsTrivial());
+
+ NProto::TReadLimit protoReadLimit;
+ ToProto(&protoReadLimit, readLimit);
+ protoReadLimit.clear_key_bound_prefix();
+ protoReadLimit.clear_key_bound_is_inclusive();
+
+ TReadLimit newReadLimit(protoReadLimit, /* isUpper */true, /* keyLength */2);
+
+ EXPECT_EQ(newReadLimit.KeyBound(), keyBound);
+ EXPECT_EQ(newReadLimit.GetRowIndex(), 1);
+ EXPECT_EQ(newReadLimit.GetOffset(), 2);
+ EXPECT_EQ(newReadLimit.GetChunkIndex(), 3);
+ EXPECT_EQ(newReadLimit.GetTabletIndex(), 4);
+ EXPECT_FALSE(newReadLimit.IsTrivial());
+}
+
+TEST(TReadLimitTest, Interop)
+{
+ TReadLimit readLimitA;
+ readLimitA.SetRowIndex(1);
+ readLimitA.SetOffset(2);
+ readLimitA.SetChunkIndex(3);
+ readLimitA.SetTabletIndex(4);
+
+ TLegacyReadLimit legacyReadLimitA;
+ legacyReadLimitA.SetRowIndex(1);
+ legacyReadLimitA.SetOffset(2);
+ legacyReadLimitA.SetChunkIndex(3);
+ legacyReadLimitA.SetTabletIndex(4);
+
+ // I am too lazy to write a proper comparison operator,
+ // so let us just compare string read limit representations.
+
+ EXPECT_EQ(ToString(legacyReadLimitA), ToString(ReadLimitToLegacyReadLimit(readLimitA)));
+ EXPECT_EQ(ToString(readLimitA), ToString(ReadLimitFromLegacyReadLimitKeyless(legacyReadLimitA)));
+ EXPECT_EQ(ToString(readLimitA), ToString(ReadLimitFromLegacyReadLimit(legacyReadLimitA, /* isUpper */ true, /* keyLength */ 1)));
+
+ TReadLimit readLimitB;
+ readLimitB.KeyBound() = TOwningKeyBound::FromRow() > MakeRow({42});
+
+ TLegacyReadLimit legacyReadLimitB;
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedInt64Value(42));
+ builder.AddValue(MakeUnversionedSentinelValue(EValueType::Max));
+ legacyReadLimitB.SetLegacyKey(builder.FinishRow());
+
+ EXPECT_EQ(ToString(legacyReadLimitB), ToString(ReadLimitToLegacyReadLimit(readLimitB)));
+ EXPECT_EQ(ToString(readLimitB), ToString(ReadLimitFromLegacyReadLimit(legacyReadLimitB, /* isUpper */ false, /* keyLength */ 1)));
+ // Crashes: ReadLimitFromLegacyReadLimitKeyless(legacyReadLimitB).
+}
+
+TEST(TReadLimitTest, Trivial)
+{
+ TReadLimit readLimit;
+ EXPECT_TRUE(readLimit.IsTrivial());
+
+ NProto::TReadLimit protoReadLimit;
+ ToProto(&protoReadLimit, readLimit);
+
+ TReadLimit newReadLimit(protoReadLimit, /* isUpper */true);
+ EXPECT_TRUE(newReadLimit.IsTrivial());
+}
+
+TEST(TReadRangeTest, ProtobufConversion)
+{
+ TReadRange readRange;
+ auto lowerKeyBound = TOwningKeyBound::FromRow(/* prefix */MakeRow({1, 42}), /* isInclusive */true, /* isUpper */false);
+ readRange.LowerLimit().KeyBound() = lowerKeyBound;
+ readRange.LowerLimit().SetRowIndex(1);
+ auto upperKeyBound = TOwningKeyBound::FromRow(/* prefix */MakeRow({12, 13}), /* isInclusive */true, /* isUpper */true);
+ readRange.UpperLimit().KeyBound() = upperKeyBound;
+ readRange.UpperLimit().SetRowIndex(2);
+
+ NProto::TReadRange protoReadRange;
+ ToProto(&protoReadRange, readRange);
+
+ TReadRange newReadRange(protoReadRange);
+
+ EXPECT_EQ(newReadRange.LowerLimit().KeyBound(), lowerKeyBound);
+ EXPECT_EQ(newReadRange.LowerLimit().GetRowIndex(), 1);
+ EXPECT_EQ(newReadRange.UpperLimit().KeyBound(), upperKeyBound);
+ EXPECT_EQ(newReadRange.UpperLimit().GetRowIndex(), 2);
+}
+
+TEST(TReadRangeTest, Trvial)
+{
+ TReadRange readRange;
+ EXPECT_TRUE(readRange.LowerLimit().IsTrivial());
+ EXPECT_TRUE(readRange.UpperLimit().IsTrivial());
+
+ NProto::TReadRange protoReadRange;
+ ToProto(&protoReadRange, readRange);
+
+ TReadRange newReadRange(protoReadRange);
+
+ EXPECT_TRUE(newReadRange.LowerLimit().IsTrivial());
+ EXPECT_TRUE(newReadRange.UpperLimit().IsTrivial());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NChunkClient
+
diff --git a/yt/yt/client/unittests/replication_progress_ut.cpp b/yt/yt/client/unittests/replication_progress_ut.cpp
new file mode 100644
index 0000000000..0c00a02d69
--- /dev/null
+++ b/yt/yt/client/unittests/replication_progress_ut.cpp
@@ -0,0 +1,410 @@
+#include <yt/yt/client/chaos_client/replication_card.h>
+#include <yt/yt/client/chaos_client/replication_card_serialization.h>
+
+#include <yt/yt/core/yson/string.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <library/cpp/iterator/zip.h>
+
+namespace NYT::NChaosClient {
+namespace {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool Equal(const TReplicationProgress& lhs, const TReplicationProgress& rhs)
+{
+ if (lhs.UpperKey != rhs.UpperKey || lhs.Segments.size() != rhs.Segments.size()) {
+ return false;
+ }
+ for (const auto& [lhs, rhs] : Zip(lhs.Segments, rhs.Segments)) {
+ if (lhs.Timestamp != rhs.Timestamp || lhs.LowerKey != rhs.LowerKey) {
+ return false;
+ }
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUpdateReplicationProgressTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<
+ const char*,
+ const char*,
+ const char*>>
+{ };
+
+TEST_P(TUpdateReplicationProgressTest, Simple)
+{
+ const auto& params = GetParam();
+ auto progress = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<0>(params)));
+ const auto& update = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<1>(params)));
+ const auto& expected = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<2>(params)));
+
+ UpdateReplicationProgress(&progress, update);
+
+ EXPECT_TRUE(Equal(progress, expected))
+ << "progress: " << std::get<0>(params) << std::endl
+ << "update: " << std::get<1>(params) << std::endl
+ << "expected: " << std::get<2>(params) << std::endl
+ << "actual: " << ConvertToYsonString(progress, EYsonFormat::Text).AsStringBuf() << std::endl;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TUpdateReplicationProgressTest,
+ TUpdateReplicationProgressTest,
+ ::testing::Values(
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}"),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1u}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1u}];upper_key=[<type=max>#]}"),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[1];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}"),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[1];timestamp=1}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1u}];upper_key=[<type=max>#]}"),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[1];timestamp=1}];upper_key=[2]}",
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1};{lower_key=[2];timestamp=0}];upper_key=[<type=max>#]}"),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[2]}",
+ "{segments=[{lower_key=[1];timestamp=1}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1}];upper_key=[2]}"),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[2];timestamp=2}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[1];timestamp=1};{lower_key=[3];timestamp=3}];upper_key=[4]}",
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1};{lower_key=[2];timestamp=2};{lower_key=[3];timestamp=3};{lower_key=[4];timestamp=2}];upper_key=[<type=max>#]}")
+));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCompareReplicationProgressTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<
+ const char*,
+ const char*,
+ bool>>
+{ };
+
+TEST_P(TCompareReplicationProgressTest, Simple)
+{
+ const auto& params = GetParam();
+ const auto& progress = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<0>(params)));
+ const auto& other = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<1>(params)));
+ bool expected = std::get<2>(params);
+
+ bool result = IsReplicationProgressGreaterOrEqual(progress, other);
+
+ EXPECT_EQ(result, expected)
+ << "progress: " << std::get<0>(params) << std::endl
+ << "other: " << std::get<1>(params) << std::endl
+ << "expected: " << expected << std::endl
+ << "actual: " << result << std::endl;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TCompareReplicationProgressTest,
+ TCompareReplicationProgressTest,
+ ::testing::Values(
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ true),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1}];upper_key=[<type=max>#]}",
+ false),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=1};{lower_key=[1];timestamp=0};];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1}];upper_key=[<type=max>#]}",
+ false),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=1}];upper_key=[1]}",
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=2}];upper_key=[<type=max>#]}",
+ true),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[2];timestamp=1};{lower_key=[4];timestamp=2}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[1];timestamp=0};{lower_key=[3];timestamp=1}];upper_key=[4]}",
+ true),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=1};{lower_key=[1];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1}];upper_key=[1]}",
+ true),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1};{lower_key=[2];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1};{lower_key=[2];timestamp=0}];upper_key=[<type=max>#]}",
+ true),
+ std::make_tuple(
+ "{segments=[{lower_key=[1];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1};{lower_key=[1];timestamp=0}];upper_key=[<type=max>#]}",
+ true),
+ std::make_tuple(
+ "{segments=[{lower_key=[2];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1};{lower_key=[1];timestamp=0}];upper_key=[<type=max>#]}",
+ true),
+ std::make_tuple(
+ "{segments=[{lower_key=[2];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=1};{lower_key=[3];timestamp=0}];upper_key=[<type=max>#]}",
+ false),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=0}];upper_key=[<type=max>#]}",
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1}];upper_key=[<type=max>#]}",
+ false)
+));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGatherReplicationProgressTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<
+ std::vector<const char*>,
+ const char*>>
+{ };
+
+TEST_P(TGatherReplicationProgressTest, Simple)
+{
+ const auto& params = GetParam();
+ const auto& serializedProgresses = std::get<0>(params);
+ const auto& expected = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<1>(params)));
+
+ std::vector<TReplicationProgress> progresses;
+ std::vector<TUnversionedRow> pivotKeys;
+ for (const auto& serialized : serializedProgresses) {
+ progresses.push_back(ConvertTo<TReplicationProgress>(TYsonStringBuf(serialized)));
+ pivotKeys.push_back(progresses.back().Segments.front().LowerKey);
+ }
+
+ auto result = GatherReplicationProgress(progresses, pivotKeys, progresses.back().UpperKey.Get());
+
+ EXPECT_TRUE(IsReplicationProgressEqual(result, expected))
+ << "progresses: " << Format("%v", std::get<0>(params)) << std::endl
+ << "expected: " << std::get<1>(params) << std::endl
+ << "actual: " << ConvertToYsonString(result, EYsonFormat::Text).AsStringBuf() << std::endl;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TGatherReplicationProgressTest,
+ TGatherReplicationProgressTest,
+ ::testing::Values(
+ std::make_tuple(
+ std::vector<const char*>{
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[1]}",
+ "{segments=[{lower_key=[1];timestamp=1}];upper_key=[2]}"
+ },
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1}];upper_key=[2]}"),
+ std::make_tuple(
+ std::vector<const char*>{
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[1]}",
+ "{segments=[{lower_key=[0];timestamp=0}];upper_key=[2]}"
+ },
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[2]}"),
+ std::make_tuple(
+ std::vector<const char*>{
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[1]}",
+ "{segments=[{lower_key=[1];timestamp=1}];upper_key=[2]}",
+ "{segments=[{lower_key=[2];timestamp=1}];upper_key=[3]}"
+ },
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1}];upper_key=[3]}"),
+ std::make_tuple(
+ std::vector<const char*>{
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[1]}",
+ "{segments=[{lower_key=[1];timestamp=0}];upper_key=[2]}",
+ "{segments=[{lower_key=[2];timestamp=1}];upper_key=[3]}"
+ },
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[2];timestamp=1}];upper_key=[3]}")
+));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TScatterReplicationProgressTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<
+ const char*,
+ const char*,
+ const char*,
+ std::vector<const char*>>>
+{ };
+
+TEST_P(TScatterReplicationProgressTest, Simple)
+{
+ const auto& params = GetParam();
+ const auto& progress = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<0>(params)));
+ const auto& owningPivotKeys = ConvertTo<std::vector<TUnversionedOwningRow>>(TYsonStringBuf(std::get<1>(params)));
+ const auto& upperKey = ConvertTo<TUnversionedOwningRow>(TYsonStringBuf(std::get<2>(params)));
+ const auto& serializedExpected = std::get<3>(params);
+
+ std::vector<TUnversionedRow> pivotKeys;
+ for (const auto& row : owningPivotKeys) {
+ pivotKeys.push_back(row.Get());
+ }
+
+ std::vector<TReplicationProgress> expected;
+ for (const auto& serialized : serializedExpected) {
+ expected.push_back(ConvertTo<TReplicationProgress>(TYsonStringBuf(serialized)));
+ }
+
+ auto result = ScatterReplicationProgress(progress, pivotKeys, upperKey.Get());
+ bool allEqual = true;
+ if (expected.size() != result.size()) {
+ allEqual = false;
+ } else {
+ for (int index = 0; index < std::ssize(expected); ++index) {
+ if (!IsReplicationProgressEqual(result[index], expected[index])) {
+ allEqual = false;
+ break;
+ }
+ }
+ }
+
+ EXPECT_TRUE(allEqual)
+ << "progresses: " << std::get<0>(params) << std::endl
+ << "pivot keys: " << std::get<1>(params) << std::endl
+ << "upper key: " << std::get<2>(params) << std::endl
+ << "expected: " << Format("%v", expected) << std::endl
+ << "actual: " << Format("%v", result) << std::endl;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TScatterReplicationProgressTest,
+ TScatterReplicationProgressTest,
+ ::testing::Values(
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "[[]; [1]]",
+ "[<type=max>#]",
+ std::vector<const char*>{
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[1]}",
+ "{segments=[{lower_key=[1];timestamp=0}];upper_key=[<type=max>#]}"
+ }),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[<type=max>#]}",
+ "[[]; [1]]",
+ "[2]",
+ std::vector<const char*>{
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[1]}",
+ "{segments=[{lower_key=[1];timestamp=0}];upper_key=[2]}"
+ }),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1}];upper_key=[2]}",
+ "[[]; [1]]",
+ "[2]",
+ std::vector<const char*>{
+ "{segments=[{lower_key=[];timestamp=0}];upper_key=[1]}",
+ "{segments=[{lower_key=[1];timestamp=1}];upper_key=[2]}"
+ }),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[2];timestamp=1};{lower_key=[4];timestamp=0}];upper_key=[6]}",
+ "[[1]; [3]]",
+ "[5]",
+ std::vector<const char*>{
+ "{segments=[{lower_key=[1];timestamp=0};{lower_key=[2];timestamp=1}];upper_key=[3]}",
+ "{segments=[{lower_key=[3];timestamp=1};{lower_key=[4];timestamp=0}];upper_key=[5]}",
+ }),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[2];timestamp=1};{lower_key=[4];timestamp=0}];upper_key=[6]}",
+ "[[1]; [4]]",
+ "[6]",
+ std::vector<const char*>{
+ "{segments=[{lower_key=[1];timestamp=0};{lower_key=[2];timestamp=1}];upper_key=[4]}",
+ "{segments=[{lower_key=[4];timestamp=0}];upper_key=[6]}"
+ }),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[2];timestamp=1};{lower_key=[4];timestamp=0}];upper_key=[6]}",
+ "[[3]; [4]]",
+ "[5]",
+ std::vector<const char*>{
+ "{segments=[{lower_key=[3];timestamp=1}];upper_key=[4]}",
+ "{segments=[{lower_key=[4];timestamp=0}];upper_key=[5]}"
+ }),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[2];timestamp=1};{lower_key=[4];timestamp=0}];upper_key=[6]}",
+ "[[1]; [2]; [3]; [4]; [5]]",
+ "[6]",
+ std::vector<const char*>{
+ "{segments=[{lower_key=[1];timestamp=0}];upper_key=[2]}",
+ "{segments=[{lower_key=[2];timestamp=1}];upper_key=[3]}",
+ "{segments=[{lower_key=[3];timestamp=1}];upper_key=[4]}",
+ "{segments=[{lower_key=[4];timestamp=0}];upper_key=[5]}",
+ "{segments=[{lower_key=[5];timestamp=0}];upper_key=[6]}"
+ })
+));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReplicationProgressTimestampForKeyTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<std::tuple<
+ const char*,
+ const char*,
+ std::optional<TTimestamp>>>
+{ };
+
+TEST_P(TReplicationProgressTimestampForKeyTest, Simple)
+{
+ const auto& params = GetParam();
+ const auto& progress = ConvertTo<TReplicationProgress>(TYsonStringBuf(std::get<0>(params)));
+ const auto& key = ConvertTo<TUnversionedOwningRow>(TYsonStringBuf(std::get<1>(params)));
+ const auto& expected = std::get<2>(params);
+
+ auto result = FindReplicationProgressTimestampForKey(progress, key.Elements());
+
+ EXPECT_EQ(result, expected)
+ << "progresses: " << std::get<0>(params) << std::endl
+ << "key: " << std::get<1>(params) << std::endl
+ << "expected: " << Format("%v", expected) << std::endl
+ << "actual: " << Format("%v", result) << std::endl;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TReplicationProgressTimestampForKeyTest,
+ TReplicationProgressTimestampForKeyTest,
+ ::testing::Values(
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=1}];upper_key=[<type=max>#]}",
+ "[1]",
+ 1),
+ std::make_tuple(
+ "{segments=[{lower_key=[1];timestamp=1}];upper_key=[<type=max>#]}",
+ "[]",
+ std::nullopt),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=1}];upper_key=[1]}",
+ "[1]",
+ std::nullopt),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=1}];upper_key=[1]}",
+ "[2]",
+ std::nullopt),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1}];upper_key=[<type=max>#]}",
+ "[1]",
+ 1),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=0};{lower_key=[1];timestamp=1};{lower_key=[2];timestamp=2};];upper_key=[<type=max>#]}",
+ "[1]",
+ 1),
+ std::make_tuple(
+ "{segments=[{lower_key=[];timestamp=1};{lower_key=[2];timestamp=2};];upper_key=[<type=max>#]}",
+ "[1]",
+ 1)
+));
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NChaosClient
diff --git a/yt/yt/client/unittests/row_helpers.cpp b/yt/yt/client/unittests/row_helpers.cpp
new file mode 100644
index 0000000000..d28628c5ab
--- /dev/null
+++ b/yt/yt/client/unittests/row_helpers.cpp
@@ -0,0 +1,70 @@
+#include "row_helpers.h"
+
+#include <yt/yt/core/yson/string.h>
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NYT {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void EnsureTypesMatch(EValueType expected, EValueType actual)
+{
+ if (expected != actual) {
+ THROW_ERROR_EXCEPTION("Unexpected type of TUnversionedValue: expected %Qlv, actual %Qlv",
+ expected,
+ actual);
+ }
+}
+
+i64 GetInt64(const TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::Int64, row.Type);
+ return row.Data.Int64;
+}
+
+ui64 GetUint64(const TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::Uint64, row.Type);
+ return row.Data.Uint64;
+}
+
+double GetDouble(const NTableClient::TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::Double, row.Type);
+ return row.Data.Double;
+}
+
+bool GetBoolean(const TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::Boolean, row.Type);
+ return row.Data.Boolean;
+}
+
+TString GetString(const TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::String, row.Type);
+ return row.AsString();
+}
+
+NYTree::INodePtr GetAny(const NTableClient::TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::Any, row.Type);
+ return NYTree::ConvertToNode(NYson::TYsonString(row.AsString()));
+}
+
+NYTree::INodePtr GetComposite(const NTableClient::TUnversionedValue& row)
+{
+ EnsureTypesMatch(EValueType::Composite, row.Type);
+ return NYTree::ConvertToNode(NYson::TYsonString(row.AsString()));
+}
+
+bool IsNull(const NTableClient::TUnversionedValue& row)
+{
+ return row.Type == EValueType::Null;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/unittests/row_helpers.h b/yt/yt/client/unittests/row_helpers.h
new file mode 100644
index 0000000000..4a3fbd854f
--- /dev/null
+++ b/yt/yt/client/unittests/row_helpers.h
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/value_consumer.h>
+
+#include <vector>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCollectingValueConsumer
+ : public NTableClient::IValueConsumer
+{
+public:
+ explicit TCollectingValueConsumer(NTableClient::TTableSchemaPtr schema = New<NTableClient::TTableSchema>())
+ : Schema_(std::move(schema))
+ { }
+
+ explicit TCollectingValueConsumer(NTableClient::TNameTablePtr nameTable, NTableClient::TTableSchemaPtr schema = New<NTableClient::TTableSchema>())
+ : Schema_(std::move(schema))
+ , NameTable_(std::move(nameTable))
+ { }
+
+ const NTableClient::TNameTablePtr& GetNameTable() const override
+ {
+ return NameTable_;
+ }
+
+ const NTableClient::TTableSchemaPtr& GetSchema() const override
+ {
+ return Schema_;
+ }
+
+ bool GetAllowUnknownColumns() const override
+ {
+ return true;
+ }
+
+ void OnBeginRow() override
+ { }
+
+ void OnValue(const NTableClient::TUnversionedValue& value) override
+ {
+ Builder_.AddValue(value);
+ }
+
+ void OnEndRow() override
+ {
+ RowList_.emplace_back(Builder_.FinishRow());
+ }
+
+ NTableClient::TUnversionedRow GetRow(size_t rowIndex)
+ {
+ return RowList_.at(rowIndex);
+ }
+
+ std::optional<NTableClient::TUnversionedValue> FindRowValue(size_t rowIndex, TStringBuf columnName) const
+ {
+ NTableClient::TUnversionedRow row = RowList_.at(rowIndex);
+ auto id = GetNameTable()->GetIdOrThrow(columnName);
+
+ for (const auto& value : row) {
+ if (value.Id == id) {
+ return value;
+ }
+ }
+ return std::nullopt;
+ }
+
+ NTableClient::TUnversionedValue GetRowValue(size_t rowIndex, TStringBuf columnName) const
+ {
+ auto row = FindRowValue(rowIndex, columnName);
+ if (!row) {
+ THROW_ERROR_EXCEPTION("Cannot find column %Qv", columnName);
+ }
+ return *row;
+ }
+
+ size_t Size() const
+ {
+ return RowList_.size();
+ }
+
+ const std::vector<NTableClient::TUnversionedOwningRow>& GetRowList() const {
+ return RowList_;
+ }
+
+private:
+ const NTableClient::TTableSchemaPtr Schema_;
+ const NTableClient::TNameTablePtr NameTable_ = New<NTableClient::TNameTable>();
+ NTableClient::TUnversionedOwningRowBuilder Builder_;
+ std::vector<NTableClient::TUnversionedOwningRow> RowList_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+i64 GetInt64(const NTableClient::TUnversionedValue& row);
+ui64 GetUint64(const NTableClient::TUnversionedValue& row);
+double GetDouble(const NTableClient::TUnversionedValue& row);
+bool GetBoolean(const NTableClient::TUnversionedValue& row);
+TString GetString(const NTableClient::TUnversionedValue& row);
+NYTree::INodePtr GetAny(const NTableClient::TUnversionedValue& row);
+NYTree::INodePtr GetComposite(const NTableClient::TUnversionedValue& row);
+bool IsNull(const NTableClient::TUnversionedValue& row);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/unittests/row_ut.cpp b/yt/yt/client/unittests/row_ut.cpp
new file mode 100644
index 0000000000..2eacf5e19c
--- /dev/null
+++ b/yt/yt/client/unittests/row_ut.cpp
@@ -0,0 +1,165 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/versioned_row.h>
+#include <yt/yt/client/table_client/row_buffer.h>
+
+#include <yt/yt/core/misc/protobuf_helpers.h>
+
+#include <limits>
+
+namespace NYT::NTableClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CheckSerialize(TUnversionedRow original)
+{
+ auto serialized = NYT::ToProto<TString>(original);
+ auto deserialized = NYT::FromProto<TUnversionedOwningRow>(serialized);
+
+ ASSERT_EQ(original, deserialized);
+}
+
+TEST(TUnversionedRowTest, Serialize1)
+{
+ TUnversionedOwningRowBuilder builder;
+ auto row = builder.FinishRow();
+ CheckSerialize(row);
+}
+
+TEST(TUnversionedRowTest, Serialize2)
+{
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedSentinelValue(EValueType::Null, 0));
+ builder.AddValue(MakeUnversionedInt64Value(42, 1));
+ builder.AddValue(MakeUnversionedDoubleValue(0.25, 2));
+ CheckSerialize(builder.FinishRow());
+}
+
+TEST(TUnversionedRowTest, Serialize3)
+{
+ // TODO(babenko): cannot test Any type at the moment since CompareRowValues does not work
+ // for it.
+ TUnversionedOwningRowBuilder builder;
+ builder.AddValue(MakeUnversionedStringValue("string1", 10));
+ builder.AddValue(MakeUnversionedInt64Value(1234, 20));
+ builder.AddValue(MakeUnversionedStringValue("string2", 30));
+ builder.AddValue(MakeUnversionedDoubleValue(4321.0, 1000));
+ builder.AddValue(MakeUnversionedStringValue("", 10000));
+ CheckSerialize(builder.FinishRow());
+}
+
+TEST(TUnversionedRowTest, Serialize4)
+{
+ // TODO(babenko): cannot test Any type at the moment since CompareRowValues does not work
+ // for it.
+ TUnversionedRowBuilder builder;
+ builder.AddValue(MakeUnversionedStringValue("string1"));
+ builder.AddValue(MakeUnversionedStringValue("string2"));
+ CheckSerialize(builder.GetRow());
+}
+
+TEST(TUnversionedRowTest, Serialize5)
+{
+ CheckSerialize(TUnversionedRow());
+}
+
+TEST(TUnversionedValueTest, CompareNaN)
+{
+ auto nanValue = MakeUnversionedDoubleValue(std::numeric_limits<double>::quiet_NaN());
+ auto doubleValue = MakeUnversionedDoubleValue(3.14);
+ EXPECT_EQ(CompareRowValues(nanValue, nanValue), 0);
+ EXPECT_EQ(CompareRowValues(nanValue, doubleValue), 1);
+ EXPECT_EQ(CompareRowValues(doubleValue, nanValue), -1);
+
+ static const char* stringValueData = "foo";
+ auto stringValue = MakeUnversionedStringValue(stringValueData);
+
+ EXPECT_NO_THROW(CompareRowValues(nanValue, stringValue));
+ EXPECT_NO_THROW(CompareRowValues(stringValue, nanValue));
+}
+
+TEST(TUnversionedValueTest, CompareComposite)
+{
+ auto compositeValue = MakeUnversionedCompositeValue("[]");
+ auto stringValue = MakeUnversionedStringValue("foo");
+ auto anyValue = MakeUnversionedAnyValue("[]");
+ auto nullValue = MakeUnversionedSentinelValue(EValueType::Null);
+ EXPECT_THROW_WITH_SUBSTRING(CompareRowValues(compositeValue, stringValue), "Cannot compare values of types");
+ EXPECT_THROW_WITH_SUBSTRING(CompareRowValues(stringValue, compositeValue), "Cannot compare values of types");
+
+ EXPECT_THROW_WITH_SUBSTRING(CompareRowValues(compositeValue, anyValue), "Cannot compare values of types");
+ EXPECT_THROW_WITH_SUBSTRING(CompareRowValues(anyValue, compositeValue), "Cannot compare values of types");
+
+ EXPECT_TRUE(CompareRowValues(compositeValue, nullValue) > 0);
+ EXPECT_TRUE(CompareRowValues(nullValue, compositeValue) < 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TFormatTest, UnversionedValue)
+{
+ auto value = MakeUnversionedInt64Value(123, 7, EValueFlags::Aggregate | EValueFlags::Hunk);
+
+ EXPECT_EQ(Format("%v", value), "%&7#123");
+ EXPECT_EQ(Format("%kv", value), "123");
+}
+
+TEST(TFormatTest, VersionedValue)
+{
+ auto value = MakeInt64Value<TVersionedValue>(123, 7, EValueFlags::Aggregate | EValueFlags::Hunk);
+ value.Timestamp = 0x1234567890abcdef;
+
+ EXPECT_EQ(Format("%v", value), "%&7#123@1234567890abcdef");
+}
+
+TEST(TFormatTest, UnversionedRow)
+{
+ auto value1 = MakeUnversionedInt64Value(123, 2, EValueFlags::Aggregate);
+ auto value2 = MakeUnversionedInt64Value(234, 3, EValueFlags::Hunk);
+ auto value3 = MakeUnversionedInt64Value(345, 4, EValueFlags::Aggregate | EValueFlags::Hunk);
+
+ TUnversionedRowBuilder builder;
+ builder.AddValue(value1);
+ builder.AddValue(value2);
+ builder.AddValue(value3);
+ auto row = builder.GetRow();
+
+ EXPECT_EQ(Format("%v", row), "[%2#123, &3#234, %&4#345]");
+ EXPECT_EQ(Format("%kv", row), "[123, 234, 345]");
+}
+
+TEST(TFormatTest, VersionedRow)
+{
+ auto key1 = MakeUnversionedInt64Value(6, 0);
+ auto key2 = MakeUnversionedInt64Value(7, 1, EValueFlags::Hunk);
+ auto key3 = MakeUnversionedInt64Value(8, 2, EValueFlags::Aggregate);
+
+ auto value1 = MakeInt64Value<TVersionedValue>(123, 3, EValueFlags::Aggregate);
+ value1.Timestamp = 0xaaa;
+ auto value2 = MakeInt64Value<TVersionedValue>(234, 4, EValueFlags::Hunk);
+ value2.Timestamp = 0xbbb;
+ auto value3 = MakeInt64Value<TVersionedValue>(345, 5, EValueFlags::Aggregate | EValueFlags::Hunk);
+ value3.Timestamp = 0xccc;
+
+ TVersionedRowBuilder builder(New<TRowBuffer>());
+ builder.AddKey(key1);
+ builder.AddKey(key2);
+ builder.AddKey(key3);
+
+ builder.AddValue(value1);
+ builder.AddValue(value2);
+ builder.AddValue(value3);
+
+ builder.AddDeleteTimestamp(0xeee);
+
+ auto row = builder.FinishRow();
+
+ EXPECT_EQ(Format("%v", row), "[6, 7, 8 | %3#123@aaa, &4#234@bbb, %&5#345@ccc | ccc, bbb, aaa | eee]");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/schema_ut.cpp b/yt/yt/client/unittests/schema_ut.cpp
new file mode 100644
index 0000000000..3482b866b5
--- /dev/null
+++ b/yt/yt/client/unittests/schema_ut.cpp
@@ -0,0 +1,506 @@
+#include "logical_type_shortcuts.h"
+#include "yt/yt/client/table_client/logical_type.h"
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/comparator.h>
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/schema_serialization_helpers.h>
+
+#include <yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.pb.h>
+
+#include <yt/yt/core/ytree/convert.h>
+
+#include <random>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TColumnSchema ColumnFromYson(const TString& yson)
+{
+ auto maybeDeletedColumn = ConvertTo<TMaybeDeletedColumnSchema>(TYsonStringBuf(yson));
+ YT_VERIFY(!maybeDeletedColumn.Deleted());
+ return static_cast<TColumnSchema>(maybeDeletedColumn);
+}
+
+TEST(TTableSchemaTest, ColumnTypeV1Deserialization)
+{
+ using namespace NLogicalTypeShortcuts;
+
+ {
+ auto column = ColumnFromYson(
+ "{"
+ " name=x;"
+ " type=int64;"
+ "}");
+ EXPECT_EQ(*column.LogicalType(), *Optional(Int64()));
+ EXPECT_EQ(column.IsOfV1Type(), true);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Int64), true);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Uint64), false);
+ EXPECT_EQ(column.CastToV1Type(), ESimpleLogicalValueType::Int64);
+ EXPECT_EQ(column.GetWireType(), EValueType::Int64);
+ EXPECT_EQ(column.Required(), false);
+ EXPECT_EQ(IsV3Composite(column.LogicalType()), false);
+ }
+
+ {
+ auto column = ColumnFromYson(
+ "{"
+ " name=x;"
+ " type=uint64;"
+ " required=%true"
+ "}");
+ EXPECT_EQ(*column.LogicalType(), *Uint64());
+ EXPECT_EQ(column.IsOfV1Type(), true);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Uint64), true);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Int64), false);
+ EXPECT_EQ(column.CastToV1Type(), ESimpleLogicalValueType::Uint64);
+ EXPECT_EQ(column.GetWireType(), EValueType::Uint64);
+ EXPECT_EQ(column.Required(), true);
+ EXPECT_EQ(IsV3Composite(column.LogicalType()), false);
+ }
+
+ {
+ auto column = ColumnFromYson(
+ "{"
+ " name=x;"
+ " type=null;"
+ "}");
+ EXPECT_EQ(*column.LogicalType(), *SimpleLogicalType(ESimpleLogicalValueType::Null));
+ EXPECT_EQ(column.Required(), false);
+ EXPECT_EQ(column.IsOfV1Type(), true);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Null), true);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Int64), false);
+ EXPECT_EQ(column.CastToV1Type(), ESimpleLogicalValueType::Null);
+ EXPECT_EQ(column.GetWireType(), EValueType::Null);
+ EXPECT_EQ(column.Required(), false);
+ EXPECT_EQ(IsV3Composite(column.LogicalType()), false);
+ }
+
+ EXPECT_ANY_THROW(ColumnFromYson(
+ "{"
+ " name=x;"
+ " type=null;"
+ " required=%true;"
+ "}"));
+}
+
+TEST(TTableSchemaTest, ColumnTypeV3Deserialization)
+{
+ using namespace NLogicalTypeShortcuts;
+ auto listUtf8Column = ColumnFromYson(R"(
+ {
+ name=x;
+ type_v3={
+ type_name=list;
+ item=utf8;
+ }
+ }
+ )");
+ EXPECT_EQ(*listUtf8Column.LogicalType(), *List(Utf8()));
+ EXPECT_EQ(listUtf8Column.Required(), true);
+ EXPECT_EQ(listUtf8Column.IsOfV1Type(), false);
+ EXPECT_EQ(listUtf8Column.IsOfV1Type(ESimpleLogicalValueType::Utf8), false);
+ EXPECT_EQ(listUtf8Column.IsOfV1Type(ESimpleLogicalValueType::Any), false);
+ EXPECT_EQ(listUtf8Column.CastToV1Type(), ESimpleLogicalValueType::Any);
+ EXPECT_EQ(listUtf8Column.GetWireType(), EValueType::Composite);
+ EXPECT_EQ(IsV3Composite(listUtf8Column.LogicalType()), true);
+
+ {
+ auto column = ColumnFromYson(R"(
+ {
+ name=x;
+ type_v3={
+ type_name=list;
+ item=utf8;
+ };
+ required=%true;
+ }
+ )");
+ EXPECT_EQ(column, listUtf8Column);
+ }
+
+ {
+ auto column = ColumnFromYson(R"(
+ {
+ name=x;
+ type_v3={
+ type_name=list;
+ item=utf8;
+ };
+ type=any;
+ }
+ )");
+ EXPECT_EQ(column, listUtf8Column);
+ }
+
+ {
+ auto column = ColumnFromYson(R"(
+ {
+ name=x;
+ type_v3={
+ type_name=optional;
+ item={
+ type_name=optional;
+ item=utf8;
+ }
+ };
+ type=any;
+ required=%false;
+ }
+ )");
+ EXPECT_EQ(*column.LogicalType(), *Optional(Optional(Utf8())));
+ EXPECT_EQ(column.Required(), false);
+ EXPECT_EQ(column.IsOfV1Type(), false);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Utf8), false);
+ EXPECT_EQ(column.IsOfV1Type(ESimpleLogicalValueType::Any), false);
+ EXPECT_EQ(column.CastToV1Type(), ESimpleLogicalValueType::Any);
+ EXPECT_EQ(column.GetWireType(), EValueType::Composite);
+ EXPECT_EQ(IsV3Composite(column.LogicalType()), true);
+ }
+
+ {
+ auto decimalColumn = ColumnFromYson(R"(
+ {
+ name=x;
+ type_v3={
+ type_name=decimal;
+ precision=4;
+ scale=2;
+ }
+ }
+ )");
+ EXPECT_EQ(*decimalColumn.LogicalType(), *Decimal(4, 2));
+ EXPECT_EQ(decimalColumn.Required(), true);
+ EXPECT_EQ(decimalColumn.IsOfV1Type(), false);
+ EXPECT_EQ(decimalColumn.IsOfV1Type(ESimpleLogicalValueType::String), false);
+ EXPECT_EQ(decimalColumn.IsOfV1Type(ESimpleLogicalValueType::Any), false);
+ EXPECT_EQ(decimalColumn.CastToV1Type(), ESimpleLogicalValueType::String);
+ EXPECT_EQ(decimalColumn.GetWireType(), EValueType::String);
+ EXPECT_EQ(IsV3Composite(decimalColumn.LogicalType()), false);
+ }
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ColumnFromYson(R"(
+ {
+ name=x;
+ type_v3={
+ type_name=optional;
+ item={
+ type_name=optional;
+ item=utf8;
+ }
+ };
+ required=%true;
+ }
+ )"),
+ R"("type_v3" does not match "required")"
+ );
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ColumnFromYson(R"(
+ {
+ name=x;
+ type_v3={
+ type_name=optional;
+ item={
+ type_name=optional;
+ item=utf8
+ }
+ };
+ type=utf8;
+ }
+ )"),
+ R"("type_v3" does not match "type")"
+ );
+}
+
+TEST(TTableSchemaTest, MaxInlineHunkSizeSerialization)
+{
+ {
+ auto column = ColumnFromYson(R"(
+ {
+ name=x;
+ type=string;
+ }
+ )");
+ auto serializedColumn = ConvertToAttributes(column);
+ EXPECT_FALSE(serializedColumn->FindYson("max_inline_hunk_size").operator bool());
+ }
+
+ {
+ auto column = ColumnFromYson(R"(
+ {
+ name=x;
+ type=string;
+ max_inline_hunk_size=100
+ }
+ )");
+ auto serializedColumn = ConvertToAttributes(column);
+ EXPECT_EQ(100, serializedColumn->Get<i64>("max_inline_hunk_size"));
+ }
+}
+
+TEST(TTableSchemaTest, MaxInlineHunkSizeDeserialization)
+{
+ {
+ auto column = ColumnFromYson(R"(
+ {
+ name=x;
+ type=string;
+ }
+ )");
+ EXPECT_FALSE(column.MaxInlineHunkSize().has_value());
+ }
+
+ {
+ auto column = ColumnFromYson(R"(
+ {
+ name=x;
+ type=string;
+ max_inline_hunk_size=100
+ }
+ )");
+ EXPECT_EQ(column.MaxInlineHunkSize(), 100);
+ }
+}
+
+TEST(TTableSchemaTest, ColumnSchemaValidation)
+{
+ auto expectBad = [] (const auto& schema) {
+ EXPECT_THROW(ValidateColumnSchema(schema, true, true), std::exception);
+ };
+
+ // Empty names are not ok.
+ expectBad(TColumnSchema("", EValueType::String));
+
+ // Names starting from SystemColumnNamePrefix are not ok.
+ expectBad(TColumnSchema(SystemColumnNamePrefix + "Name", EValueType::String));
+
+ // Names longer than MaxColumnNameLength are not ok.
+ expectBad(TColumnSchema(TString(MaxColumnNameLength + 1, 'z'), EValueType::String));
+
+ // Empty lock names are not ok.
+ expectBad(
+ TColumnSchema("Name", EValueType::String)
+ .SetLock(TString("")));
+
+ // Locks on key columns are not ok.
+ expectBad(
+ TColumnSchema("Name", EValueType::String)
+ .SetSortOrder(ESortOrder::Ascending)
+ .SetLock(TString("LockName")));
+
+ // Locks longer than MaxColumnLockLength are not ok.
+ expectBad(
+ TColumnSchema("Name", EValueType::String)
+ .SetLock(TString(MaxColumnLockLength + 1, 'z')));
+
+ // Column type should be valid according to the ValidateSchemaValueType function.
+ // Non-key columns can't be computed.
+ expectBad(
+ TColumnSchema("Name", EValueType::String)
+ .SetExpression(TString("SomeExpression")));
+
+ // Key columns can't be aggregated.
+ expectBad(
+ TColumnSchema("Name", EValueType::String)
+ .SetSortOrder(ESortOrder::Ascending)
+ .SetAggregate(TString("sum")));
+
+ ValidateColumnSchema(TColumnSchema("Name", EValueType::String));
+ ValidateColumnSchema(TColumnSchema("Name", EValueType::Any));
+ ValidateColumnSchema(
+ TColumnSchema(TString(256, 'z'), EValueType::String)
+ .SetLock(TString(256, 'z')));
+ ValidateColumnSchema(
+ TColumnSchema("Name", EValueType::String)
+ .SetSortOrder(ESortOrder::Ascending)
+ .SetExpression(TString("SomeExpression")));
+ ValidateColumnSchema(
+ TColumnSchema("Name", EValueType::String)
+ .SetAggregate(TString("sum")));
+
+ // Struct field validation
+ expectBad(
+ TColumnSchema("Column", StructLogicalType({
+ {"", SimpleLogicalType(ESimpleLogicalValueType::Int8)}
+ })));
+ expectBad(
+ TColumnSchema("Column", StructLogicalType({
+ {TString(257, 'a'), SimpleLogicalType(ESimpleLogicalValueType::Int8)}
+ })));
+
+ expectBad(
+ TColumnSchema("Column", StructLogicalType({
+ {"\255", SimpleLogicalType(ESimpleLogicalValueType::Int8)}
+ })));
+
+ ValidateColumnSchema(
+ TColumnSchema("Column", ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int8)), ESortOrder::Ascending)
+ );
+
+ expectBad(
+ TColumnSchema("Column", ListLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))), ESortOrder::Ascending)
+ );
+
+ expectBad(
+ TColumnSchema("Column", EValueType::String)
+ .SetMaxInlineHunkSize(0)
+ );
+
+ expectBad(
+ TColumnSchema("Column", EValueType::String)
+ .SetMaxInlineHunkSize(-1)
+ );
+
+ expectBad(
+ TColumnSchema("Column", EValueType::Int64)
+ .SetMaxInlineHunkSize(100)
+ );
+
+ expectBad(
+ TColumnSchema("Column", EValueType::String, ESortOrder::Ascending)
+ .SetMaxInlineHunkSize(100)
+ );
+
+ ValidateColumnSchema(
+ TColumnSchema("Column", EValueType::String)
+ .SetMaxInlineHunkSize(100)
+ );
+
+ ValidateColumnSchema(
+ TColumnSchema("Column", EValueType::Any)
+ .SetMaxInlineHunkSize(100)
+ );
+
+ expectBad(
+ TColumnSchema("Column", StructLogicalType({
+ {"foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"bar", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }), ESortOrder::Ascending)
+ );
+}
+
+TEST(TTableSchemaTest, ValidateTableSchemaTest)
+{
+ auto expectBad = [] (const auto& schemaString) {
+ TTableSchema schema;
+ Deserialize(schema, ConvertToNode(TYsonString(TStringBuf(schemaString))));
+
+ EXPECT_THROW(ValidateTableSchema(schema, true), std::exception);
+ };
+ expectBad("[{name=x;type=int64;sort_order=ascending;expression=z}; {name=y;type=uint64;sort_order=ascending}; {name=a;type=int64}]");
+ expectBad("[{name=x;type=int64;sort_order=ascending;expression=y}; {name=y;type=uint64;sort_order=ascending}; {name=a;type=int64}]");
+ expectBad("[{name=x;type=int64;sort_order=ascending;expression=x}; {name=y;type=uint64;sort_order=ascending}; {name=a;type=int64}]");
+ expectBad("[{name=x;type=int64;sort_order=ascending;expression=\"uint64(y)\"}; {name=y;type=uint64;sort_order=ascending}; {name=a;type=int64}]");
+}
+
+TEST(TTableSchemaTest, ColumnSchemaProtobufBackwardCompatibility)
+{
+ NProto::TColumnSchema columnSchemaProto;
+ columnSchemaProto.set_name("foo");
+ columnSchemaProto.set_type(static_cast<int>(EValueType::Uint64));
+
+ TColumnSchema columnSchema;
+ FromProto(&columnSchema, columnSchemaProto);
+
+ EXPECT_EQ(*columnSchema.LogicalType(), *OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint64)));
+ EXPECT_EQ(columnSchema.GetWireType(), EValueType::Uint64);
+ EXPECT_EQ(columnSchema.Name(), "foo");
+ EXPECT_EQ(columnSchema.StableName().Get(), "foo");
+
+ columnSchemaProto.set_simple_logical_type(static_cast<int>(ESimpleLogicalValueType::Uint32));
+ columnSchemaProto.set_name("foo");
+ columnSchemaProto.set_stable_name("foo_stable");
+ FromProto(&columnSchema, columnSchemaProto);
+
+ EXPECT_EQ(*columnSchema.LogicalType(), *OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint32)));
+ EXPECT_EQ(columnSchema.GetWireType(), EValueType::Uint64);
+ EXPECT_EQ(columnSchema.Name(), "foo");
+ EXPECT_EQ(columnSchema.StableName().Get(), "foo_stable");
+}
+
+TEST(TTableSchemaTest, EqualIgnoringRequiredness)
+{
+ auto schema1 = TTableSchema({
+ TColumnSchema("foo", SimpleLogicalType(ESimpleLogicalValueType::Int64)),
+ });
+
+ auto schema2 = TTableSchema({
+ TColumnSchema("foo", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+ });
+
+ auto schema3 = TTableSchema({
+ TColumnSchema("foo", SimpleLogicalType(ESimpleLogicalValueType::String)),
+ });
+
+ EXPECT_TRUE(schema1 != schema2);
+ EXPECT_TRUE(IsEqualIgnoringRequiredness(schema1, schema2));
+ EXPECT_FALSE(IsEqualIgnoringRequiredness(schema1, schema3));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TLockMaskTest, Simple)
+{
+ TLockMask mask;
+ mask.Set(17, ELockType::SharedStrong);
+ EXPECT_EQ(mask.Get(17), ELockType::SharedStrong);
+}
+
+TEST(TLockMaskTest, RandomChanges)
+{
+ constexpr int IterationCount = 1'000'000;
+ constexpr int MaxLockIndex = 256;
+ constexpr int MaxLockValue = static_cast<int>(TEnumTraits<ELockType>::GetMaxValue());
+
+ std::mt19937 rng(42);
+
+ TLockMask mask;
+ std::vector<ELockType> locks(MaxLockIndex, ELockType::None);
+ for (int iteration = 0; iteration < IterationCount; ++iteration) {
+ if (rng() % 2 == 0) {
+ int index = rng() % MaxLockIndex;
+ EXPECT_EQ(locks[index], mask.Get(index));
+ } else {
+ int index = rng() % MaxLockIndex;
+ auto value = CheckedEnumCast<ELockType>(rng() % MaxLockValue);
+ mask.Set(index, value);
+ locks[index] = value;
+ }
+ }
+}
+
+TEST(TLockMaskTest, ConvertToLegacy)
+{
+ TLockMask mask;
+ mask.Set(2, ELockType::Exclusive);
+ mask.Set(18, ELockType::SharedStrong);
+
+ EXPECT_FALSE(mask.HasNewLocks());
+ auto legacyLocks = mask.ToLegacyMask();
+ for (int index = 0; index < TLegacyLockMask::MaxCount; ++index) {
+ auto lock = legacyLocks.Get(index);
+ if (index == 2) {
+ EXPECT_EQ(lock, ELockType::Exclusive);
+ } else if (index == 18) {
+ EXPECT_EQ(lock, ELockType::SharedStrong);
+ } else {
+ EXPECT_EQ(lock, ELockType::None);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/schemaful_dsv_parser_ut.cpp b/yt/yt/client/unittests/schemaful_dsv_parser_ut.cpp
new file mode 100644
index 0000000000..000ae5f635
--- /dev/null
+++ b/yt/yt/client/unittests/schemaful_dsv_parser_ut.cpp
@@ -0,0 +1,259 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/test_framework/yson_consumer_mock.h>
+
+#include <yt/yt/client/formats/schemaful_dsv_parser.h>
+
+#include <yt/yt/core/yson/null_consumer.h>
+
+namespace NYT::NFormats {
+namespace {
+
+using namespace NYson;
+
+using ::testing::InSequence;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSchemafulDsvParserTest, Simple)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("5"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar("6"));
+ EXPECT_CALL(Mock, OnEndMap());
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("100"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar("max\tignat"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "5\t6\n"
+ "100\tmax\\tignat\n";
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = std::vector<TString>();
+ config->Columns->push_back("a");
+ config->Columns->push_back("b");
+
+ ParseSchemafulDsv(input, &Mock, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSchemafulDsvParserTest, TableIndex)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginAttributes());
+ EXPECT_CALL(Mock, OnKeyedItem("table_index"));
+ EXPECT_CALL(Mock, OnInt64Scalar(1));
+ EXPECT_CALL(Mock, OnEndAttributes());
+ EXPECT_CALL(Mock, OnEntity());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("x"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginAttributes());
+ EXPECT_CALL(Mock, OnKeyedItem("table_index"));
+ EXPECT_CALL(Mock, OnInt64Scalar(0));
+ EXPECT_CALL(Mock, OnEndAttributes());
+ EXPECT_CALL(Mock, OnEntity());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("y"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("z"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "1\tx\n"
+ "0\ty\n"
+ "0\tz\n";
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = std::vector<TString>();
+ config->Columns->push_back("a");
+ config->EnableTableIndex = true;
+
+ ParseSchemafulDsv(input, &Mock, config);
+}
+
+TEST(TSchemafulDsvParserTest, TooManyRows)
+{
+ TString input = "5\t6\n";
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = {"a"};
+
+ EXPECT_THROW({ ParseSchemafulDsv(input, GetNullYsonConsumer(), config); }, std::exception);
+}
+
+TEST(TSchemafulDsvParserTest, SpecialSymbols)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ auto value = TString("6\0", 2);
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("5\r"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar(value));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input("5\r\t6\0\n", 6);
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = std::vector<TString>();
+ config->Columns->push_back("a");
+ config->Columns->push_back("b");
+
+ ParseSchemafulDsv(input, &Mock, config);
+}
+
+TEST(TSchemafulDsvParserTest, EnabledEscaping)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ auto value = TString("6\0", 2);
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("5\r\r"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar(value));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input("5\r\\r\t6\0\n", 8);
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = std::vector<TString>();
+ config->Columns->push_back("a");
+ config->Columns->push_back("b");
+ config->EnableEscaping = true;
+
+ ParseSchemafulDsv(input, &Mock, config);
+}
+
+TEST(TSchemafulDsvParserTest, DisabledEscaping)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ auto value = TString("6\0", 2);
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("5\r\\r"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar(value));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input("5\r\\r\t6\0\n", 8);
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = std::vector<TString>();
+ config->Columns->push_back("a");
+ config->Columns->push_back("b");
+ config->EnableEscaping = false;
+
+ ParseSchemafulDsv(input, &Mock, config);
+}
+
+TEST(TSchemafulDsvParserTest, ColumnsNamesHeader)
+{
+ TString input("a\tb\n1\t2\n");
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = std::vector<TString>();
+ config->Columns->push_back("a");
+ config->Columns->push_back("b");
+ config->EnableColumnNamesHeader = true;
+
+ EXPECT_THROW(ParseSchemafulDsv(input, GetNullYsonConsumer(), config), std::exception);
+}
+
+TEST(TSchemafulDsvParserTest, MissingValueModePrintSentinel)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ TString input = "x\t\tz\n";
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("x"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnKeyedItem("c"));
+ EXPECT_CALL(Mock, OnStringScalar("z"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ auto config = New<TSchemafulDsvFormatConfig>();
+ config->Columns = {"a", "b", "c"};
+ // By default missing_value_mode = fail and no sentinel values are used,
+ // i. e. there is no way to represent YSON entity with this format.
+
+ ParseSchemafulDsv(input, &Mock, config);
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("x"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnEntity());
+ EXPECT_CALL(Mock, OnKeyedItem("c"));
+ EXPECT_CALL(Mock, OnStringScalar("z"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ config->MissingValueMode = EMissingSchemafulDsvValueMode::PrintSentinel;
+ // By default missing_value_sentinel = "".
+
+ ParseSchemafulDsv(input, &Mock, config);
+
+ input = "null\tNULL\t\n";
+
+ config->MissingValueSentinel = "NULL";
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("null"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnEntity());
+ EXPECT_CALL(Mock, OnKeyedItem("c"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ ParseSchemafulDsv(input, &Mock, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/schemaful_dsv_writer_ut.cpp b/yt/yt/client/unittests/schemaful_dsv_writer_ut.cpp
new file mode 100644
index 0000000000..90a3af0dcb
--- /dev/null
+++ b/yt/yt/client/unittests/schemaful_dsv_writer_ut.cpp
@@ -0,0 +1,344 @@
+#include <yt/yt/core/test_framework/framework.h>
+#include "format_writer_ut.h"
+
+#include <yt/yt/client/formats/schemaful_dsv_writer.h>
+#include <yt/yt/client/formats/format.h>
+
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <limits>
+
+namespace NYT::NFormats {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NConcurrency;
+using namespace NTableClient;
+
+class TSchemalessWriterForSchemafulDsvTest
+ : public ::testing::Test
+{
+protected:
+ TNameTablePtr NameTable_;
+ int KeyAId_;
+ int KeyBId_;
+ int KeyCId_;
+ int KeyDId_;
+ int TableIndexId_;
+ int RangeIndexId_;
+ int RowIndexId_;
+ TSchemafulDsvFormatConfigPtr Config_;
+
+ ISchemalessFormatWriterPtr Writer_;
+
+ TStringStream OutputStream_;
+
+ TSchemalessWriterForSchemafulDsvTest() {
+ NameTable_ = New<TNameTable>();
+ KeyAId_ = NameTable_->RegisterName("column_a");
+ KeyBId_ = NameTable_->RegisterName("column_b");
+ KeyCId_ = NameTable_->RegisterName("column_c");
+ KeyDId_ = NameTable_->RegisterName("column_d");
+ TableIndexId_ = NameTable_->RegisterName(TableIndexColumnName);
+ RowIndexId_ = NameTable_->RegisterName(RowIndexColumnName);
+ RangeIndexId_ = NameTable_->RegisterName(RangeIndexColumnName);
+
+ Config_ = New<TSchemafulDsvFormatConfig>();
+ }
+
+ void CreateStandardWriter() {
+ auto controlAttributesConfig = New<TControlAttributesConfig>();
+ controlAttributesConfig->EnableTableIndex = Config_->EnableTableIndex;
+ Writer_ = CreateSchemalessWriterForSchemafulDsv(
+ Config_,
+ NameTable_,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&OutputStream_)),
+ false, // enableContextSaving
+ controlAttributesConfig,
+ 0 /* keyColumnCount */);
+ }
+};
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, Simple)
+{
+ Config_->Columns = {"column_b", "column_c", "column_a"};
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("value_a", KeyAId_));
+ row1.AddValue(MakeUnversionedInt64Value(-42, KeyBId_));
+ row1.AddValue(MakeUnversionedBooleanValue(true, KeyCId_));
+ row1.AddValue(MakeUnversionedStringValue("garbage", KeyDId_));
+
+ // Ignore system columns.
+ row1.AddValue(MakeUnversionedInt64Value(2, TableIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(42, RowIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(1, RangeIndexId_));
+
+ TUnversionedRowBuilder row2;
+ // The order is reversed.
+ row2.AddValue(MakeUnversionedStringValue("value_c", KeyCId_));
+ row2.AddValue(MakeUnversionedBooleanValue(false, KeyBId_));
+ row2.AddValue(MakeUnversionedInt64Value(23, KeyAId_));
+
+ std::vector<TUnversionedRow> rows = {row1.GetRow(), row2.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput =
+ "-42\ttrue\tvalue_a\n"
+ "false\tvalue_c\t23\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+// This test shows the actual behavior of writer. It is OK to change it in the future. :)
+TEST_F(TSchemalessWriterForSchemafulDsvTest, TrickyDoubleRepresentations)
+{
+ Config_->Columns = {"column_a", "column_b", "column_c", "column_d"};
+ CreateStandardWriter();
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedDoubleValue(1.234567890123456, KeyAId_));
+ row1.AddValue(MakeUnversionedDoubleValue(42, KeyBId_));
+ row1.AddValue(MakeUnversionedDoubleValue(1e300, KeyCId_));
+ row1.AddValue(MakeUnversionedDoubleValue(-1e-300, KeyDId_));
+
+ std::vector<TUnversionedRow> rows = {row1.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+ TString expectedOutput = "1.234567890123456\t42.\t1e+300\t-1e-300\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, IntegralTypeRepresentations)
+{
+ Config_->Columns = {"column_a", "column_b", "column_c", "column_d"};
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedInt64Value(0LL, KeyAId_));
+ row1.AddValue(MakeUnversionedInt64Value(-1LL, KeyBId_));
+ row1.AddValue(MakeUnversionedInt64Value(1LL, KeyCId_));
+ row1.AddValue(MakeUnversionedInt64Value(99LL, KeyDId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedInt64Value(123LL, KeyAId_));
+ row2.AddValue(MakeUnversionedInt64Value(-123LL, KeyBId_));
+ row2.AddValue(MakeUnversionedInt64Value(1234LL, KeyCId_));
+ row2.AddValue(MakeUnversionedInt64Value(-1234LL, KeyDId_));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedUint64Value(0ULL, KeyAId_));
+ row3.AddValue(MakeUnversionedUint64Value(98ULL, KeyBId_));
+ row3.AddValue(MakeUnversionedUint64Value(987ULL, KeyCId_));
+ row3.AddValue(MakeUnversionedUint64Value(9876ULL, KeyDId_));
+
+ TUnversionedRowBuilder row4;
+ row4.AddValue(MakeUnversionedInt64Value(std::numeric_limits<i64>::max(), KeyAId_));
+ row4.AddValue(MakeUnversionedInt64Value(std::numeric_limits<i64>::min(), KeyBId_));
+ row4.AddValue(MakeUnversionedInt64Value(std::numeric_limits<i64>::min() + 1LL, KeyCId_));
+ row4.AddValue(MakeUnversionedUint64Value(std::numeric_limits<ui64>::max(), KeyDId_));
+
+ std::vector<TUnversionedRow> rows =
+ {row1.GetRow(), row2.GetRow(), row3.GetRow(), row4.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+ TString expectedOutput =
+ "0\t-1\t1\t99\n"
+ "123\t-123\t1234\t-1234\n"
+ "0\t98\t987\t9876\n"
+ "9223372036854775807\t-9223372036854775808\t-9223372036854775807\t18446744073709551615\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, EmptyColumnList)
+{
+ Config_->Columns = std::vector<TString>();
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedInt64Value(0LL, KeyAId_));
+
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+ TString expectedOutput = "\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, MissingValueMode)
+{
+ Config_->Columns = {"column_a", "column_b", "column_c"};
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("Value1A", KeyAId_));
+ row1.AddValue(MakeUnversionedStringValue("Value1B", KeyBId_));
+ row1.AddValue(MakeUnversionedStringValue("Value1C", KeyCId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("Value2A", KeyAId_));
+ row2.AddValue(MakeUnversionedStringValue("Value2C", KeyCId_));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("Value3A", KeyAId_));
+ row3.AddValue(MakeUnversionedStringValue("Value3B", KeyBId_));
+ row3.AddValue(MakeUnversionedStringValue("Value3C", KeyCId_));
+
+ std::vector<TUnversionedRow> rows =
+ {row1.GetRow(), row2.GetRow(), row3.GetRow()};
+
+ {
+ Config_->MissingValueMode = EMissingSchemafulDsvValueMode::SkipRow;
+ CreateStandardWriter();
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+ TString expectedOutput =
+ "Value1A\tValue1B\tValue1C\n"
+ "Value3A\tValue3B\tValue3C\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+ OutputStream_.Clear();
+ }
+
+ {
+ Config_->MissingValueMode = EMissingSchemafulDsvValueMode::Fail;
+ CreateStandardWriter();
+ EXPECT_EQ(false, Writer_->Write(rows));
+ EXPECT_THROW(Writer_->Close()
+ .Get()
+ .ThrowOnError(), std::exception);
+ OutputStream_.Clear();
+ }
+
+ {
+ Config_->MissingValueMode = EMissingSchemafulDsvValueMode::PrintSentinel;
+ Config_->MissingValueSentinel = "~";
+ CreateStandardWriter();
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+ TString expectedOutput =
+ "Value1A\tValue1B\tValue1C\n"
+ "Value2A\t~\tValue2C\n"
+ "Value3A\tValue3B\tValue3C\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+ OutputStream_.Clear();
+ }
+}
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, NameTableExpansion)
+{
+ Config_->Columns = {"Column1"};
+ Config_->MissingValueMode = {EMissingSchemafulDsvValueMode::PrintSentinel};
+ CreateStandardWriter();
+ TestNameTableExpansion(Writer_, NameTable_);
+}
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, TableIndex)
+{
+ Config_->Columns = {"column_a", "column_b", "column_c", "column_d"};
+ Config_->EnableTableIndex = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row0;
+ row0.AddValue(MakeUnversionedInt64Value(0LL, KeyAId_));
+ row0.AddValue(MakeUnversionedInt64Value(1LL, KeyBId_));
+ row0.AddValue(MakeUnversionedInt64Value(2LL, KeyCId_));
+ row0.AddValue(MakeUnversionedInt64Value(3LL, KeyDId_));
+
+ // It's necessary to specify a column corresponding to the table index
+ // when enable_table_index = true.
+ EXPECT_EQ(false, Writer_->Write(std::vector<TUnversionedRow>{row0.GetRow()}));
+
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedInt64Value(42LL, TableIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(0LL, KeyAId_));
+ row1.AddValue(MakeUnversionedInt64Value(1LL, KeyBId_));
+ row1.AddValue(MakeUnversionedInt64Value(2LL, KeyCId_));
+ row1.AddValue(MakeUnversionedInt64Value(3LL, KeyDId_));
+
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedInt64Value(42LL, TableIndexId_));
+ row2.AddValue(MakeUnversionedInt64Value(4LL, KeyAId_));
+ row2.AddValue(MakeUnversionedInt64Value(5LL, KeyBId_));
+ row2.AddValue(MakeUnversionedInt64Value(6LL, KeyCId_));
+ row2.AddValue(MakeUnversionedInt64Value(7LL, KeyDId_));
+
+ EXPECT_EQ(true, Writer_->Write(std::vector<TUnversionedRow>{row1.GetRow(), row2.GetRow()}));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedInt64Value(23LL, TableIndexId_));
+ row3.AddValue(MakeUnversionedUint64Value(8LL, KeyAId_));
+ row3.AddValue(MakeUnversionedUint64Value(9LL, KeyBId_));
+ row3.AddValue(MakeUnversionedUint64Value(10LL, KeyCId_));
+ row3.AddValue(MakeUnversionedUint64Value(11ULL, KeyDId_));
+
+ EXPECT_EQ(true, Writer_->Write(std::vector<TUnversionedRow>{row3.GetRow()}));
+
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+ TString expectedOutput =
+ "42\t0\t1\t2\t3\n"
+ "42\t4\t5\t6\t7\n"
+ "23\t8\t9\t10\t11\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, ValidateDuplicateNames)
+{
+ Config_->Columns = {"column_a", "column_b", "column_a"};
+ Config_->EnableTableIndex = true;
+ EXPECT_THROW(CreateStandardWriter(), TErrorException);
+}
+
+TEST_F(TSchemalessWriterForSchemafulDsvTest, ColumnsHeader)
+{
+ Config_->Columns = {"column_b", "column_c", "column_a"};
+ Config_->EnableColumnNamesHeader = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("value_a", KeyAId_));
+ row1.AddValue(MakeUnversionedInt64Value(-42, KeyBId_));
+ row1.AddValue(MakeUnversionedBooleanValue(true, KeyCId_));
+ std::vector<TUnversionedRow> rows = {row1.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput =
+ "column_b\tcolumn_c\tcolumn_a\n"
+ "-42\ttrue\tvalue_a\n";
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/skiff_format_ut.cpp b/yt/yt/client/unittests/skiff_format_ut.cpp
new file mode 100644
index 0000000000..4878b7f673
--- /dev/null
+++ b/yt/yt/client/unittests/skiff_format_ut.cpp
@@ -0,0 +1,3006 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include "logical_type_shortcuts.h"
+#include "value_examples.h"
+#include "row_helpers.h"
+#include "yson_helpers.h"
+
+#include <yt/yt/client/formats/config.h>
+#include <yt/yt/client/formats/parser.h>
+#include <yt/yt/client/formats/skiff_parser.h>
+#include <yt/yt/client/formats/skiff_writer.h>
+#include <yt/yt/client/formats/format.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/validate_logical_type.h>
+
+#include <yt/yt/library/named_value/named_value.h>
+#include <yt/yt/library/skiff_ext/schema_match.h>
+
+#include <yt/yt/core/yson/string.h>
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/tree_visitor.h>
+
+#include <library/cpp/skiff/skiff.h>
+#include <library/cpp/skiff/skiff_schema.h>
+
+#include <util/stream/null.h>
+#include <util/string/hex.h>
+
+namespace NYT {
+
+namespace {
+
+using namespace NFormats;
+using namespace NNamedValue;
+using namespace NSkiff;
+using namespace NSkiffExt;
+using namespace NTableClient;
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ConvertToSkiffSchemaShortDebugString(INodePtr node)
+{
+ auto skiffFormatConfig = ConvertTo<TSkiffFormatConfigPtr>(std::move(node));
+ auto skiffSchemas = ParseSkiffSchemas(skiffFormatConfig->SkiffSchemaRegistry, skiffFormatConfig->TableSkiffSchemas);
+ TStringStream result;
+ result << '{';
+ for (const auto& schema : skiffSchemas) {
+ result << GetShortDebugString(schema);
+ result << ',';
+ }
+ result << '}';
+ return result.Str();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ConvertToYsonTextStringStable(const INodePtr& node)
+{
+ TStringStream out;
+ TYsonWriter writer(&out, EYsonFormat::Text);
+ VisitTree(node, &writer, true, TAttributeFilter());
+ writer.Flush();
+ return out.Str();
+}
+
+TTableSchemaPtr CreateSingleValueTableSchema(const TLogicalTypePtr& logicalType)
+{
+ std::vector<TColumnSchema> columns;
+ if (logicalType) {
+ columns.emplace_back("value", logicalType);
+
+ }
+ auto strict = static_cast<bool>(logicalType);
+ return New<TTableSchema>(columns, strict);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSkiffSchemaParse, TestAllowedTypes)
+{
+ EXPECT_EQ(
+ "{uint64,}",
+
+ ConvertToSkiffSchemaShortDebugString(
+ BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("table_skiff_schemas")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("wire_type")
+ .Value("uint64")
+ .EndMap()
+ .EndList()
+ .EndMap()));
+
+ EXPECT_EQ(
+ "{string32,}",
+
+ ConvertToSkiffSchemaShortDebugString(
+ BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("table_skiff_schemas")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("wire_type")
+ .Value("string32")
+ .EndMap()
+ .EndList()
+ .EndMap()));
+
+ EXPECT_EQ(
+ "{variant8<string32;int64;>,}",
+
+ ConvertToSkiffSchemaShortDebugString(
+ BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("table_skiff_schemas")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("wire_type")
+ .Value("variant8")
+ .Item("children")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("wire_type")
+ .Value("string32")
+ .EndMap()
+ .Item()
+ .BeginMap()
+ .Item("wire_type")
+ .Value("int64")
+ .EndMap()
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()));
+
+ EXPECT_EQ(
+ "{variant8<int64;string32;>,}",
+
+ ConvertToSkiffSchemaShortDebugString(
+ BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("skiff_schema_registry")
+ .BeginMap()
+ .Item("item1")
+ .BeginMap()
+ .Item("wire_type")
+ .Value("int64")
+ .EndMap()
+ .Item("item2")
+ .BeginMap()
+ .Item("wire_type")
+ .Value("string32")
+ .EndMap()
+ .EndMap()
+ .Item("table_skiff_schemas")
+ .BeginList()
+ .Item()
+ .BeginMap()
+ .Item("wire_type")
+ .Value("variant8")
+ .Item("children")
+ .BeginList()
+ .Item().Value("$item1")
+ .Item().Value("$item2")
+ .EndList()
+ .EndMap()
+ .EndList()
+ .EndMap()));
+}
+
+TEST(TSkiffSchemaParse, TestRecursiveTypesAreDisallowed)
+{
+ try {
+ ConvertToSkiffSchemaShortDebugString(
+ BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("skiff_schema_registry")
+ .BeginMap()
+ .Item("item1")
+ .BeginMap()
+ .Item("wire_type")
+ .Value("variant8")
+ .Item("children")
+ .BeginList()
+ .Item().Value("$item1")
+ .EndList()
+ .EndMap()
+ .EndMap()
+ .Item("table_skiff_schemas")
+ .BeginList()
+ .Item().Value("$item1")
+ .EndList()
+ .EndMap());
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("recursive types are forbidden"));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSkiffSchemaDescription, TestDescriptionDerivation)
+{
+ auto schema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Foo"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Uint64),
+ })->SetName("Bar"),
+ });
+
+ auto tableDescriptionList = CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ EXPECT_EQ(std::ssize(tableDescriptionList), 1);
+ EXPECT_EQ(tableDescriptionList[0].HasOtherColumns, false);
+ EXPECT_EQ(tableDescriptionList[0].SparseFieldDescriptionList.empty(), true);
+
+ auto denseFieldDescriptionList = tableDescriptionList[0].DenseFieldDescriptionList;
+ EXPECT_EQ(std::ssize(denseFieldDescriptionList), 2);
+
+ EXPECT_EQ(denseFieldDescriptionList[0].Name(), "Foo");
+ EXPECT_EQ(denseFieldDescriptionList[0].ValidatedSimplify(), EWireType::Uint64);
+}
+
+TEST(TSkiffSchemaDescription, TestKeySwitchColumn)
+{
+ {
+ auto schema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Foo"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("$key_switch"),
+ });
+
+ auto tableDescriptionList = CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ EXPECT_EQ(std::ssize(tableDescriptionList), 1);
+ EXPECT_EQ(tableDescriptionList[0].KeySwitchFieldIndex, std::optional<size_t>(1));
+ }
+ {
+ auto schema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("$key_switch"),
+ });
+
+ try {
+ auto tableDescriptionList = CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Column \"$key_switch\" has unexpected Skiff type"));
+ }
+ }
+}
+
+TEST(TSkiffSchemaDescription, TestDisallowEmptyNames)
+{
+ auto schema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Foo"),
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName(""),
+ });
+
+ try {
+ CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("must have a name"));
+ }
+}
+
+TEST(TSkiffSchemaDescription, TestWrongRowType)
+{
+ auto schema = CreateRepeatedVariant16Schema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Foo"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Bar"),
+ });
+
+ try {
+ CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Invalid wire type for table row"));
+ }
+}
+
+TEST(TSkiffSchemaDescription, TestOtherColumnsOk)
+{
+ auto schema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Foo"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Bar"),
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("$other_columns"),
+ });
+
+ auto tableDescriptionList = CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ ASSERT_EQ(std::ssize(tableDescriptionList), 1);
+ ASSERT_EQ(tableDescriptionList[0].HasOtherColumns, true);
+}
+
+TEST(TSkiffSchemaDescription, TestOtherColumnsWrongType)
+{
+ auto schema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Foo"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Bar"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("$other_columns"),
+ });
+
+ try {
+ CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Invalid wire type for column \"$other_columns\""));
+ }
+}
+
+TEST(TSkiffSchemaDescription, TestOtherColumnsWrongPlace)
+{
+ auto schema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Foo"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("$other_columns"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("Bar"),
+ });
+
+ try {
+ CreateTableDescriptionList({schema}, RangeIndexColumnName, RowIndexColumnName);
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Invalid placement of special column \"$other_columns\""));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ISchemalessFormatWriterPtr CreateSkiffWriter(
+ std::shared_ptr<TSkiffSchema> skiffSchema,
+ TNameTablePtr nameTable,
+ IOutputStream* outputStream,
+ const std::vector<TTableSchemaPtr>& tableSchemaList,
+ int keyColumnCount = 0,
+ bool enableEndOfStream = false)
+{
+ auto controlAttributesConfig = New<TControlAttributesConfig>();
+ controlAttributesConfig->EnableKeySwitch = (keyColumnCount > 0);
+ controlAttributesConfig->EnableEndOfStream = enableEndOfStream;
+ return CreateWriterForSkiff(
+ {std::move(skiffSchema)},
+ std::move(nameTable),
+ tableSchemaList,
+ NConcurrency::CreateAsyncAdapter(outputStream),
+ false,
+ controlAttributesConfig,
+ keyColumnCount);
+}
+
+TString TableToSkiff(
+ const TLogicalTypePtr& logicalType,
+ const std::shared_ptr<TSkiffSchema>& typeSchema,
+ const TNamedValue::TValue& value)
+{
+ auto schema = CreateSingleValueTableSchema(logicalType);
+ auto skiffSchema = CreateTupleSchema({
+ typeSchema->SetName("value")
+ });
+
+ auto nameTable = New<TNameTable>();
+
+ TStringStream resultStream;
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {schema});
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", value}
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ auto result = resultStream.Str();
+ if (!TStringBuf(result).StartsWith(TString(2, '\0'))) {
+ THROW_ERROR_EXCEPTION("Expected skiff value to start with \\x00\\x00, but prefix is %Qv",
+ EscapeC(result.substr(0, 2)));
+ }
+
+ return result.substr(2);
+}
+
+TNamedValue::TValue SkiffToTable(
+ const TLogicalTypePtr& logicalType,
+ const std::shared_ptr<TSkiffSchema>& typeSchema,
+ const TString& skiffValue)
+{
+ auto schema = CreateSingleValueTableSchema(logicalType);
+ auto skiffSchema = CreateTupleSchema({
+ typeSchema->SetName("value")
+ });
+ auto nameTable = New<TNameTable>();
+
+ TCollectingValueConsumer rowCollector(schema);
+ auto parser = CreateParserForSkiff(skiffSchema, &rowCollector);
+ parser->Read(TString(2, 0));
+ parser->Read(skiffValue);
+ parser->Finish();
+
+ if (rowCollector.Size() != 1) {
+ THROW_ERROR_EXCEPTION("Expected 1 row collected, actual %v",
+ rowCollector.Size());
+ }
+ auto value = rowCollector.GetRowValue(0, "value");
+ return TNamedValue::ExtractValue(value);
+}
+
+#define CHECK_BIDIRECTIONAL_CONVERSION(logicalTypeArg, skiffSchemaArg, tableValueArg, hexSkiffArg) \
+ do { \
+ try { \
+ TLogicalTypePtr logicalType = (logicalTypeArg); \
+ std::shared_ptr<TSkiffSchema> skiffSchema = (skiffSchemaArg); \
+ TNamedValue::TValue tableValue = (tableValueArg); \
+ TString hexSkiff = (hexSkiffArg); \
+ auto nameTable = New<TNameTable>(); \
+ auto actualSkiff = TableToSkiff(logicalType, skiffSchema, tableValue); \
+ EXPECT_EQ(HexEncode(actualSkiff), hexSkiff); \
+ auto actualValue = SkiffToTable(logicalType, skiffSchema, HexDecode(hexSkiff)); \
+ EXPECT_EQ(actualValue, tableValue); \
+ } catch (const std::exception& ex) { \
+ ADD_FAILURE() << "unexpected exception: " << ex.what(); \
+ } \
+ } while (0)
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TestAllWireTypes(bool useSchema)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("int64"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("uint64"),
+ CreateSimpleTypeSchema(EWireType::Double)->SetName("double_1"),
+ CreateSimpleTypeSchema(EWireType::Double)->SetName("double_2"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("boolean"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("string32"),
+ CreateSimpleTypeSchema(EWireType::Nothing)->SetName("null"),
+
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName("opt_int64"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Uint64),
+ })->SetName("opt_uint64"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Double),
+ })->SetName("opt_double_1"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Double),
+ })->SetName("opt_double_2"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ })->SetName("opt_boolean"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::String32),
+ })->SetName("opt_string32"),
+ });
+ std::vector<TTableSchemaPtr> tableSchemas;
+ if (useSchema) {
+ tableSchemas.push_back(New<TTableSchema>(std::vector{
+ TColumnSchema("int64", EValueType::Int64),
+ TColumnSchema("uint64", EValueType::Uint64),
+ TColumnSchema("double_1", EValueType::Double),
+ TColumnSchema("double_2", ESimpleLogicalValueType::Float),
+ TColumnSchema("boolean", EValueType::Boolean),
+ TColumnSchema("string32", EValueType::String),
+ TColumnSchema("null", EValueType::Null),
+ TColumnSchema("opt_int64", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))),
+ TColumnSchema("opt_uint64", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint64))),
+ TColumnSchema("opt_double_1", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Double))),
+ TColumnSchema("opt_double_2", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Float))),
+ TColumnSchema("opt_boolean", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Boolean))),
+ TColumnSchema("opt_string32", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))),
+ }));
+ } else {
+ tableSchemas.push_back(New<TTableSchema>());
+ }
+ auto nameTable = New<TNameTable>();
+ TString result;
+ {
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, tableSchemas);
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {"int64", -1},
+ {"uint64", 2u},
+ {"double_1", 3.0},
+ {"double_2", 3.0},
+ {"boolean", true},
+ {"string32", "four"},
+ {"null", nullptr},
+
+ {"opt_int64", -5},
+ {"opt_uint64", 6u},
+ {"opt_double_1", 7.0},
+ {"opt_double_2", 7.0},
+ {"opt_boolean", false},
+ {"opt_string32", "eight"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Write({
+ MakeRow(nameTable, {
+ {"int64", -9},
+ {"uint64", 10u},
+ {"double_1", 11.0},
+ {"double_2", 11.0},
+ {"boolean", false},
+ {"string32", "twelve"},
+ {"null", nullptr},
+
+ {"opt_int64", nullptr},
+ {"opt_uint64", nullptr},
+ {"opt_double_1", nullptr},
+ {"opt_double_2", nullptr},
+ {"opt_boolean", nullptr},
+ {"opt_string32", nullptr},
+ {TableIndexColumnName, 0},
+ }).Get()
+ });
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ }
+
+ TStringInput resultInput(result);
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), -1);
+ ASSERT_EQ(checkedSkiffParser.ParseUint64(), 2u);
+ // double_1
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 3.0);
+ // double_2
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 3.0);
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), true);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "four");
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), -5);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseUint64(), 6u);
+
+ // double_1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 7.0);
+
+ // double_2
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 7.0);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), false);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "eight");
+
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), -9);
+ ASSERT_EQ(checkedSkiffParser.ParseUint64(), 10u);
+ // double_1
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 11.0);
+ // double_2
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 11.0);
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), false);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "twelve");
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ // double_1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ // double_2
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+
+ // end
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestAllWireTypesNoSchema)
+{
+ TestAllWireTypes(false);
+}
+
+TEST(TSkiffWriter, TestAllWireTypesWithSchema)
+{
+ TestAllWireTypes(true);
+}
+
+class TSkiffYsonWireTypeP
+ : public ::testing::TestWithParam<std::tuple<
+ TLogicalTypePtr,
+ TNamedValue::TValue,
+ TString
+ >>
+{
+public:
+ static std::vector<ParamType> GetCases()
+ {
+ using namespace NLogicalTypeShortcuts;
+ std::vector<ParamType> result;
+
+ for (const auto& example : GetPrimitiveValueExamples()) {
+ result.emplace_back(example.LogicalType, example.Value, example.PrettyYson);
+ result.emplace_back(nullptr, example.Value, example.PrettyYson);
+ }
+
+ for (const auto type : TEnumTraits<ESimpleLogicalValueType>::GetDomainValues()) {
+ auto logicalType = OptionalLogicalType(SimpleLogicalType(type));
+ if (IsV3Composite(logicalType)) {
+ // Optional<Null> is not v1 type
+ continue;
+ }
+ result.emplace_back(logicalType, nullptr, "#");
+ }
+ return result;
+ }
+
+ static const std::vector<ParamType> Cases;
+};
+
+const std::vector<TSkiffYsonWireTypeP::ParamType> TSkiffYsonWireTypeP::Cases = TSkiffYsonWireTypeP::GetCases();
+
+INSTANTIATE_TEST_SUITE_P(
+ Cases,
+ TSkiffYsonWireTypeP,
+ ::testing::ValuesIn(TSkiffYsonWireTypeP::Cases));
+
+TEST_P(TSkiffYsonWireTypeP, Test)
+{
+ const auto& [logicalType, value, expectedYson] = GetParam();
+ TTableSchemaPtr tableSchema;
+ if (logicalType) {
+ tableSchema = New<TTableSchema>(std::vector<TColumnSchema>{
+ TColumnSchema("column", logicalType),
+ });
+ } else {
+ tableSchema = New<TTableSchema>();
+ }
+ auto skiffTableSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("column"),
+ });
+ auto nameTable = New<TNameTable>();
+ TStringStream actualSkiffDataStream;
+ auto writer = CreateSkiffWriter(skiffTableSchema, nameTable, &actualSkiffDataStream, {tableSchema});
+ writer->Write({
+ MakeRow(nameTable, {{"column", value}})
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ auto actualSkiffData = actualSkiffDataStream.Str();
+ {
+ TMemoryInput in(actualSkiffData);
+ TCheckedSkiffParser parser(CreateVariant16Schema({skiffTableSchema}), &in);
+ EXPECT_EQ(parser.ParseVariant16Tag(), 0);
+ auto actualYson = parser.ParseYson32();
+ parser.ValidateFinished();
+
+ EXPECT_EQ(CanonizeYson(actualYson), CanonizeYson(expectedYson));
+ }
+
+ TCollectingValueConsumer rowCollector(nameTable);
+ auto parser = CreateParserForSkiff(skiffTableSchema, tableSchema, &rowCollector);
+ parser->Read(actualSkiffDataStream.Str());
+ parser->Finish();
+ auto actualValue = rowCollector.GetRowValue(0, "column");
+ EXPECT_EQ(actualValue, TNamedValue("column", value).ToUnversionedValue(nameTable));
+}
+
+TEST(TSkiffWriter, TestYsonWireType)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("yson32"),
+
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Yson32),
+ })->SetName("opt_yson32"),
+ });
+ auto nameTable = New<TNameTable>();
+ TString result;
+ {
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()});
+
+ // Row 0 (Null)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+
+ {"yson32", nullptr},
+ {"opt_yson32", nullptr},
+ }).Get(),
+ });
+
+ // Row 1 (Int64)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+
+ {"yson32", -5},
+ {"opt_yson32", -6},
+ }).Get(),
+ });
+
+ // Row 2 (Uint64)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+
+ {"yson32", 42u},
+ {"opt_yson32", 43u},
+ }).Get(),
+ });
+
+ // Row 3 ((Double)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+
+ {"yson32", 2.7182818},
+ {"opt_yson32", 3.1415926},
+ }).Get(),
+ });
+
+ // Row 4 ((Boolean)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+
+ {"yson32", true},
+ {"opt_yson32", false},
+ }).Get(),
+ });
+
+ // Row 5 ((String)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+
+ {"yson32", "Yin"},
+ {"opt_yson32", "Yang"},
+ }).Get(),
+ });
+
+ // Row 6 ((Any)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+
+ {"yson32", EValueType::Any, "{foo=bar;}"},
+ {"opt_yson32", EValueType::Any, "{bar=baz;}"},
+ }).Get(),
+ });
+
+ // Row 7 ((missing optional values)
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ }
+
+ TStringInput resultInput(result);
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ auto parseYson = [] (TCheckedSkiffParser* parser) {
+ auto yson = TString{parser->ParseYson32()};
+ return ConvertToNode(TYsonString(yson));
+ };
+
+ // Row 0 (Null)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->GetType(), ENodeType::Entity);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+
+ // Row 1 (Int64)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsInt64()->GetValue(), -5);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsInt64()->GetValue(), -6);
+
+ // Row 2 (Uint64)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsUint64()->GetValue(), 42u);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsUint64()->GetValue(), 43u);
+
+ // Row 3 (Double)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsDouble()->GetValue(), 2.7182818);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsDouble()->GetValue(), 3.1415926);
+
+ // Row 4 (Boolean)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsBoolean()->GetValue(), true);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsBoolean()->GetValue(), false);
+
+ // Row 5 (String)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsString()->GetValue(), "Yin");
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsString()->GetValue(), "Yang");
+
+ // Row 6 (Any)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsMap()->GetChildOrThrow("foo")->AsString()->GetValue(), "bar");
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->AsMap()->GetChildOrThrow("bar")->AsString()->GetValue(), "baz");
+
+ // Row 7 (Null)
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser)->GetType(), ENodeType::Entity);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+
+ // end
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+class TSkiffFormatSmallIntP
+: public ::testing::TestWithParam<std::tuple<
+ std::shared_ptr<TSkiffSchema>,
+ TLogicalTypePtr,
+ TNamedValue::TValue,
+ TString
+>>
+{
+public:
+ static std::vector<ParamType> GetCases()
+ {
+ using namespace NLogicalTypeShortcuts;
+
+ std::vector<ParamType> result;
+
+ auto addSimpleCase = [&result] (
+ EWireType wireType,
+ const TLogicalTypePtr& logicalType,
+ auto value,
+ TStringBuf skiffValue)
+ {
+ auto simpleSkiffSchema = CreateSimpleTypeSchema(wireType);
+ auto simpleSkiffData = TString(2, 0) + skiffValue;
+ result.emplace_back(simpleSkiffSchema, logicalType, value, simpleSkiffData);
+ };
+
+ auto addListCase = [&result] (
+ EWireType wireType,
+ const TLogicalTypePtr& logicalType,
+ auto value,
+ TStringBuf skiffValue)
+ {
+ auto listSkiffSchema = CreateRepeatedVariant8Schema({CreateSimpleTypeSchema(wireType)});
+ auto listSkiffData = TString(3, 0) + skiffValue + TString(1, '\xff');
+ auto listValue = TNamedValue::TValue{
+ TNamedValue::TComposite{
+ BuildYsonStringFluently()
+ .BeginList()
+ .Item().Value(value)
+ .EndList().ToString()
+ }
+ };
+ result.emplace_back(listSkiffSchema, List(logicalType), listValue, listSkiffData);
+ };
+
+ auto addSimpleAndListCases = [&] (
+ EWireType wireType,
+ const TLogicalTypePtr& logicalType,
+ auto value,
+ TStringBuf skiffValue)
+ {
+ addSimpleCase(wireType, logicalType, value, skiffValue);
+ addListCase(wireType, logicalType, value, skiffValue);
+ };
+
+ auto addMultiCase = [&] (EWireType wireType, auto value, TStringBuf skiffValue) {
+ auto add = [&] (const TLogicalTypePtr& logicalType) {
+ addSimpleAndListCases(wireType, logicalType, value, skiffValue);
+ };
+ addSimpleCase(wireType, Yson(), value, skiffValue);
+
+ using T = std::decay_t<decltype(value)>;
+ static_assert(std::is_integral_v<T>);
+ if constexpr (std::is_signed_v<T>) {
+ if (std::numeric_limits<i8>::min() <= value && value <= std::numeric_limits<i8>::max()) {
+ add(Int8());
+ }
+ if (std::numeric_limits<i16>::min() <= value && value <= std::numeric_limits<i16>::max()) {
+ add(Int16());
+ }
+ if (std::numeric_limits<i32>::min() <= value && value <= std::numeric_limits<i32>::max()) {
+ add(Int32());
+ }
+ add(Int64());
+ } else {
+ if (value <= std::numeric_limits<ui8>::max()) {
+ add(Uint8());
+ }
+ if (value <= std::numeric_limits<ui16>::max()) {
+ add(Uint16());
+ }
+ if (value <= std::numeric_limits<ui32>::max()) {
+ add(Uint32());
+ }
+ add(Uint64());
+ }
+ };
+ addMultiCase(EWireType::Int8, 0, TStringBuf("\x00"sv));
+ addMultiCase(EWireType::Int8, 42, TStringBuf("*"));
+ addMultiCase(EWireType::Int8, -42, TStringBuf("\xd6"sv));
+ addMultiCase(EWireType::Int8, 127, TStringBuf("\x7f"sv));
+ addMultiCase(EWireType::Int8, -128, TStringBuf("\x80"sv));
+
+ addMultiCase(EWireType::Int16, 0, TStringBuf("\x00\x00"sv));
+ addMultiCase(EWireType::Int16, 42, TStringBuf("\x2a\x00"sv));
+ addMultiCase(EWireType::Int16, -42, TStringBuf("\xd6\xff"sv));
+ addMultiCase(EWireType::Int16, 0x7fff, TStringBuf("\xff\x7f"sv));
+ addMultiCase(EWireType::Int16, -0x8000, TStringBuf("\x00\x80"sv));
+
+ addMultiCase(EWireType::Int32, 0, TStringBuf("\x00\x00\x00\x00"sv));
+ addMultiCase(EWireType::Int32, 42, TStringBuf("\x2a\x00\x00\x00"sv));
+ addMultiCase(EWireType::Int32, -42, TStringBuf("\xd6\xff\xff\xff"sv));
+ addMultiCase(EWireType::Int32, 0x7fffffff, TStringBuf("\xff\xff\xff\x7f"sv));
+ addMultiCase(EWireType::Int32, -0x80000000l, TStringBuf("\x00\x00\x00\x80"sv));
+
+ addMultiCase(EWireType::Uint8, 0ull, TStringBuf("\x00"sv));
+ addMultiCase(EWireType::Uint8, 42ull, TStringBuf("*"));
+ addMultiCase(EWireType::Uint8, 255ull, TStringBuf("\xff"sv));
+
+ addMultiCase(EWireType::Uint16, 0ull, TStringBuf("\x00\x00"sv));
+ addMultiCase(EWireType::Uint16, 42ull, TStringBuf("\x2a\x00"sv));
+ addMultiCase(EWireType::Uint16, 0xFFFFull, TStringBuf("\xff\xff"sv));
+
+ addMultiCase(EWireType::Uint32, 0ull, TStringBuf("\x00\x00\x00\x00"sv));
+ addMultiCase(EWireType::Uint32, 42ull, TStringBuf("\x2a\x00\x00\x00"sv));
+ addMultiCase(EWireType::Uint32, 0xFFFFFFFFull, TStringBuf("\xff\xff\xff\xff"sv));
+
+ addSimpleAndListCases(EWireType::Uint16, Date(), 0ull, TStringBuf("\x00\x00"sv));
+ addSimpleAndListCases(EWireType::Uint16, Date(), 42ull, TStringBuf("\x2a\x00"sv));
+ addSimpleAndListCases(EWireType::Uint16, Date(), DateUpperBound - 1, TStringBuf("\x08\xc2"sv));
+
+ addSimpleAndListCases(EWireType::Uint32, Datetime(), 0ull, TStringBuf("\x00\x00\x00\x00"sv));
+ addSimpleAndListCases(EWireType::Uint32, Datetime(), 42ull, TStringBuf("\x2a\x00\x00\x00"sv));
+ addSimpleAndListCases(EWireType::Uint32, Datetime(), DatetimeUpperBound - 1, TStringBuf("\x7f\xdd\xce\xff"sv));
+
+ return result;
+ }
+
+ static const std::vector<ParamType> Cases;
+};
+
+const std::vector<TSkiffFormatSmallIntP::ParamType> TSkiffFormatSmallIntP::Cases = TSkiffFormatSmallIntP::GetCases();
+
+INSTANTIATE_TEST_SUITE_P(
+ Cases,
+ TSkiffFormatSmallIntP,
+ ::testing::ValuesIn(TSkiffFormatSmallIntP::Cases));
+
+TEST_P(TSkiffFormatSmallIntP, Test)
+{
+ const auto& [skiffValueSchema, logicalType, value, expectedSkiffData] = GetParam();
+
+ const auto nameTable = New<TNameTable>();
+
+ TStringStream actualSkiffData;
+ auto skiffTableSchema = CreateTupleSchema({
+ skiffValueSchema->SetName("column")
+ });
+ auto tableSchema = New<TTableSchema>(std::vector<TColumnSchema>{
+ TColumnSchema("column", logicalType),
+ });
+ auto writer = CreateSkiffWriter(skiffTableSchema, nameTable, &actualSkiffData, {tableSchema});
+ writer->Write({
+ MakeRow(nameTable, {{"column", value}})
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ EXPECT_EQ(actualSkiffData.Str(), expectedSkiffData);
+
+ TCollectingValueConsumer rowCollector(nameTable);
+ auto parser = CreateParserForSkiff(skiffTableSchema, tableSchema, &rowCollector);
+ parser->Read(expectedSkiffData);
+ parser->Finish();
+ auto actualValue = rowCollector.GetRowValue(0, "column");
+
+ EXPECT_EQ(actualValue, TNamedValue("common", value).ToUnversionedValue(nameTable));
+}
+
+TEST(TSkiffWriter, TestBadSmallIntegers)
+{
+ using namespace NLogicalTypeShortcuts;
+ auto writeSkiffValue = [] (
+ std::shared_ptr<TSkiffSchema>&& typeSchema,
+ TLogicalTypePtr logicalType,
+ TNamedValue::TValue value)
+ {
+ TStringStream result;
+ auto skiffSchema = CreateTupleSchema({
+ typeSchema->SetName("column")
+ });
+ auto tableSchema = New<TTableSchema>(std::vector<TColumnSchema>{
+ TColumnSchema("column", std::move(logicalType)),
+ });
+ auto nameTable = New<TNameTable>();
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &result, {tableSchema});
+ writer->Write({
+ MakeRow(nameTable, {{"column", std::move(value)}})
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ return result.Str();
+ };
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Int8), Int64(), 128),
+ "is out of range for possible values");
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Int8), Int64(), -129),
+ "is out of range for possible values");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Int16), Int64(), 0x8000),
+ "is out of range for possible values");
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Int16), Int64(), -0x8001),
+ "is out of range for possible values");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Int32), Int64(), 0x80000000ll),
+ "is out of range for possible values");
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Int32), Int64(), -0x80000001ll),
+ "is out of range for possible values");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Uint8), Uint64(), 256ull),
+ "is out of range for possible values");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Uint16), Uint64(), 0x1FFFFull),
+ "is out of range for possible values");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ writeSkiffValue(CreateSimpleTypeSchema(EWireType::Uint32), Uint64(), 0x100000000ull),
+ "is out of range for possible values");
+}
+
+class TSkiffFormatUuidTestP : public ::testing::TestWithParam<std::tuple<
+ TNameTablePtr,
+ TTableSchemaPtr,
+ std::shared_ptr<TSkiffSchema>,
+ std::vector<TUnversionedOwningRow>,
+ TString
+>>
+{
+public:
+ static std::vector<ParamType> GetCases()
+ {
+ using namespace NLogicalTypeShortcuts;
+
+ auto nameTable = New<TNameTable>();
+ const auto stringUuidValue = TStringBuf("\xee\x1f\x37\x70" "\xb9\x93\x64\xb5" "\xe4\xdf\xe9\x03" "\x67\x5c\x30\x62");
+ const auto uint128UuidValue = TStringBuf("\x62\x30\x5c\x67" "\x03\xe9\xdf\xe4" "\xb5\x64\x93\xb9" "\x70\x37\x1f\xee");
+
+ const auto requiredTableSchema = New<TTableSchema>(std::vector<TColumnSchema>{TColumnSchema("uuid", Uuid())});
+ const auto optionalTableSchema = New<TTableSchema>(std::vector<TColumnSchema>{TColumnSchema("uuid", Optional(Uuid()))});
+
+ const auto optionalUint128SkiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Uint128),
+ })->SetName("uuid"),
+ });
+
+ const auto requiredUint128SkiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint128)->SetName("uuid"),
+ });
+
+ const auto optionalStringSkiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::String32),
+ })->SetName("uuid"),
+ });
+
+ const auto requiredStringSkiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("uuid"),
+ });
+
+ std::vector<ParamType> result;
+
+ result.emplace_back(
+ nameTable,
+ requiredTableSchema,
+ requiredUint128SkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + uint128UuidValue);
+
+ result.emplace_back(
+ nameTable,
+ optionalTableSchema,
+ requiredUint128SkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + uint128UuidValue);
+
+ result.emplace_back(
+ nameTable,
+ requiredTableSchema,
+ optionalUint128SkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + "\1" + uint128UuidValue);
+
+ result.emplace_back(
+ nameTable,
+ optionalTableSchema,
+ optionalUint128SkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + "\1" + uint128UuidValue);
+
+ const TString uuidLen = TString(TStringBuf("\x10\x00\x00\x00"sv));
+
+ result.emplace_back(
+ nameTable,
+ requiredTableSchema,
+ requiredStringSkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + uuidLen + stringUuidValue);
+
+ result.emplace_back(
+ nameTable,
+ optionalTableSchema,
+ requiredStringSkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + uuidLen + stringUuidValue);
+
+ result.emplace_back(
+ nameTable,
+ requiredTableSchema,
+ optionalStringSkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + "\1" + uuidLen + stringUuidValue);
+
+ result.emplace_back(
+ nameTable,
+ optionalTableSchema,
+ optionalStringSkiffSchema,
+ std::vector<TUnversionedOwningRow>{
+ MakeRow(nameTable, {{"uuid", stringUuidValue}}),
+ },
+ TString(2, '\0') + "\1" + uuidLen + stringUuidValue);
+
+ return result;
+ }
+
+ static const std::vector<ParamType> Cases;
+};
+
+const std::vector<TSkiffFormatUuidTestP::ParamType> TSkiffFormatUuidTestP::Cases = TSkiffFormatUuidTestP::GetCases();
+
+INSTANTIATE_TEST_SUITE_P(
+ Cases,
+ TSkiffFormatUuidTestP,
+ ::testing::ValuesIn(TSkiffFormatUuidTestP::Cases));
+
+TEST_P(TSkiffFormatUuidTestP, Test)
+{
+ const auto& [nameTable, tableSchema, skiffSchema, rows, skiffString] = GetParam();
+
+ TStringStream result;
+ std::vector<TUnversionedRow> nonOwningRows;
+ for (const auto& row : rows) {
+ nonOwningRows.emplace_back(row);
+ }
+ auto skiffWriter = CreateSkiffWriter(skiffSchema, nameTable, &result, {tableSchema});
+ skiffWriter->Write(MakeRange(nonOwningRows));
+ skiffWriter->Close().Get().ThrowOnError();
+ ASSERT_EQ(result.Str(), skiffString);
+
+ TCollectingValueConsumer rowCollector(nameTable);
+ auto requiredParser = CreateParserForSkiff(skiffSchema, tableSchema, &rowCollector);
+ requiredParser->Read(result.Str());
+ requiredParser->Finish();
+ ASSERT_EQ(rowCollector.GetRowList(), rows);
+}
+
+TEST(TSkiffFormatUuidTest, TestError)
+{
+ using namespace NLogicalTypeShortcuts;
+
+ auto nameTable = New<TNameTable>();
+ auto tableSchema = New<TTableSchema>(
+ std::vector<TColumnSchema>{TColumnSchema("uuid", Optional(Uuid()))});
+
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Uint128)->SetName("uuid"),
+ });
+
+ TStringStream result;
+ auto skiffWriter = CreateSkiffWriter(skiffSchema, nameTable, &result, {tableSchema});
+ skiffWriter->Write({
+ MakeRow(nameTable, {{"uuid", nullptr}}),
+ });
+ EXPECT_THROW_WITH_SUBSTRING(skiffWriter->Close().Get().ThrowOnError(),
+ "Unexpected type");
+
+}
+
+class TSkiffWriterSingular
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<ESimpleLogicalValueType>
+{};
+
+INSTANTIATE_TEST_SUITE_P(
+ Singular,
+ TSkiffWriterSingular,
+ ::testing::Values(ESimpleLogicalValueType::Null, ESimpleLogicalValueType::Void));
+
+TEST_P(TSkiffWriterSingular, TestOptionalSingular)
+{
+ const auto singularType = GetParam();
+
+ auto skiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ })->SetName("opt_null"),
+ });
+
+ auto nameTable = New<TNameTable>();
+ const std::vector<TTableSchemaPtr> tableSchemas = {
+ New<TTableSchema>(std::vector{
+ TColumnSchema("opt_null", OptionalLogicalType(SimpleLogicalType(singularType))),
+ }),
+ };
+
+ TString result;
+ {
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, tableSchemas);
+ // Row 0
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"opt_null", nullptr},
+ }).Get(),
+ });
+ // Row 1
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"opt_null", EValueType::Composite, "[#]"},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ }
+
+ TStringInput resultInput(result);
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestRearrange)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("number"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::String32),
+ })->SetName("eng"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::String32),
+ })->SetName("rus"),
+ });
+ auto nameTable = New<TNameTable>();
+ TString result;
+ {
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()});
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"number", 1},
+ {"eng", "one"},
+ {"rus", nullptr},
+ }).Get()
+ });
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"eng", nullptr},
+ {"number", 2},
+ {"rus", "dva"},
+ }).Get()
+ });
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"rus", "tri"},
+ {"eng", "three"},
+ {"number", 3},
+ }).Get()
+ });
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ }
+
+ TStringInput resultInput(result);
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "one");
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 2);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "dva");
+
+ // row 2
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 3);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "three");
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "tri");
+
+ // end
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestMissingRequiredField)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("number"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("eng"),
+ });
+ auto nameTable = New<TNameTable>();
+ TString result;
+ try {
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()});
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"number", 1},
+ }).Get()
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Unexpected type of \"eng\" column"));
+ }
+}
+
+TEST(TSkiffWriter, TestSparse)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateRepeatedVariant16Schema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("int64"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("uint64"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("string32"),
+ })->SetName("$sparse_columns"),
+ });
+
+ auto nameTable = New<TNameTable>();
+ TString result;
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()});
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"int64", -1},
+ {"string32", "minus one"},
+ }).Get(),
+ });
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"string32", "minus five"},
+ {"int64", -5},
+ }).Get(),
+ });
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"uint64", 42u},
+ }).Get(),
+ });
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"int64", -8},
+ {"uint64", nullptr},
+ {"string32", nullptr},
+ }).Get(),
+ });
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(result);
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), -1);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 2);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "minus one");
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), EndOfSequenceTag<ui16>());
+
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 2);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "minus five");
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), -5);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), EndOfSequenceTag<ui16>());
+
+ // row 2
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseUint64(), 42u);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), EndOfSequenceTag<ui16>());
+
+ // row 3
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), -8);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), EndOfSequenceTag<ui16>());
+
+ // row 4
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), EndOfSequenceTag<ui16>());
+
+ // end
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestMissingFields)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("value"),
+ });
+
+ try {
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()});
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"unknown_column", "four"},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Column \"unknown_column\" is not described by Skiff schema"));
+ }
+
+ try {
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto unknownColumnId = nameTable->RegisterName("unknown_column");
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, std::vector{New<TTableSchema>()});
+
+ ASSERT_TRUE(unknownColumnId < nameTable->GetId("value"));
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"unknown_column", "four"},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+ ADD_FAILURE();
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Column \"unknown_column\" is not described by Skiff schema"));
+ }
+}
+
+TEST(TSkiffWriter, TestOtherColumns)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64)
+ })->SetName("int64_column"),
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("$other_columns"),
+ });
+
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ nameTable->RegisterName("string_column");
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()});
+
+ // Row 0.
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"string_column", "foo"},
+ }).Get(),
+ });
+
+ // Row 1.
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"int64_column", 42},
+ }).Get(),
+ });
+ // Row 2.
+ writer->Write({
+ MakeRow(nameTable, {
+ {TableIndexColumnName, 0},
+ {"other_string_column", "bar"},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ auto parseYson = [] (TCheckedSkiffParser* parser) {
+ auto yson = TString{parser->ParseYson32()};
+ return ConvertToYsonTextStringStable(ConvertToNode(TYsonString(yson)));
+ };
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser), "{\"string_column\"=\"foo\";}");
+
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 42);
+ ASSERT_EQ(parseYson(&checkedSkiffParser), "{}");
+
+ // row 2
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(parseYson(&checkedSkiffParser), "{\"other_string_column\"=\"bar\";}");
+
+ // end
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestKeySwitch)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("value"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("$key_switch"),
+ });
+
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()}, 1);
+
+ writer->Write({
+ // Row 0.
+ MakeRow(nameTable, {
+ {"value", "one"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ // Row 1.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", "one"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ // Row 2.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", "two"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ TString buf;
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "one");
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), false);
+
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "one");
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), false);
+
+ // row 2
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "two");
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), true);
+
+ // end
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestEndOfStream)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("value"),
+ });
+
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()}, 1, true);
+
+ // Row 0.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", "zero"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ // Row 1.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", "one"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ TString buf;
+
+ // Row 0.
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "zero");
+
+ // Row 1.
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "one");
+
+ // End of stream.
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0xffff);
+
+ // The End.
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestRowRangeIndex)
+{
+ const auto rowAndRangeIndex = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName("$range_index"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName("$row_index"),
+ });
+
+ struct TRow {
+ int TableIndex;
+ std::optional<int> RangeIndex;
+ std::optional<int> RowIndex;
+ };
+ auto generateUnversionedRow = [] (const TRow& row, const TNameTablePtr& nameTable) {
+ std::vector<TNamedValue> values = {
+ {TableIndexColumnName, row.TableIndex},
+ };
+ if (row.RangeIndex) {
+ values.emplace_back(RangeIndexColumnName, *row.RangeIndex);
+ }
+ if (row.RowIndex) {
+ values.push_back({RowIndexColumnName, *row.RowIndex});
+ }
+ return MakeRow(nameTable, values);
+ };
+
+ auto skiffWrite = [generateUnversionedRow] (const std::vector<TRow>& rows, const std::shared_ptr<TSkiffSchema>& skiffSchema) {
+ std::vector<TTableSchemaPtr> tableSchemas;
+ {
+ THashSet<int> tableIndices;
+ for (const auto& row : rows) {
+ tableIndices.insert(row.TableIndex);
+ }
+ tableSchemas.assign(tableIndices.size(), New<TTableSchema>());
+ }
+
+
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto writer = CreateSkiffWriter(
+ skiffSchema,
+ nameTable,
+ &resultStream,
+ tableSchemas);
+
+ for (const auto& row : rows) {
+ writer->Write({generateUnversionedRow(row, nameTable)});
+ }
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ return HexEncode(resultStream.Str());
+ };
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, 0, 0},
+ {0, 0, 1},
+ {0, 0, 2},
+ }, rowAndRangeIndex).data(),
+
+ "0000" "01""00000000""00000000" "01""00000000""00000000"
+ "0000" "00" "00"
+ "0000" "00" "00"
+ );
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, 0, 0},
+ {0, 0, 1},
+ {0, 0, 3},
+ }, rowAndRangeIndex).data(),
+
+ "0000" "01""00000000""00000000" "01""00000000""00000000"
+ "0000" "00" "00"
+ "0000" "00" "01""03000000""00000000"
+ );
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, 0, 0},
+ {0, 0, 1},
+ {0, 1, 2},
+ {0, 1, 3},
+ }, rowAndRangeIndex).data(),
+
+ "0000" "01""00000000""00000000" "01""00000000""00000000"
+ "0000" "00" "00"
+ "0000" "01""01000000""00000000" "01""02000000""00000000"
+ "0000" "00" "00"
+ );
+
+ EXPECT_THROW_WITH_SUBSTRING(skiffWrite({{0, 0, {}}}, rowAndRangeIndex), "index requested but reader did not return it");
+ EXPECT_THROW_WITH_SUBSTRING(skiffWrite({{0, {}, 0}}, rowAndRangeIndex), "index requested but reader did not return it");
+
+ const auto rowAndRangeIndexAllowMissing = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ })->SetName("$range_index"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ })->SetName("$row_index"),
+ });
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, 0, 0},
+ {0, 0, 1},
+ {0, 0, 2},
+ }, rowAndRangeIndexAllowMissing).data(),
+
+ "0000" "01""00000000""00000000" "01""00000000""00000000"
+ "0000" "00" "00"
+ "0000" "00" "00"
+ );
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, 0, 0},
+ {0, 0, 1},
+ {0, 0, 3},
+ }, rowAndRangeIndexAllowMissing).data(),
+
+ "0000" "01""00000000""00000000" "01""00000000""00000000"
+ "0000" "00" "00"
+ "0000" "00" "01""03000000""00000000"
+ );
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, 0, 0},
+ {0, 0, 1},
+ {0, 1, 2},
+ {0, 1, 3},
+ }, rowAndRangeIndexAllowMissing).data(),
+
+ "0000" "01""00000000""00000000" "01""00000000""00000000"
+ "0000" "00" "00"
+ "0000" "01""01000000""00000000" "01""02000000""00000000"
+ "0000" "00" "00"
+ );
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, {}, {}},
+ {0, {}, {}},
+ {0, {}, {}},
+ {0, {}, {}},
+ }, rowAndRangeIndexAllowMissing).data(),
+
+ "0000" "02" "02"
+ "0000" "02" "02"
+ "0000" "02" "02"
+ "0000" "02" "02"
+ );
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, {}, 0},
+ {0, {}, 1},
+ {0, {}, 3},
+ {0, {}, 4},
+ }, rowAndRangeIndexAllowMissing).data(),
+
+ "0000" "02" "01""00000000""00000000"
+ "0000" "02" "00"
+ "0000" "02" "01""03000000""00000000"
+ "0000" "02" "00"
+ );
+
+ EXPECT_STREQ(
+ skiffWrite({
+ {0, 0, {}},
+ {0, 0, {}},
+ {0, 1, {}},
+ {0, 1, {}},
+ }, rowAndRangeIndexAllowMissing).data(),
+
+ "0000" "01""00000000""00000000" "02"
+ "0000" "00" "02"
+ "0000" "01""01000000""00000000" "02"
+ "0000" "00" "02"
+ );
+}
+
+TEST(TSkiffWriter, TestRowIndexOnlyOrRangeIndexOnly)
+{
+ TString columnNameList[] = {
+ RowIndexColumnName,
+ RangeIndexColumnName,
+ };
+
+ for (const auto& columnName : columnNameList) {
+ auto skiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName(columnName),
+ });
+
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()}, 1);
+
+ // Row 0.
+ writer->Write({
+ MakeRow(nameTable, {
+ {columnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 0);
+
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+ }
+}
+
+TEST(TSkiffWriter, TestComplexType)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("name"),
+ CreateRepeatedVariant8Schema({
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("x"),
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("y"),
+ })
+ })->SetName("points")
+ })->SetName("value"),
+ });
+
+ {
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto tableSchema = New<TTableSchema>(std::vector{
+ TColumnSchema("value", StructLogicalType({
+ {"name", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {
+ "points",
+ ListLogicalType(
+ StructLogicalType({
+ {"x", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"y", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ })
+ )
+ }
+ })),
+ });
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, std::vector{tableSchema});
+
+ // Row 0.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", EValueType::Composite, "[foo;[[0; 1];[2;3]]]"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "foo");
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 2);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 3);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), EndOfSequenceTag<ui8>());
+
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+ }
+}
+
+TEST(TSkiffWriter, TestEmptyComplexType)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("name"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("value"),
+ })
+ })->SetName("value"),
+ });
+
+ {
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto tableSchema = New<TTableSchema>(std::vector{
+ TColumnSchema("value", OptionalLogicalType(
+ StructLogicalType({
+ {"name", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"value", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }))
+ ),
+ });
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, std::vector{tableSchema});
+
+ // Row 0.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", nullptr},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 0);
+
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+ }
+}
+
+TEST(TSkiffWriter, TestSparseComplexType)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateRepeatedVariant16Schema({
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("name"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("value"),
+ })->SetName("value"),
+ })->SetName("$sparse_columns"),
+ });
+
+ {
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto tableSchema = New<TTableSchema>(std::vector{
+ TColumnSchema("value", OptionalLogicalType(
+ StructLogicalType({
+ {"name", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"value", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }))
+ ),
+ });
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, std::vector{tableSchema});
+
+ // Row 0.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", EValueType::Composite, "[foo;bar;]"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "foo");
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "bar");
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), EndOfSequenceTag<ui16>());
+
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+ }
+}
+
+TEST(TSkiffWriter, TestSparseComplexTypeWithExtraOptional)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateRepeatedVariant16Schema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("name"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("value"),
+ })
+ })->SetName("value"),
+ })->SetName("$sparse_columns"),
+ });
+
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto tableSchema = New<TTableSchema>(std::vector{
+ TColumnSchema("value", OptionalLogicalType(
+ StructLogicalType({
+ {"name", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"value", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ }))
+ ),
+ });
+
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, std::vector{tableSchema});
+
+ // Row 0.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", EValueType::Composite, "[foo;bar;]"},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "foo");
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "bar");
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), EndOfSequenceTag<ui16>());
+
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+}
+
+TEST(TSkiffWriter, TestBadWireTypeForSimpleColumn)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Yson32),
+ })
+ })->SetName("opt_yson32"),
+ });
+ auto nameTable = New<TNameTable>();
+ TStringStream resultStream;
+ EXPECT_THROW_WITH_SUBSTRING(
+ CreateSkiffWriter(skiffSchema, nameTable, &resultStream, std::vector{New<TTableSchema>()}),
+ "cannot be represented with Skiff schema"
+ );
+}
+
+TEST(TSkiffWriter, TestMissingComplexColumn)
+{
+ auto optionalSkiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateRepeatedVariant8Schema({CreateSimpleTypeSchema(EWireType::Int64)}),
+ })->SetName("opt_list"),
+ });
+ auto requiredSkiffSchema = CreateTupleSchema({
+ CreateRepeatedVariant8Schema({CreateSimpleTypeSchema(EWireType::Int64)})->SetName("opt_list"),
+ });
+
+ { // Non optional Skiff schema
+ auto nameTable = New<TNameTable>();
+ EXPECT_THROW_WITH_SUBSTRING(
+ CreateSkiffWriter(requiredSkiffSchema, nameTable, &Cnull, std::vector{New<TTableSchema>()}),
+ "cannot be represented with Skiff schema"
+ );
+ }
+
+ {
+ auto nameTable = New<TNameTable>();
+ TStringStream resultStream;
+ auto writer = CreateSkiffWriter(optionalSkiffSchema, nameTable, &resultStream, std::vector{New<TTableSchema>()});
+ writer->Write({
+ MakeRow(nameTable, { }).Get(),
+ MakeRow(nameTable, {
+ {"opt_list", nullptr},
+ }).Get(),
+ MakeRow(nameTable, { }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ EXPECT_EQ(HexEncode(resultStream.Str()), "0000" "00" "0000" "00" "0000" "00");
+ }
+}
+
+TEST(TSkiffWriter, TestSkippedFields)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("number"),
+ CreateSimpleTypeSchema(EWireType::Nothing)->SetName("string"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName(RangeIndexColumnName),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName(RowIndexColumnName),
+ CreateSimpleTypeSchema(EWireType::Double)->SetName("double"),
+ });
+ auto tableSchema = New<TTableSchema>(std::vector{
+ TColumnSchema("number", EValueType::Int64),
+ TColumnSchema("string", EValueType::String),
+ TColumnSchema("double", EValueType::Double),
+ });
+
+ auto nameTable = New<TNameTable>();
+ TString result;
+ {
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {tableSchema});
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {"number", 1},
+ {"string", "hello"},
+ {RangeIndexColumnName, 0},
+ {RowIndexColumnName, 0},
+ {"double", 1.5},
+ }).Get()
+ });
+ writer->Write({
+ MakeRow(nameTable, {
+ {"number", 1},
+ {RangeIndexColumnName, 5},
+ {RowIndexColumnName, 1},
+ {"double", 2.5},
+ }).Get()
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(result);
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 1.5);
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 5);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseDouble(), 2.5);
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+ }
+
+}
+
+TEST(TSkiffWriter, TestSkippedFieldsOutOfRange)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Nothing)->SetName("string"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName(RangeIndexColumnName),
+ });
+ auto tableSchema = New<TTableSchema>(std::vector{
+ TColumnSchema("string", EValueType::String),
+ });
+
+ auto nameTable = New<TNameTable>();
+ TString result;
+ {
+ TStringOutput resultStream(result);
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {tableSchema});
+
+ writer->Write({
+ MakeRow(nameTable, {
+ {"string", "hello"},
+ {RangeIndexColumnName, 0},
+ }).Get()
+ });
+ writer->Write({
+ MakeRow(nameTable, {
+ {RangeIndexColumnName, 5},
+ }).Get()
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(result);
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 0);
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseVariant8Tag(), 1);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 5);
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+ }
+
+}
+
+TEST(TSkiffWriter, TestSkippedFieldsAndKeySwitch)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("value"),
+ CreateSimpleTypeSchema(EWireType::Nothing)->SetName("skipped"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("$key_switch"),
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("value1"),
+ });
+ TStringStream resultStream;
+ auto nameTable = New<TNameTable>();
+ auto writer = CreateSkiffWriter(skiffSchema, nameTable, &resultStream, {New<TTableSchema>()}, 1);
+
+ writer->Write({
+ // Row 0.
+ MakeRow(nameTable, {
+ {"value", "one"},
+ {"value1", 0},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ // Row 1.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", "one"},
+ {"value1", 1},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ // Row 2.
+ writer->Write({
+ MakeRow(nameTable, {
+ {"value", "two"},
+ {"value1", 2},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ });
+ writer->Close()
+ .Get()
+ .ThrowOnError();
+
+ TStringInput resultInput(resultStream.Str());
+ TCheckedSkiffParser checkedSkiffParser(CreateVariant16Schema({skiffSchema}), &resultInput);
+
+ TString buf;
+
+ // row 0
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "one");
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), false);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 0);
+
+ // row 1
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "one");
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), false);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 1);
+
+ // row 2
+ ASSERT_EQ(checkedSkiffParser.ParseVariant16Tag(), 0);
+ ASSERT_EQ(checkedSkiffParser.ParseString32(), "two");
+ ASSERT_EQ(checkedSkiffParser.ParseBoolean(), true);
+ ASSERT_EQ(checkedSkiffParser.ParseInt64(), 2);
+
+ // end
+ ASSERT_EQ(checkedSkiffParser.HasMoreData(), false);
+ checkedSkiffParser.ValidateFinished();
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSkiffParser, Simple)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("int64"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("uint64"),
+ CreateSimpleTypeSchema(EWireType::Double)->SetName("double"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("boolean"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("string32"),
+ CreateSimpleTypeSchema(EWireType::Nothing)->SetName("null"),
+
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ })->SetName("opt_int64"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Uint64),
+ })->SetName("opt_uint64"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Double),
+ })->SetName("opt_double"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ })->SetName("opt_boolean"),
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::String32),
+ })->SetName("opt_string32"),
+ });
+
+ TCollectingValueConsumer collectedRows;
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteInt64(-1);
+ checkedSkiffWriter.WriteUint64(2);
+ checkedSkiffWriter.WriteDouble(3.0);
+ checkedSkiffWriter.WriteBoolean(true);
+ checkedSkiffWriter.WriteString32("foo");
+
+ checkedSkiffWriter.WriteVariant8Tag(0);
+ checkedSkiffWriter.WriteVariant8Tag(0);
+ checkedSkiffWriter.WriteVariant8Tag(0);
+ checkedSkiffWriter.WriteVariant8Tag(0);
+ checkedSkiffWriter.WriteVariant8Tag(0);
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 1);
+
+ ASSERT_EQ(GetInt64(collectedRows.GetRowValue(0, "int64")), -1);
+ ASSERT_EQ(GetUint64(collectedRows.GetRowValue(0, "uint64")), 2u);
+ ASSERT_EQ(GetDouble(collectedRows.GetRowValue(0, "double")), 3.0);
+ ASSERT_EQ(GetBoolean(collectedRows.GetRowValue(0, "boolean")), true);
+ ASSERT_EQ(GetString(collectedRows.GetRowValue(0, "string32")), "foo");
+ ASSERT_EQ(IsNull(collectedRows.GetRowValue(0, "null")), true);
+
+ ASSERT_EQ(IsNull(collectedRows.GetRowValue(0, "opt_int64")), true);
+ ASSERT_EQ(IsNull(collectedRows.GetRowValue(0, "opt_uint64")), true);
+ ASSERT_EQ(IsNull(collectedRows.GetRowValue(0, "opt_double")), true);
+ ASSERT_EQ(IsNull(collectedRows.GetRowValue(0, "opt_boolean")), true);
+ ASSERT_EQ(IsNull(collectedRows.GetRowValue(0, "opt_string32")), true);
+}
+
+TEST(TSkiffParser, TestOptionalNull)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ })->SetName("opt_null"),
+ });
+ auto nameTable = New<TNameTable>();
+
+ {
+ TCollectingValueConsumer collectedRows;
+ EXPECT_THROW_WITH_SUBSTRING(
+ CreateParserForSkiff(skiffSchema, &collectedRows),
+ "cannot be represented with Skiff schema");
+ }
+
+ auto tableSchema = New<TTableSchema>(std::vector{
+ TColumnSchema("opt_null", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null))),
+ });
+
+ TCollectingValueConsumer collectedRows(tableSchema);
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteVariant8Tag(0);
+
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteVariant8Tag(1);
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 2);
+
+ ASSERT_EQ(collectedRows.GetRowValue(0, "opt_null").Type, EValueType::Null);
+}
+
+TEST(TSkiffParser, TestSparse)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateRepeatedVariant16Schema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("int64"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("uint64"),
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("string32"),
+ })->SetName("$sparse_columns"),
+ });
+
+ TCollectingValueConsumer collectedRows;
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ // row 1
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ // sparse fields begin
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteInt64(-42);
+ checkedSkiffWriter.WriteVariant16Tag(1);
+ checkedSkiffWriter.WriteUint64(54);
+ checkedSkiffWriter.WriteVariant16Tag(EndOfSequenceTag<ui16>());
+
+ // row 2
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ // sparse fields begin
+ checkedSkiffWriter.WriteVariant16Tag(2);
+ checkedSkiffWriter.WriteString32("foo");
+ checkedSkiffWriter.WriteVariant16Tag(EndOfSequenceTag<ui16>());
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 2);
+
+ ASSERT_EQ(GetInt64(collectedRows.GetRowValue(0, "int64")), -42);
+ ASSERT_EQ(GetUint64(collectedRows.GetRowValue(0, "uint64")), 54u);
+ ASSERT_FALSE(collectedRows.FindRowValue(0, "string32"));
+
+ ASSERT_FALSE(collectedRows.FindRowValue(1, "int64"));
+ ASSERT_FALSE(collectedRows.FindRowValue(1, "uint64"));
+ ASSERT_EQ(GetString(collectedRows.GetRowValue(1, "string32")), "foo");
+}
+
+TEST(TSkiffParser, TestYsonWireType)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("yson"),
+ });
+
+ TCollectingValueConsumer collectedRows;
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ // Row 0.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteYson32("-42");
+
+ // Row 1.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteYson32("42u");
+
+ // Row 2.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteYson32("\"foobar\"");
+
+ // Row 3.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteYson32("%true");
+
+ // Row 4.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteYson32("{foo=bar}");
+
+ // Row 5.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteYson32("#");
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 6);
+ ASSERT_EQ(GetInt64(collectedRows.GetRowValue(0, "yson")), -42);
+ ASSERT_EQ(GetUint64(collectedRows.GetRowValue(1, "yson")), 42u);
+ ASSERT_EQ(GetString(collectedRows.GetRowValue(2, "yson")), "foobar");
+ ASSERT_EQ(GetBoolean(collectedRows.GetRowValue(3, "yson")), true);
+ ASSERT_EQ(GetAny(collectedRows.GetRowValue(4, "yson"))->AsMap()->GetChildOrThrow("foo")->AsString()->GetValue(), "bar");
+ ASSERT_EQ(IsNull(collectedRows.GetRowValue(5, "yson")), true);
+}
+
+TEST(TSkiffParser, TestBadYsonWireType)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("yson"),
+ });
+
+ auto parseYsonUsingSkiff = [&] (TStringBuf ysonValue) {
+ TCollectingValueConsumer collectedRows;
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+ TStringStream dataStream;
+ ASSERT_NO_THROW({
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteYson32(ysonValue);
+
+ checkedSkiffWriter.Finish();
+ });
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+ };
+
+ try {
+ parseYsonUsingSkiff("[42");
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Premature end of stream"));
+ }
+
+ try {
+ parseYsonUsingSkiff("<foo=bar>42");
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Table values cannot have top-level attributes"));
+ }
+}
+
+TEST(TSkiffParser, TestSpecialColumns)
+{
+ std::shared_ptr<TSkiffSchema> skiffSchemaList[] = {
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("yson"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("$key_switch"),
+ }),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("yson"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("$row_switch"),
+ }),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("yson"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("$range_switch"),
+ }),
+ };
+
+ for (const auto& skiffSchema : skiffSchemaList) {
+ try {
+ TCollectingValueConsumer collectedRows;
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+ } catch (std::exception& e) {
+ EXPECT_THAT(e.what(), testing::HasSubstr("Skiff parser does not support \"$key_switch\""));
+ }
+ }
+}
+
+TEST(TSkiffParser, TestOtherColumns)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("name"),
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("$other_columns"),
+ });
+
+ TCollectingValueConsumer collectedRows;
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ // Row 0.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteString32("row_0");
+ checkedSkiffWriter.WriteYson32("{foo=-42;}");
+
+ // Row 1.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteString32("row_1");
+ checkedSkiffWriter.WriteYson32("{bar=qux;baz={boolean=%false;};}");
+
+ // Row 2.
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 2);
+ ASSERT_EQ(GetString(collectedRows.GetRowValue(0, "name")), "row_0");
+ ASSERT_EQ(GetInt64(collectedRows.GetRowValue(0, "foo")), -42);
+
+ ASSERT_EQ(GetString(collectedRows.GetRowValue(1, "name")), "row_1");
+ ASSERT_EQ(GetString(collectedRows.GetRowValue(1, "bar")), "qux");
+ ASSERT_EQ(ConvertToYsonTextStringStable(GetAny(collectedRows.GetRowValue(1, "baz"))), "{\"boolean\"=%false;}");
+}
+
+TEST(TSkiffParser, TestComplexColumn)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("value"),
+ })->SetName("column")
+ });
+
+ TCollectingValueConsumer collectedRows(
+ New<TTableSchema>(std::vector{
+ TColumnSchema("column", NTableClient::StructLogicalType({
+ {"key", NTableClient::SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"value", NTableClient::SimpleLogicalType(ESimpleLogicalValueType::Int64)}
+ }))
+ }));
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ // Row 0.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteString32("row_0");
+ checkedSkiffWriter.WriteInt64(42);
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 1);
+ ASSERT_EQ(ConvertToYsonTextStringStable(GetComposite(collectedRows.GetRowValue(0, "column"))), "[\"row_0\";42;]");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSkiffParser, TestEmptyInput)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("column"),
+ });
+
+ TCollectingValueConsumer collectedRows;
+
+ {
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+ parser->Finish();
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 0);
+ }
+ {
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+ parser->Read("");
+ parser->Finish();
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 0);
+ }
+ {
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+ parser->Read("");
+ parser->Read("");
+ parser->Finish();
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 0);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TSkiffParser, ColumnIds)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("field_a"),
+ CreateSimpleTypeSchema(EWireType::Uint64)->SetName("field_b")
+ });
+
+ TCollectingValueConsumer collectedRows;
+ collectedRows.GetNameTable()->GetIdOrRegisterName("field_b");
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteInt64(-1);
+ checkedSkiffWriter.WriteUint64(2);
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 1);
+
+ ASSERT_EQ(GetInt64(collectedRows.GetRowValue(0, "field_a")), -1);
+ ASSERT_EQ(GetUint64(collectedRows.GetRowValue(0, "field_b")), 2u);
+}
+
+TEST(TSkiffParser, TestSparseComplexType)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateRepeatedVariant16Schema({
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("name"),
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("value"),
+ })->SetName("value"),
+ })->SetName("$sparse_columns"),
+ });
+
+ TCollectingValueConsumer collectedRows(
+ New<TTableSchema>(std::vector{
+ TColumnSchema("value", OptionalLogicalType(
+ StructLogicalType({
+ {"name", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"value", SimpleLogicalType(ESimpleLogicalValueType::Int64)}
+ })
+ ))
+ }));
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ // Row 0.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteString32("row_0");
+ checkedSkiffWriter.WriteInt64(10);
+ checkedSkiffWriter.WriteVariant16Tag(EndOfSequenceTag<ui16>());
+
+ // Row 1.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteVariant16Tag(EndOfSequenceTag<ui16>());
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 2);
+ EXPECT_EQ(ConvertToYsonTextStringStable(GetComposite(collectedRows.GetRowValue(0, "value"))), "[\"row_0\";10;]");
+ EXPECT_FALSE(collectedRows.FindRowValue(1, "value"));
+}
+
+TEST(TSkiffParser, TestSparseComplexTypeWithExtraOptional)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateRepeatedVariant16Schema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("value"),
+ })
+ })->SetName("column"),
+ })->SetName("$sparse_columns"),
+ });
+
+ TCollectingValueConsumer collectedRows(
+ New<TTableSchema>(std::vector{
+ TColumnSchema("column", OptionalLogicalType(
+ StructLogicalType({
+ {"key", NTableClient::SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"value", NTableClient::SimpleLogicalType(ESimpleLogicalValueType::Int64)}
+ })
+ ))
+ }));
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ TStringStream dataStream;
+ TCheckedSkiffWriter checkedSkiffWriter(CreateVariant16Schema({skiffSchema}), &dataStream);
+
+ // Row 0.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteVariant8Tag(1);
+ checkedSkiffWriter.WriteString32("row_0");
+ checkedSkiffWriter.WriteInt64(42);
+ checkedSkiffWriter.WriteVariant16Tag(EndOfSequenceTag<ui16>());
+
+ // Row 1.
+ checkedSkiffWriter.WriteVariant16Tag(0);
+ checkedSkiffWriter.WriteVariant16Tag(EndOfSequenceTag<ui16>());
+
+ checkedSkiffWriter.Finish();
+
+ parser->Read(dataStream.Str());
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 2);
+ ASSERT_EQ(ConvertToYsonTextStringStable(GetComposite(collectedRows.GetRowValue(0, "column"))), "[\"row_0\";42;]");
+ ASSERT_FALSE(collectedRows.FindRowValue(1, "column"));
+}
+
+
+TEST(TSkiffParser, TestBadWireTypeForSimpleColumn)
+{
+ auto skiffSchema = CreateTupleSchema({
+ CreateVariant8Schema({
+ CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Yson32),
+ })
+ })->SetName("opt_yson32"),
+ });
+
+ TCollectingValueConsumer collectedRows;
+ EXPECT_THROW_WITH_SUBSTRING(
+ CreateParserForSkiff(skiffSchema, &collectedRows),
+ "cannot be represented with Skiff schema"
+ );
+}
+
+TEST(TSkiffParser, TestEmptyColumns)
+{
+ auto skiffSchema = CreateTupleSchema({});
+ TCollectingValueConsumer collectedRows;
+ auto parser = CreateParserForSkiff(skiffSchema, &collectedRows);
+
+ parser->Read(TStringBuf("\x00\x00\x00\x00"sv));
+ parser->Finish();
+
+ ASSERT_EQ(static_cast<int>(collectedRows.Size()), 2);
+}
+
+TEST(TSkiffFormat, TestTimestamp)
+{
+ using namespace NLogicalTypeShortcuts;
+ CHECK_BIDIRECTIONAL_CONVERSION(Timestamp(), CreateSimpleTypeSchema(EWireType::Uint64), 42ull, "2A000000" "00000000");
+ CHECK_BIDIRECTIONAL_CONVERSION(Interval(), CreateSimpleTypeSchema(EWireType::Int64), 42, "2A000000" "00000000");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/yt/yt/client/unittests/skiff_yson_converter_ut.cpp b/yt/yt/client/unittests/skiff_yson_converter_ut.cpp
new file mode 100644
index 0000000000..18ecfac352
--- /dev/null
+++ b/yt/yt/client/unittests/skiff_yson_converter_ut.cpp
@@ -0,0 +1,728 @@
+#include "logical_type_shortcuts.h"
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/formats/skiff_yson_converter.h>
+
+#include <yt/yt/core/yson/parser.h>
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/core/yson/token_writer.h>
+#include <yt/yt/core/yson/writer.h>
+
+#include <library/cpp/skiff/skiff.h>
+#include <library/cpp/skiff/skiff_schema.h>
+
+#include <util/string/hex.h>
+
+#include <util/stream/mem.h>
+
+namespace NYT::NFormats {
+namespace {
+
+using namespace NTableClient;
+using namespace NSkiff;
+using namespace NYson;
+using namespace NTableClient::NLogicalTypeShortcuts;
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::shared_ptr<TSkiffSchema> SkiffOptional(std::shared_ptr<TSkiffSchema> skiffSchema)
+{
+ return CreateVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ std::move(skiffSchema)
+ });
+}
+
+TString ConvertYsonHex(
+ const TLogicalTypePtr& logicalType,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ TStringBuf ysonString,
+ const TYsonToSkiffConverterConfig& config = {})
+{
+ auto converter = CreateYsonToSkiffConverter(
+ TComplexTypeFieldDescriptor("test-field", logicalType),
+ skiffSchema,
+ config);
+
+ // Yson parsers have a bug when they can't parse some values that end unexpectedly.
+ TString spacedYsonInput = TString{ysonString} + " ";
+
+ TStringStream out;
+ {
+ TCheckedInDebugSkiffWriter writer(skiffSchema, &out);
+
+ TMemoryInput in(spacedYsonInput);
+ TYsonPullParser pullParser(&in, EYsonType::Node);
+ TYsonPullParserCursor cursor(&pullParser);
+
+ converter(&cursor, &writer);
+
+ EXPECT_EQ(cursor.GetCurrent().GetType(), EYsonItemType::EndOfStream);
+ writer.Finish();
+ }
+
+ auto result = HexEncode(out.Str());
+ result.to_lower();
+ return result;
+}
+
+TString ConvertHexToTextYson(
+ const TLogicalTypePtr& logicalType,
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ TStringBuf hexString,
+ const TSkiffToYsonConverterConfig& config = {})
+{
+ auto converter = CreateSkiffToYsonConverter(TComplexTypeFieldDescriptor("test-field", logicalType), skiffSchema, config);
+
+
+ TStringStream binaryOut;
+ {
+ TString binaryString = HexDecode(hexString);
+ TMemoryInput in(binaryString);
+ TCheckedInDebugSkiffParser parser(skiffSchema, &in);
+
+ auto writer = TCheckedInDebugYsonTokenWriter(&binaryOut);
+ converter(&parser, &writer);
+ EXPECT_EQ(parser.GetReadBytesCount(), binaryString.size());
+ }
+ binaryOut.Finish();
+
+ TStringStream out;
+ {
+ auto writer = TYsonWriter(&out, EYsonFormat::Text);
+ ParseYsonStringBuffer(binaryOut.Str(), EYsonType::Node, &writer);
+ }
+ out.Finish();
+
+ return out.Str();
+}
+
+
+#define CHECK_BIDIRECTIONAL_CONVERSION(logicalType, skiffSchema, ysonString, skiffString, ...) \
+ do { \
+ std::tuple<TYsonToSkiffConverterConfig,TSkiffToYsonConverterConfig> cfg = {__VA_ARGS__}; \
+ auto actualSkiffString = ConvertYsonHex(logicalType, skiffSchema, ysonString, std::get<0>(cfg)); \
+ EXPECT_EQ(actualSkiffString, skiffString) << "Yson -> Skiff conversion error"; \
+ auto actualYsonString = ConvertHexToTextYson(logicalType, skiffSchema, skiffString, std::get<1>(cfg)); \
+ EXPECT_EQ(actualYsonString, ysonString) << "Skiff -> Yson conversion error"; \
+ } while (0)
+
+
+TEST(TYsonSkiffConverterTest, TestSimpleTypes)
+{
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Int8(),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ "-42",
+ "d6ffffff" "ffffffff");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Uint64(),
+ CreateSimpleTypeSchema(EWireType::Uint64),
+ "42u",
+ "2a000000" "00000000");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Uint64(),
+ CreateSimpleTypeSchema(EWireType::Uint64),
+ "8u",
+ "08000000" "00000000");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Bool(),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ "%true",
+ "01");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Double(),
+ CreateSimpleTypeSchema(EWireType::Double),
+ "0.",
+ "00000000" "00000000");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Float(),
+ CreateSimpleTypeSchema(EWireType::Double),
+ "0.",
+ "00000000" "00000000");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ String(),
+ CreateSimpleTypeSchema(EWireType::String32),
+ "\"foo\"",
+ "03000000" "666f6f");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Null(),
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ "#",
+ "");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Uuid(),
+ CreateSimpleTypeSchema(EWireType::Uint128),
+ "\"\\xF0\\xF1\\xF2\\xF3\\xF4\\xF5\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF\"",
+ "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Uuid(),
+ CreateSimpleTypeSchema(EWireType::String32),
+ "\"\\xF0\\xF1\\xF2\\xF3\\xF4\\xF5\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF\"",
+ "10000000f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+}
+
+TEST(TYsonSkiffConverterTest, TestYson32)
+{
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Yson(),
+ CreateSimpleTypeSchema(EWireType::Yson32),
+ "-42",
+ "02000000" "0253");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Yson(),
+ CreateSimpleTypeSchema(EWireType::Yson32),
+ "#",
+ "01000000" "23");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Yson(),
+ CreateSimpleTypeSchema(EWireType::Yson32),
+ "[1;2;[3;];]",
+ "0e000000" "5b02023b02043b5b02063b5d3b5d");
+}
+
+TEST(TYsonSkiffConverterTest, TestOptionalTypes)
+{
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Int64()),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64)),
+ "-42",
+ "01" "d6ffffff" "ffffffff");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Int64()),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64)),
+ "#",
+ "00");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Optional(Bool())),
+ SkiffOptional(SkiffOptional(CreateSimpleTypeSchema(EWireType::Boolean))),
+ "[%true;]",
+ "01" "01" "01");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Optional(Bool())),
+ SkiffOptional(SkiffOptional(CreateSimpleTypeSchema(EWireType::Boolean))),
+ "[#;]",
+ "01" "00");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Optional(Bool())),
+ SkiffOptional(SkiffOptional(CreateSimpleTypeSchema(EWireType::Boolean))),
+ "#",
+ "00");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(List(Bool())),
+ SkiffOptional(CreateRepeatedVariant8Schema({CreateSimpleTypeSchema(EWireType::Boolean)})),
+ "#",
+ "00");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Optional(List(Bool()))),
+ SkiffOptional(
+ SkiffOptional(
+ CreateRepeatedVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Boolean)
+ })
+ )
+ ),
+ "[[%true;%false;%true;];]",
+ "01" "01" "0001" "0000" "0001" "ff");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Optional(List(Bool()))),
+ SkiffOptional(
+ SkiffOptional(
+ CreateRepeatedVariant8Schema({
+ CreateSimpleTypeSchema(EWireType::Boolean)
+ })
+ )
+ ),
+ "[#;]",
+ "0100");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ConvertYsonHex(
+ Optional(Optional(Bool())),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Boolean)),
+ " [ %true ] "),
+ "Optional nesting mismatch");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ConvertHexToTextYson(
+ Optional(Bool()),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ "00"),
+ "Optional nesting mismatch");
+
+ TYsonToSkiffConverterConfig ysonToSkiffConfig;
+ ysonToSkiffConfig.AllowOmitTopLevelOptional = true;
+
+ TSkiffToYsonConverterConfig skiffToYsonConfig;
+ skiffToYsonConfig.AllowOmitTopLevelOptional = true;
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Optional(Bool())),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Boolean)),
+ "[%true;]",
+ "01" "01",
+ ysonToSkiffConfig,
+ skiffToYsonConfig);
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Optional(Optional(Bool())),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Boolean)),
+ "[#;]",
+ "00",
+ ysonToSkiffConfig,
+ skiffToYsonConfig);
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ConvertYsonHex(
+ Optional(Optional(Bool())),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Boolean)),
+ " # ",
+ ysonToSkiffConfig),
+ "value expected to be nonempty");
+}
+
+TEST(TYsonSkiffConverterTest, TestListTypes)
+{
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ List(Bool()),
+ CreateRepeatedVariant8Schema({CreateSimpleTypeSchema(EWireType::Boolean)}),
+ "[]",
+ "ff");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ List(Bool()),
+ CreateRepeatedVariant8Schema({CreateSimpleTypeSchema(EWireType::Boolean)}),
+ "[%true;%true;%true;]",
+ "00" "01" "00" "01" "00" "01" "ff");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ List(List(Bool())),
+ CreateRepeatedVariant8Schema({CreateRepeatedVariant8Schema({CreateSimpleTypeSchema(EWireType::Boolean)})}),
+ "[[];[%true;];[%true;%true;];]",
+ "00" "ff" "00" "0001ff" "00" "00010001ff" "ff");
+}
+
+TEST(TYsonSkiffConverterTest, TestStruct)
+{
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Struct(
+ "key", String(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("value"),
+ }),
+ "[\"true\";%true;]",
+ "04000000" "74727565" "01");
+}
+
+TEST(TYsonSkiffConverterTest, TestSkippedFields)
+{
+ TString skiffString;
+ skiffString = ConvertYsonHex(
+ Struct(
+ "key", String(),
+ "subkey", Int64(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("value"),
+ }),
+ " [ true ; 1; %true ] ");
+ EXPECT_EQ(skiffString, "04000000" "74727565" "01"sv);
+
+ skiffString = ConvertYsonHex(
+ Struct(
+ "key", String(),
+ "subkey", Int64(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("subkey"),
+ }),
+ " [ true ; 1; %true ] ");
+ EXPECT_EQ(skiffString, "01000000" "00000000"sv);
+
+ try {
+ ConvertHexToTextYson(
+ Struct(
+ "key", String(),
+ "subkey", Int64(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("subkey"),
+ }),
+ "01000000" "00000000");
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::ContainsRegex("Non optional struct field .* is missing"));
+ }
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Struct(
+ "key", Optional(String()),
+ "subkey", Int64(),
+ "value", Optional(Bool())
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64)->SetName("subkey"),
+ }),
+ "[#;15;#;]",
+ "0f000000" "00000000");
+}
+
+TEST(TYsonSkiffConverterTest, TestUnknownSkiffFields)
+{
+ TString skiffString;
+ skiffString = ConvertYsonHex(
+ Struct(
+ "key", String(),
+ "subkey", Int64(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::String32))->SetName("key2"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("value"),
+ }),
+ " [ true ; 1; %true ] ");
+ EXPECT_EQ(skiffString, "04000000" "74727565" "00" "01"sv);
+
+ skiffString = ConvertYsonHex(
+ Struct(
+ "key", String(),
+ "subkey", Int64(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("value"),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Yson32))->SetName("value2"),
+ }),
+ " [ true ; 1; %true ] ");
+ EXPECT_EQ(skiffString, "04000000" "74727565" "01" "00"sv);
+
+
+ try {
+ ConvertYsonHex(
+ Struct(
+ "key", String(),
+ "subkey", Int64(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("value"),
+ CreateSimpleTypeSchema(EWireType::Yson32)->SetName("value2"),
+ }),
+ " [ true ; 1; %true ] ");
+ GTEST_FAIL() << "exception expected";
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::ContainsRegex("Non optional Skiff field .* is missing corresponding logical struct field"));
+ }
+
+ try {
+ ConvertHexToTextYson(
+ Struct(
+ "key", String(),
+ "subkey", Int64(),
+ "value", Bool()
+ ),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32)->SetName("key"),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::String32))->SetName("key2"),
+ CreateSimpleTypeSchema(EWireType::Boolean)->SetName("value"),
+ }),
+ "04000000" "74727565" "00" "01"sv);
+ GTEST_FAIL() << "expected_exception";
+ } catch (const std::exception& e) {
+ EXPECT_THAT(e.what(), testing::ContainsRegex("is not found in logical type"));
+ }
+}
+
+TEST(TYsonSkiffConverterTest, TestTuple)
+{
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Tuple(String(), Bool()),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ }),
+ "[\"true\";%true;]",
+ "04000000" "74727565" "01");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ Tuple(Int64(), Optional(Int64())),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Int64),
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64)),
+ }),
+ "[2;42;]",
+ "02000000" "00000000" "01" "2a000000" "00000000");
+}
+
+TEST(TYsonSkiffConverterTest, TestTupleSkippedFields)
+{
+ TString skiffString;
+ skiffString = ConvertYsonHex(
+ Tuple(String(), Int64(), Bool()),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32),
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ }),
+ " [ true ; 1; %true ] ");
+ EXPECT_EQ(skiffString, "04000000" "74727565" "01"sv);
+
+ skiffString = ConvertYsonHex(
+ Tuple(String(), Int64(), Bool()),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ }),
+ " [ true ; 1; %true ] ");
+ EXPECT_EQ(skiffString, "01000000" "00000000"sv);
+
+ skiffString = ConvertYsonHex(
+ Tuple(Optional(String()), Int64(), Optional(Bool())),
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::Nothing),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ CreateSimpleTypeSchema(EWireType::Nothing)
+ }),
+ "[#;15;#;]"
+ );
+ EXPECT_EQ(skiffString, "0f000000" "00000000"sv);
+}
+
+TEST(TYsonSkiffConverterTest, TestDict)
+{
+ const auto logicalType = Dict(String(), Int64());
+ const auto skiffSchema = CreateRepeatedVariant8Schema({
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32),
+ CreateSimpleTypeSchema(EWireType::Int64)
+ })
+ });
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ logicalType,
+ skiffSchema,
+ "[[\"one\";1;];[\"two\";2;];]",
+ "00" "03000000" "6f6e65" "01000000" "00000000"
+ "00" "03000000" "74776f" "02000000" "00000000"
+ "ff"
+ );
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ConvertHexToTextYson(logicalType, skiffSchema, "01" "01000000" "6f" "01000000" "00000000" "ff"),
+ "Unexpected repeated_variant8 tag"
+ );
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ConvertHexToTextYson(logicalType, skiffSchema, "00" "01000000" "6f" "01000000" "00000000"),
+ "Premature end of stream"
+ );
+}
+
+TEST(TYsonSkiffConverterTest, TestTagged)
+{
+ const auto logicalType = Tagged(
+ "tag",
+ Dict(Tagged("tag", String()), Int64()));
+ const auto skiffSchema = CreateRepeatedVariant8Schema({
+ CreateTupleSchema({
+ CreateSimpleTypeSchema(EWireType::String32),
+ CreateSimpleTypeSchema(EWireType::Int64)
+ })
+ });
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ logicalType,
+ skiffSchema,
+ "[[\"one\";1;];[\"two\";2;];]",
+ "00" "03000000" "6f6e65" "01000000" "00000000"
+ "00" "03000000" "74776f" "02000000" "00000000"
+ "ff"
+ );
+}
+
+TEST(TYsonSkiffConverterTest, TestOptionalVariantSimilarity)
+{
+ auto logicalType = Optional(
+ VariantTuple(Null(), Int64())
+ );
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ logicalType,
+ SkiffOptional(SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64))),
+ "[1;42;]",
+ "01" "01" "2a000000" "00000000");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ logicalType,
+ SkiffOptional(SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64))),
+ "[0;#;]",
+ "01" "00");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ logicalType,
+ SkiffOptional(SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64))),
+ "#",
+ "00");
+
+ TYsonToSkiffConverterConfig ysonToSkiffConfig;
+ ysonToSkiffConfig.AllowOmitTopLevelOptional = true;
+
+ TSkiffToYsonConverterConfig skiffToYsonConfig;
+ skiffToYsonConfig.AllowOmitTopLevelOptional = true;
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ logicalType,
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64)),
+ "[1;42;]",
+ "01" "2a000000" "00000000",
+ ysonToSkiffConfig,
+ skiffToYsonConfig);
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ logicalType,
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64)),
+ "[0;#;]",
+ "00",
+ ysonToSkiffConfig,
+ skiffToYsonConfig);
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ ConvertYsonHex(
+ logicalType,
+ SkiffOptional(CreateSimpleTypeSchema(EWireType::Int64)),
+ "#",
+ ysonToSkiffConfig),
+ "value expected to be nonempty"
+ );
+}
+
+class TYsonSkiffConverterTestVariant
+ : public ::testing::TestWithParam<std::tuple<ELogicalMetatype, EWireType>>
+{
+public:
+ TLogicalTypePtr VariantLogicalType(const std::vector<TLogicalTypePtr>& elements)
+ {
+ auto [metatype, wireType] = GetParam();
+ if (metatype == ELogicalMetatype::VariantTuple) {
+ return VariantTupleLogicalType(elements);
+ } else {
+ std::vector<TStructField> fields;
+ for (size_t i = 0; i < elements.size(); ++i) {
+ fields.push_back({Format("field%v", i), elements[i]});
+ }
+ return VariantStructLogicalType(fields);
+ }
+ }
+
+ std::shared_ptr<TSkiffSchema> VariantSkiffSchema(std::vector<std::shared_ptr<TSkiffSchema>> elements)
+ {
+ for (size_t i = 0; i < elements.size(); ++i) {
+ elements[i]->SetName(Format("field%v", i));
+ }
+ auto [metatype, wireType] = GetParam();
+ if (wireType == EWireType::Variant8) {
+ return CreateVariant8Schema(std::move(elements));
+ } else if (wireType == EWireType::Variant16) {
+ return CreateVariant16Schema(std::move(elements));
+ }
+ Y_UNREACHABLE();
+ }
+
+ TString VariantTagInfix() const
+ {
+ auto [metatype, wireType] = GetParam();
+ if (wireType == EWireType::Variant16) {
+ return "00";
+ }
+ return {};
+ }
+};
+
+TEST_P(TYsonSkiffConverterTestVariant, TestVariant)
+{
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ VariantLogicalType({
+ Int64(),
+ Bool()
+ }),
+ VariantSkiffSchema({
+ CreateSimpleTypeSchema(EWireType::Int64),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ }),
+ "[0;42;]",
+ "00" + VariantTagInfix() + "2a000000" "00000000");
+
+ CHECK_BIDIRECTIONAL_CONVERSION(
+ VariantLogicalType({
+ Int64(),
+ Bool()
+ }),
+ VariantSkiffSchema({
+ CreateSimpleTypeSchema(EWireType::Int64),
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ }),
+ "[1;%true;]",
+ "01" + VariantTagInfix() + "01");
+}
+
+TEST_P(TYsonSkiffConverterTestVariant, TestMalformedVariants)
+{
+ auto logicalType = VariantLogicalType({
+ Bool(),
+ Int64(),
+ });
+ auto skiffSchema = VariantSkiffSchema({
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ CreateSimpleTypeSchema(EWireType::Int64),
+ });
+
+ EXPECT_THROW_WITH_SUBSTRING(ConvertYsonHex(logicalType, skiffSchema, "[2; 42]"), "Yson to Skiff conversion error");
+ EXPECT_THROW_WITH_SUBSTRING(ConvertYsonHex(logicalType, skiffSchema, "[]"), "Yson to Skiff conversion error");
+ EXPECT_THROW_WITH_SUBSTRING(ConvertYsonHex(logicalType, skiffSchema, "[0]"), "Yson to Skiff conversion error");
+
+ EXPECT_THROW_WITH_SUBSTRING(ConvertHexToTextYson(logicalType, skiffSchema, "02" + VariantTagInfix() + "00"),
+ "Skiff to Yson conversion error");
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Variants,
+ TYsonSkiffConverterTestVariant,
+ ::testing::Combine(
+ ::testing::ValuesIn({ELogicalMetatype::VariantStruct, ELogicalMetatype::VariantTuple}),
+ ::testing::ValuesIn({EWireType::Variant8, EWireType::Variant16})
+ )
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/table_consumer_ut.cpp b/yt/yt/client/unittests/table_consumer_ut.cpp
new file mode 100644
index 0000000000..4a345861d9
--- /dev/null
+++ b/yt/yt/client/unittests/table_consumer_ut.cpp
@@ -0,0 +1,131 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/unittests/mock/table_value_consumer.h>
+
+#include <yt/yt/client/table_client/schema.h>
+#include <yt/yt/client/table_client/table_consumer.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/yson/parser.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+using ::testing::InSequence;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NTableClient;
+using namespace NFormats;
+using namespace NComplexTypes;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TEmptyValueConsumer
+ : public IValueConsumer
+{
+ const TNameTablePtr& GetNameTable() const override
+ {
+ return NameTable;
+ }
+
+ const TTableSchemaPtr& GetSchema() const override
+ {
+ return Schema_;
+ }
+
+ bool GetAllowUnknownColumns() const override
+ {
+ return true;
+ }
+
+ void OnBeginRow() override
+ { }
+
+ void OnValue(const TUnversionedValue& /*value*/) override
+ { }
+
+ void OnEndRow() override
+ { }
+
+private:
+ const TTableSchemaPtr Schema_ = New<TTableSchema>();
+ const TNameTablePtr NameTable = New<TNameTable>();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TTableConsumer, EntityAsNull)
+{
+ StrictMock<TMockValueConsumer> mock(New<TNameTable>(), true);
+ EXPECT_CALL(mock, OnBeginRow());
+ EXPECT_CALL(mock, OnMyValue(MakeUnversionedSentinelValue(EValueType::Null, 0)));
+ EXPECT_CALL(mock, OnEndRow());
+
+ TYsonConverterConfig config{
+ .ComplexTypeMode = EComplexTypeMode::Positional,
+ };
+ std::unique_ptr<IYsonConsumer> consumer(new TTableConsumer(config, &mock));
+ consumer->OnBeginMap();
+ consumer->OnKeyedItem("a");
+ consumer->OnEntity();
+ consumer->OnEndMap();
+}
+
+TEST(TTableConsumer, TopLevelAttributes)
+{
+ StrictMock<TMockValueConsumer> mock(New<TNameTable>(), true);
+ EXPECT_CALL(mock, OnBeginRow());
+
+ TYsonConverterConfig config{
+ .ComplexTypeMode = EComplexTypeMode::Positional,
+ };
+ std::unique_ptr<IYsonConsumer> consumer(new TTableConsumer(config, &mock));
+ consumer->OnBeginMap();
+ consumer->OnKeyedItem("a");
+ EXPECT_THROW(consumer->OnBeginAttributes(), std::exception);
+}
+
+TEST(TTableConsumer, RowAttributes)
+{
+ StrictMock<TMockValueConsumer> mock(New<TNameTable>(), true);
+
+ TYsonConverterConfig config{
+ .ComplexTypeMode = EComplexTypeMode::Positional,
+ };
+ std::unique_ptr<IYsonConsumer> consumer(new TTableConsumer(config, &mock));
+ consumer->OnBeginAttributes();
+ consumer->OnKeyedItem("table_index");
+ consumer->OnInt64Scalar(0);
+ consumer->OnEndAttributes();
+ EXPECT_THROW(consumer->OnBeginMap(), std::exception);
+}
+
+TEST(TYsonParserTest, ContextInExceptions_TableConsumer)
+{
+ try {
+ TEmptyValueConsumer emptyValueConsumer;
+
+ TYsonConverterConfig config{
+ .ComplexTypeMode = EComplexTypeMode::Positional,
+ };
+ TTableConsumer consumer(config, &emptyValueConsumer);
+ TYsonParser parser(&consumer, EYsonType::ListFragment);
+ parser.Read("{foo=bar};");
+ parser.Read("{bar=baz};YT_LOG_IN");
+ parser.Read("FO something happened");
+ parser.Finish();
+ GTEST_FAIL() << "Expected exception to be thrown";
+ } catch (const std::exception& ex) {
+ EXPECT_THAT(ex.what(), testing::HasSubstr("YT_LOG_INFO something happened"));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NVersionedTableClient
+
diff --git a/yt/yt/client/unittests/time_text_ut.cpp b/yt/yt/client/unittests/time_text_ut.cpp
new file mode 100644
index 0000000000..a8b5d1a10a
--- /dev/null
+++ b/yt/yt/client/unittests/time_text_ut.cpp
@@ -0,0 +1,67 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/complex_types/time_text.h>
+
+namespace NYT::NComplexTypes {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TimeConverter, Correct)
+{
+ EXPECT_EQ(BinaryTimeFromText("2021-09-03", ESimpleLogicalValueType::Date), 18873U);
+
+ EXPECT_EQ(BinaryTimeFromText("2021-09-03T10:12:41Z", ESimpleLogicalValueType::Datetime), 1630663961U);
+
+ EXPECT_EQ(BinaryTimeFromText("2021-09-03T10:12:41Z", ESimpleLogicalValueType::Timestamp), 1630663961000000ULL);
+ EXPECT_EQ(BinaryTimeFromText("2021-09-03T10:12:41.1Z", ESimpleLogicalValueType::Timestamp), 1630663961100000ULL);
+ EXPECT_EQ(BinaryTimeFromText("2021-09-03T10:12:41.123456Z", ESimpleLogicalValueType::Timestamp), 1630663961123456ULL);
+}
+
+TEST(TimeConverter, InvalidLength)
+{
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09", ESimpleLogicalValueType::Date),
+ "Invalid date string length");
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03T", ESimpleLogicalValueType::Date),
+ "Invalid date string length");
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03T10:12:41Z", ESimpleLogicalValueType::Date),
+ "Invalid date string length");
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03T10:12:41.1Z", ESimpleLogicalValueType::Date),
+ "Invalid date string length");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03", ESimpleLogicalValueType::Datetime),
+ "Invalid date string length");
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03T10:12:41", ESimpleLogicalValueType::Datetime),
+ "Invalid date string length");
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03T10:12:41.1Z", ESimpleLogicalValueType::Datetime),
+ "Invalid date string length");
+
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03", ESimpleLogicalValueType::Timestamp),
+ "Invalid date string length");
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03T10:12:41.1234567Z", ESimpleLogicalValueType::Timestamp),
+ "Invalid date string length");
+}
+
+TEST(TimeConverter, InvalidFormat)
+{
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-3T", ESimpleLogicalValueType::Date),
+ "Could not parse date");
+ EXPECT_THROW_WITH_SUBSTRING(
+ BinaryTimeFromText("2021-09-03T23:59:59H", ESimpleLogicalValueType::Datetime),
+ "Could not parse date");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/unittests/unordered_reader_ut.cpp b/yt/yt/client/unittests/unordered_reader_ut.cpp
new file mode 100644
index 0000000000..43a5e74933
--- /dev/null
+++ b/yt/yt/client/unittests/unordered_reader_ut.cpp
@@ -0,0 +1,104 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/unordered_schemaful_reader.h>
+#include <yt/yt/client/table_client/unversioned_reader.h>
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/row_batch.h>
+
+#include <yt/yt/core/actions/cancelable_context.h>
+
+#include <yt/yt/core/actions/future.h>
+
+namespace NYT {
+namespace {
+
+using namespace NConcurrency;
+using namespace NTableClient;
+using namespace NChunkClient;
+using namespace NChunkClient::NProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUnorderedReaderTest
+ : public ::testing::Test
+{ };
+
+class TSchemafulReaderMock
+ : public ISchemafulUnversionedReader
+{
+public:
+ IUnversionedRowBatchPtr Read(const TRowBatchReadOptions& /*options*/ = {}) override
+ {
+ return ReadyEvent_.IsSet() ? nullptr : CreateEmptyUnversionedRowBatch();
+ }
+
+ TFuture<void> GetReadyEvent() const override
+ {
+ return ReadyEvent_;
+ }
+
+ void SetReadyEvent(const TError& error)
+ {
+ ReadyEvent_.Set(error);
+ }
+
+ TDataStatistics GetDataStatistics() const override
+ {
+ return {};
+ }
+
+ NChunkClient::TCodecStatistics GetDecompressionStatistics() const override
+ {
+ return {};
+ }
+
+ bool IsFetchingCompleted() const override
+ {
+ return false;
+ }
+
+ std::vector<TChunkId> GetFailedChunkIds() const override
+ {
+ return {};
+ }
+
+private:
+ const TPromise<void> ReadyEvent_ = NewPromise<void>();
+};
+
+TEST_F(TUnorderedReaderTest, Simple)
+{
+ auto reader1 = New<TSchemafulReaderMock>();
+ auto reader2 = New<TSchemafulReaderMock>();
+
+ auto subqueryReaderCreator = [&, index = 0] () mutable -> ISchemafulUnversionedReaderPtr {
+ if (index == 0) {
+ ++index;
+ return reader1;
+ } else if (index == 1) {
+ ++index;
+ return reader2;
+ } else {
+ return nullptr;
+ }
+ };
+
+ auto mergingReader = CreateUnorderedSchemafulReader(subqueryReaderCreator, 2);
+
+ EXPECT_TRUE(mergingReader->Read().operator bool());
+
+ reader1->SetReadyEvent(TError());
+ reader2->SetReadyEvent(TError("Error"));
+
+ EXPECT_TRUE(mergingReader->GetReadyEvent().IsSet());
+ EXPECT_TRUE(mergingReader->GetReadyEvent().Get().IsOK());
+
+ EXPECT_TRUE(mergingReader->Read().operator bool());
+ EXPECT_TRUE(mergingReader->GetReadyEvent().IsSet());
+ EXPECT_EQ("Error", mergingReader->GetReadyEvent().Get().GetMessage());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/yt/yt/client/unittests/unversioned_row_ut.cpp b/yt/yt/client/unittests/unversioned_row_ut.cpp
new file mode 100644
index 0000000000..1e32bd4f37
--- /dev/null
+++ b/yt/yt/client/unittests/unversioned_row_ut.cpp
@@ -0,0 +1,253 @@
+#include <yt/yt/client/table_client/helpers.h>
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/yson/token_writer.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/ypath_client.h>
+
+#include <util/stream/str.h>
+
+namespace NYT::NTableClient {
+namespace {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TUnversionedValue, TestConversionToYsonTokenWriter)
+{
+ auto convert = [] (TUnversionedValue value) {
+ TStringStream stream;
+ TCheckedInDebugYsonTokenWriter tokenWriter(&stream);
+ UnversionedValueToYson(value, &tokenWriter);
+ tokenWriter.Finish();
+ return stream.Str();
+ };
+
+ {
+ auto value = MakeUnversionedInt64Value(-42);
+ i64 parsed = 0;
+ EXPECT_NO_THROW(parsed = ConvertTo<i64>(TYsonString(convert(value))));
+ EXPECT_EQ(parsed, -42);
+ }
+ {
+ auto value = MakeUnversionedUint64Value(std::numeric_limits<ui64>::max());
+ ui64 parsed = 0;
+ EXPECT_NO_THROW(parsed = ConvertTo<ui64>(TYsonString(convert(value))));
+ EXPECT_EQ(parsed, std::numeric_limits<ui64>::max());
+ }
+ {
+ auto value = MakeUnversionedDoubleValue(2.718);
+ double parsed = 0.0;
+ EXPECT_NO_THROW(parsed = ConvertTo<double>(TYsonString(convert(value))));
+ EXPECT_DOUBLE_EQ(parsed, 2.718);
+ }
+ {
+ auto value = MakeUnversionedStringValue("boo");
+ TString parsed;
+ EXPECT_NO_THROW(parsed = ConvertTo<TString>(TYsonString(convert(value))));
+ EXPECT_EQ(parsed, "boo");
+ }
+ {
+ auto value = MakeUnversionedNullValue();
+ TString str;
+ EXPECT_NO_THROW(str = convert(value));
+ EXPECT_EQ(str, "#");
+ }
+ {
+ auto value = MakeUnversionedAnyValue("{x=y;z=<a=b>2}");
+ INodePtr parsed;
+ EXPECT_NO_THROW(parsed = ConvertTo<INodePtr>(TYsonString(convert(value))));
+ auto expected = BuildYsonNodeFluently()
+ .BeginMap()
+ .Item("x").Value("y")
+ .Item("z")
+ .BeginAttributes()
+ .Item("a").Value("b")
+ .EndAttributes()
+ .Value(2)
+ .EndMap();
+ EXPECT_TRUE(AreNodesEqual(parsed, expected))
+ << "parsed: " << ConvertToYsonString(parsed, EYsonFormat::Pretty).AsStringBuf()
+ << "\nexpected: " << ConvertToYsonString(expected, EYsonFormat::Pretty).AsStringBuf();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Some sanity tests.
+static_assert(TUnversionedValueConversionTraits<i64>::Inline, "i64 must be inline.");
+static_assert(TUnversionedValueConversionTraits<i64>::Scalar, "i64 must be scalar.");
+static_assert(TUnversionedValueConversionTraits<std::optional<i64>>::Inline, "i64? must be inline.");
+static_assert(TUnversionedValueConversionTraits<std::optional<i64>>::Scalar, "i64? must be scalar.");
+static_assert(!TUnversionedValueConversionTraits<TString>::Inline, "TString must not be inline.");
+static_assert(TUnversionedValueConversionTraits<TString>::Scalar, "TString must be scalar.");
+static_assert(TUnversionedValueConversionTraits<TAnnotatedValue<i64>>::Scalar, "i64 must be scalar.");
+YT_DEFINE_STRONG_TYPEDEF(TStrongInt, i64)
+static_assert(TUnversionedValueConversionTraits<TStrongInt>::Scalar, "TStrongInt must be scalar.");
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TMakeUnversionedOwningRow, Empty)
+{
+ auto row = MakeUnversionedOwningRow();
+ EXPECT_EQ(0, row.GetCount());
+}
+
+template <class T>
+void CheckSingleValue(T value)
+{
+ auto row = MakeUnversionedOwningRow(value);
+ EXPECT_EQ(1, row.GetCount());
+ EXPECT_EQ(0, row[0].Id);
+ EXPECT_EQ(value, FromUnversionedValue<T>(row[0]));
+}
+
+TEST(TMakeUnversionedOwningRow, SingleValue)
+{
+ CheckSingleValue(TGuid::Create());
+ CheckSingleValue(TString("hello"));
+ CheckSingleValue(TStringBuf("hello"));
+ CheckSingleValue(true);
+ CheckSingleValue(TYsonString(TStringBuf("{a=1}")));
+ CheckSingleValue(static_cast<i64>(-123));
+ CheckSingleValue(static_cast<ui64>(123));
+ CheckSingleValue(static_cast<i32>(-17));
+ CheckSingleValue(static_cast<ui32>(17));
+ CheckSingleValue(static_cast<i16>(-2342));
+ CheckSingleValue(static_cast<ui16>(2342));
+ CheckSingleValue(static_cast<i8>(-12));
+ CheckSingleValue(static_cast<ui8>(12));
+ CheckSingleValue(static_cast<double>(3.14));
+ CheckSingleValue(TInstant::Now());
+ CheckSingleValue(TDuration::Seconds(10));
+ CheckSingleValue(TStrongInt(123));
+}
+
+TEST(TMakeUnversionedOwningRow, CharPtr)
+{
+ auto row = MakeUnversionedOwningRow("test");
+ EXPECT_EQ(1, row.GetCount());
+ EXPECT_EQ(0, row[0].Id);
+ EXPECT_EQ("test", FromUnversionedValue<TString>(row[0]));
+}
+
+TEST(TMakeUnversionedOwningRow, NullValue)
+{
+ auto row = MakeUnversionedOwningRow(std::nullopt);
+ EXPECT_EQ(1, row.GetCount());
+ EXPECT_EQ(0, row[0].Id);
+ EXPECT_EQ(EValueType::Null, row[0].Type);
+}
+
+TEST(TMakeUnversionedOwningRow, Tuple)
+{
+ auto row = MakeUnversionedOwningRow(TString("hello"), true);
+ EXPECT_EQ(2, row.GetCount());
+ EXPECT_EQ(0, row[0].Id);
+ EXPECT_EQ("hello", FromUnversionedValue<TString>(row[0]));
+ EXPECT_EQ(1, row[1].Id);
+ EXPECT_EQ(true, FromUnversionedValue<bool>(row[1]));
+}
+
+TEST(TMakeUnversionedOwningRow, FromUnversionedRow)
+{
+ auto row = MakeUnversionedOwningRow(TString("hello"), TStringBuf("world"), 123);
+ TString a;
+ TStringBuf b;
+ i16 c;
+ FromUnversionedRow(row, &a, &b, &c);
+ EXPECT_EQ("hello", a);
+ EXPECT_EQ("world", b);
+ EXPECT_EQ(123, c);
+}
+
+TEST(TMakeUnversionedOwningRow, TupleFromUnversionedRow)
+{
+ auto row = MakeUnversionedOwningRow(TString("hello"), TStringBuf("world"), 123);
+ auto [a, b, c] = FromUnversionedRow<TString, TStringBuf, i16>(row);
+ EXPECT_EQ("hello", a);
+ EXPECT_EQ("world", b);
+ EXPECT_EQ(123, c);
+}
+
+TEST(TMakeUnversionedOwningRow, ExplicitIds)
+{
+ auto row = MakeUnversionedOwningRow(
+ TAnnotatedValue{TString("hello"), 10},
+ TAnnotatedValue{TStringBuf("world"), 20});
+ EXPECT_EQ(2, row.GetCount());
+ EXPECT_EQ(10, row[0].Id);
+ EXPECT_EQ(20, row[1].Id);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TUnversionedRowsBuilder, Empty)
+{
+ TUnversionedRowsBuilder builder;
+ auto rows = builder.Build();
+ EXPECT_EQ(0, std::ssize(rows));
+}
+
+TEST(TUnversionedRowsBuilder, SomeValues)
+{
+ TUnversionedRowsBuilder builder;
+ builder.AddRow(1, "hello");
+ builder.AddRow(2, "world");
+ auto rows = builder.Build();
+ EXPECT_EQ(2, std::ssize(rows));
+ {
+ auto [i, s] = FromUnversionedRow<int, TString>(rows[0]);
+ EXPECT_EQ(1, i);
+ EXPECT_EQ("hello", s);
+ EXPECT_EQ(0, rows[0][0].Id);
+ EXPECT_EQ(1, rows[0][1].Id);
+ }
+ {
+ auto [i, s] = FromUnversionedRow<int, TString>(rows[1]);
+ EXPECT_EQ(2, i);
+ EXPECT_EQ("world", s);
+ EXPECT_EQ(0, rows[1][0].Id);
+ EXPECT_EQ(1, rows[1][1].Id);
+ }
+}
+
+TEST(TUnversionedRowsBuilder, AnnotatedValue)
+{
+ TUnversionedRowsBuilder builder;
+ builder.AddRow(TAnnotatedValue{1, 10}, TAnnotatedValue{"hello", 20});
+ builder.AddRow(TAnnotatedValue{2, 30}, TAnnotatedValue{"world", 40});
+ builder.AddRow(TAnnotatedValue{77, 1, EValueFlags::Aggregate});
+ auto rows = builder.Build();
+ EXPECT_EQ(3, std::ssize(rows));
+ {
+ auto [i, s] = FromUnversionedRow<int, TString>(rows[0]);
+ EXPECT_EQ(1, i);
+ EXPECT_EQ("hello", s);
+ EXPECT_EQ(10, rows[0][0].Id);
+ EXPECT_EQ(20, rows[0][1].Id);
+ }
+ {
+ auto [i, s] = FromUnversionedRow<int, TString>(rows[1]);
+ EXPECT_EQ(2, i);
+ EXPECT_EQ("world", s);
+ EXPECT_EQ(30, rows[1][0].Id);
+ EXPECT_EQ(40, rows[1][1].Id);
+ }
+ {
+ auto [i] = FromUnversionedRow<int>(rows[2]);
+ EXPECT_EQ(77, i);
+ EXPECT_EQ(1, rows[2][0].Id);
+ EXPECT_EQ(EValueFlags::Aggregate, rows[2][0].Flags);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/uuid_text_ut.cpp b/yt/yt/client/unittests/uuid_text_ut.cpp
new file mode 100644
index 0000000000..0a44ac498b
--- /dev/null
+++ b/yt/yt/client/unittests/uuid_text_ut.cpp
@@ -0,0 +1,66 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/complex_types/uuid_text.h>
+
+namespace NYT::NComplexTypes {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TestBidirectionalTextYqlUuidConversion(TStringBuf bytes, TStringBuf text)
+{
+ std::array<char, UuidYqlTextSize> bytesToText;
+ TextYqlUuidFromBytes(bytes, bytesToText.data());
+ EXPECT_EQ(TString(bytesToText.data(), bytesToText.size()), text);
+
+ std::array<char, UuidBinarySize> textToBytes;
+ TextYqlUuidToBytes(text, textToBytes.data());
+ EXPECT_EQ(TString(textToBytes.data(), textToBytes.size()), bytes);
+}
+
+TEST(TUuidConverterTest, TextYql)
+{
+ TestBidirectionalTextYqlUuidConversion(
+ TStringBuf("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", UuidBinarySize),
+ "00000000-0000-0000-0000-000000000000");
+
+ TestBidirectionalTextYqlUuidConversion(
+ "\x01\x10\x20\x30\x40\x50\x60\x70\x80\x90\xa0\xb0\xc0\xd0\xe0\xf0",
+ "30201001-5040-7060-8090-a0b0c0d0e0f0");
+}
+
+TEST(TUuidConverterTest, InvalidTextYql)
+{
+ std::array<char, UuidBinarySize> textToBytes;
+ EXPECT_THROW_WITH_SUBSTRING(
+ TextYqlUuidToBytes("00000000-0000-0000-000000-0000000000", textToBytes.data()),
+ "Unexpected character: actual \"0\", expected \"-\"");
+ EXPECT_THROW_WITH_SUBSTRING(
+ TextYqlUuidToBytes("00000000-0000-0000-0000-00-0000000000", textToBytes.data()),
+ "Invalid text YQL UUID length");
+ EXPECT_THROW_WITH_SUBSTRING(
+ TextYqlUuidToBytes("g0000000-0000-0000-0000-000000000000", textToBytes.data()),
+ "Could not parse hex byte");
+}
+
+TEST(TUuidConverterTest, Guid)
+{
+ TString bytes = "\x01\x10\x20\x30\x40\x50\x60\x70\x80\x90\xa0\xb0\xc0\xd0\xe0\xf0";
+ auto guid = GuidFromBytes(bytes);
+ std::array<char, UuidBinarySize> guidToBytes;
+ GuidToBytes(guid, guidToBytes.data());
+ EXPECT_EQ(
+ bytes,
+ TStringBuf(guidToBytes.data(), guidToBytes.size()));
+
+
+ EXPECT_EQ(guid.Parts32[0], 0xc0d0e0f0U);
+ EXPECT_EQ(guid.Parts32[1], 0x8090a0b0U);
+ EXPECT_EQ(guid.Parts32[2], 0x40506070U);
+ EXPECT_EQ(guid.Parts32[3], 0x01102030U);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NComplexTypes
diff --git a/yt/yt/client/unittests/validate_logical_type_ut.cpp b/yt/yt/client/unittests/validate_logical_type_ut.cpp
new file mode 100644
index 0000000000..1bebe1c952
--- /dev/null
+++ b/yt/yt/client/unittests/validate_logical_type_ut.cpp
@@ -0,0 +1,467 @@
+#include "logical_type_shortcuts.h"
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/validate_logical_type.h>
+
+#include <util/string/escape.h>
+
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define ERROR_ATTRS(logicalType, ysonString) \
+ " at " << __FILE__ ":" << __LINE__ << '\n' \
+ << "Type: " << ToString(*logicalType) << '\n' \
+ << "YsonString: " << '"' << EscapeC(ysonString) << '"' << '\n'\
+
+#define EXPECT_BAD_TYPE(logicalTypeExpr, ysonString) \
+ do { \
+ auto logicalType = logicalTypeExpr; \
+ try { \
+ ValidateComplexLogicalType(ysonString, logicalType); \
+ ADD_FAILURE() << "Expected type failure" << ERROR_ATTRS(logicalType, ysonString); \
+ } catch (const std::exception& ex) { \
+ if (!IsSchemaViolationError(ex)) { \
+ ADD_FAILURE() << "Unexpected error" << ERROR_ATTRS(logicalType, ysonString) \
+ << "what: " << ex.what(); \
+ } \
+ } \
+ } while (0)
+
+#define EXPECT_GOOD_TYPE(logicalTypeExpr, ysonString) \
+ do { \
+ auto logicalType = logicalTypeExpr; \
+ try { \
+ ValidateComplexLogicalType(ysonString, logicalType); \
+ } catch (const std::exception& ex) { \
+ ADD_FAILURE() << "Unexpected error" << ERROR_ATTRS(logicalType, ysonString) \
+ << "what: " << ex.what(); \
+ } \
+ } while (0)
+
+bool IsSchemaViolationError(const std::exception& ex) {
+ auto errorException = dynamic_cast<const TErrorException*>(&ex);
+ if (!errorException) {
+ return false;
+ }
+ return errorException->Error().FindMatching(NYT::NTableClient::EErrorCode::SchemaViolation).has_value();
+}
+
+// TODO (ermolovd): we should use functions from NLogicalTypeShortcuts here
+
+TEST(TValidateLogicalTypeTest, TestBasicTypes)
+{
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::String), " foo ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Int64), " 42 ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Uint64), " 42u ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Double), " 3.14 ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Float), " 3.14 ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Boolean), " %false ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Null), " # ");
+
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::String), " 76 ");
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Int64), " %true ");
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Uint64), " 14 ");
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Double), " bar ");
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Float), " rab ");
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Boolean), " 1 ");
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Null), " 0 ");
+}
+
+TEST(TValidateLogicalTypeTest, TestSimpleOptionalTypes)
+{
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)), " foo ");
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)), " # ");
+ EXPECT_BAD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)), " 42 ");
+
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint64)), " 42u ");
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint64)), " # ");
+ EXPECT_BAD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Uint64)), " 42 ");
+}
+
+TEST(TValidateLogicalTypeTest, TestOptionalNull)
+{
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null)), " # ");
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null)), " [#] ");
+
+ EXPECT_BAD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null)), " [] ");
+ EXPECT_BAD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null)), " [[#]] ");
+
+ EXPECT_GOOD_TYPE(OptionalLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null))), " [[#]] ");
+ EXPECT_GOOD_TYPE(OptionalLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null))), " [#] ");
+ EXPECT_GOOD_TYPE(OptionalLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null))), " # ");
+ EXPECT_BAD_TYPE(OptionalLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null))), " [[[#]]] ");
+ EXPECT_BAD_TYPE(OptionalLogicalType(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null))), " [[[]]] ");
+}
+
+TEST(TValidateLogicalTypeTest, TestLogicalTypes)
+{
+#define CHECK_GOOD(type, value) \
+ do { \
+ EXPECT_GOOD_TYPE(SimpleLogicalType(type), value); \
+ } while (0)
+
+#define CHECK_BAD(type, value) \
+ do { \
+ EXPECT_BAD_TYPE(SimpleLogicalType(type), value); \
+ } while (0)
+
+ // Int8
+ CHECK_GOOD(ESimpleLogicalValueType::Int8, " 127 ");
+ CHECK_GOOD(ESimpleLogicalValueType::Int8, " -128 ");
+ CHECK_BAD(ESimpleLogicalValueType::Int8, " 128 ");
+ CHECK_BAD(ESimpleLogicalValueType::Int8, " -129 ");
+
+ // Uint8
+ CHECK_GOOD(ESimpleLogicalValueType::Uint8, " 127u ");
+ CHECK_GOOD(ESimpleLogicalValueType::Uint8, " 128u ");
+ CHECK_GOOD(ESimpleLogicalValueType::Uint8, " 255u ");
+ CHECK_BAD(ESimpleLogicalValueType::Uint8, " 256u ");
+ CHECK_BAD(ESimpleLogicalValueType::Uint8, " 100500u ");
+
+ // Int16
+ CHECK_GOOD(ESimpleLogicalValueType::Int16, " 32767 ");
+ CHECK_GOOD(ESimpleLogicalValueType::Int16, " -32768 ");
+ CHECK_BAD(ESimpleLogicalValueType::Int16, " 32768 ");
+ CHECK_BAD(ESimpleLogicalValueType::Int16, " -32769 ");
+
+ // Uint16
+ CHECK_GOOD(ESimpleLogicalValueType::Uint16, " 32768u ");
+ CHECK_GOOD(ESimpleLogicalValueType::Uint16, " 65535u ");
+ CHECK_BAD(ESimpleLogicalValueType::Uint16, " 65536u ");
+
+ // Int32
+ CHECK_GOOD(ESimpleLogicalValueType::Int32, " 2147483647 ");
+ CHECK_GOOD(ESimpleLogicalValueType::Int32, " -2147483648 ");
+ CHECK_BAD(ESimpleLogicalValueType::Int32, " 2147483648 ");
+ CHECK_BAD(ESimpleLogicalValueType::Int32, " -2147483649 ");
+
+ // Uint32
+ CHECK_GOOD(ESimpleLogicalValueType::Uint32, " 2147483648u ");
+ CHECK_GOOD(ESimpleLogicalValueType::Uint32, " 4294967295u ");
+ CHECK_BAD(ESimpleLogicalValueType::Uint32, " 4294967297u ");
+
+ // Uint64
+ CHECK_GOOD(ESimpleLogicalValueType::Int64, " 2147483648 ");
+ CHECK_GOOD(ESimpleLogicalValueType::Int64, " -2147483649 ");
+
+ // Uint64
+ CHECK_GOOD(ESimpleLogicalValueType::Uint64, " 4294967297u ");
+
+ // Utf8
+ CHECK_GOOD(ESimpleLogicalValueType::Utf8, " foo ");
+ CHECK_GOOD(ESimpleLogicalValueType::Utf8, " \"фу\" ");
+ CHECK_BAD(ESimpleLogicalValueType::Utf8, " \"\244\" ");
+
+ // Float
+ CHECK_GOOD(ESimpleLogicalValueType::Float, " 3.14 ");
+ CHECK_GOOD(ESimpleLogicalValueType::Float, " -3.14e35 ");
+ CHECK_GOOD(ESimpleLogicalValueType::Float, " 3.14e35 ");
+ CHECK_BAD(ESimpleLogicalValueType::Float, " 3.14e39 ");
+ CHECK_BAD(ESimpleLogicalValueType::Float, " -3.14e39 ");
+ CHECK_BAD(ESimpleLogicalValueType::Float, " blah");
+}
+
+TEST(TValidateLogicalTypeTest, TestAnyType)
+{
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), " foo ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), " 42 ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), " 15u ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), " %true ");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), " 3.14 ");
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), "#");
+
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), "[142; 53u; {foo=bar; bar=[baz]};]");
+
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any)), "#");
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any)), "[{bar=<type=list>[]}; bar; [baz];]");
+
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), "<>1");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Any), "[<>1]");
+}
+
+
+TEST(TValidateLogicalTypeTest, TestJsonType)
+{
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "\"foo\"" )");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "42" )");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "true" )");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "3.14" )");
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "null" )");
+
+ // Infinities are disallowed.
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "Infinity" )");
+
+ EXPECT_GOOD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "[142, 53, {\"foo\":\"bar\", \"bar\":[\"baz\"]}]" )");
+ // Extra comma.
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "[142, 53,]" )");
+ // Wrong delimiter.
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "[142; 53]" )");
+
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Json)), "#");
+ EXPECT_GOOD_TYPE(OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Json)), R"( "[{\"bar\": []}, \"bar\", [\"baz\"]]" )");
+
+ // Non-UTF-8.
+ EXPECT_BAD_TYPE(SimpleLogicalType(ESimpleLogicalValueType::Json), R"( "\xFF" )");
+}
+
+TEST(TValidateLogicalTypeTest, TestDecimalType)
+{
+ using namespace NLogicalTypeShortcuts;
+
+ EXPECT_GOOD_TYPE(Decimal(3, 2), R"("\x80\x00\x01\x3a")"); // 314
+ EXPECT_BAD_TYPE(Decimal(3, 2), R"("\x80\x00\x11\x3a")");
+ EXPECT_BAD_TYPE(Decimal(3, 2), R"("\x80\x11\x3a")");
+ EXPECT_BAD_TYPE(Decimal(3, 2), R"("")");
+ EXPECT_BAD_TYPE(Decimal(3, 2), R"(3.14)");
+ EXPECT_BAD_TYPE(Decimal(3, 2), R"(3)");
+ EXPECT_BAD_TYPE(Decimal(3, 2), R"(#)");
+
+ EXPECT_GOOD_TYPE(Decimal(15, 0), R"("\x80\x03\x8d\x7e\xa4\xc6\x7f\xff")"); // 10 ** 15
+ EXPECT_BAD_TYPE(Decimal(15, 0), R"("\x80\x03\x8d\x7e\xa4\xc6\x80\x00")");
+ EXPECT_BAD_TYPE(Decimal(15, 0), R"("\x80\x00\x01\x3a")");
+ EXPECT_BAD_TYPE(Decimal(15, 0), R"("")");
+
+ EXPECT_GOOD_TYPE(Decimal(35, 0), R"("\x80\x13\x42\x61\x72\xc7\x4d\x82\x2b\x87\x8f\xe7\xff\xff\xff\xff")"); // 10 ** 35
+ EXPECT_BAD_TYPE(Decimal(35, 0), R"("\x80\x13\x42\x61\x72\xc7\x4d\x82\x2b\x87\x8f\xe8\x00\x00\x00\x00")");
+ EXPECT_BAD_TYPE(Decimal(35, 0), R"("\x80\x03\x8d\x7e\xa4\xc6\x7f\xff")");
+ EXPECT_BAD_TYPE(Decimal(35, 0), R"("\x80\x00\x01\x3a")");
+ EXPECT_BAD_TYPE(Decimal(35, 0), R"("")");
+}
+
+TEST(TValidateLogicalTypeTest, TestOptionalCompositeType)
+{
+ const auto optionalOptionalInt = OptionalLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))
+ );
+ EXPECT_GOOD_TYPE(optionalOptionalInt, " [5] ");
+ EXPECT_GOOD_TYPE(optionalOptionalInt, " [#] ");
+ EXPECT_GOOD_TYPE(optionalOptionalInt, " # ");
+ EXPECT_BAD_TYPE(optionalOptionalInt, " 5 ");
+ EXPECT_BAD_TYPE(optionalOptionalInt, " [] ");
+ EXPECT_BAD_TYPE(optionalOptionalInt, " [5; 5] ");
+
+ const auto optionalListInt = OptionalLogicalType(
+ ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ )
+ );
+ EXPECT_GOOD_TYPE(optionalListInt, "[5]");
+ EXPECT_GOOD_TYPE(optionalListInt, "[5; 5]");
+ EXPECT_GOOD_TYPE(optionalListInt, "[]");
+ EXPECT_BAD_TYPE(optionalListInt, "[[5]]");
+
+ const auto optionalOptionalListInt = OptionalLogicalType(
+ OptionalLogicalType(
+ ListLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ )
+ )
+ );
+ EXPECT_GOOD_TYPE(optionalOptionalListInt, "[[5]]");
+ EXPECT_GOOD_TYPE(optionalOptionalListInt, "[[5; 5]]");
+ EXPECT_GOOD_TYPE(optionalOptionalListInt, "[[]]");
+ EXPECT_BAD_TYPE(optionalOptionalListInt, "[[[5]]]");
+ EXPECT_BAD_TYPE(optionalOptionalListInt, "[5]");
+
+ const auto optionalOptionalOptionalAny = OptionalLogicalType(
+ OptionalLogicalType(
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Any))
+ )
+ );
+ EXPECT_GOOD_TYPE(optionalOptionalOptionalAny, " [[5]] ");
+ EXPECT_GOOD_TYPE(optionalOptionalOptionalAny, " [[#]] ");
+ EXPECT_GOOD_TYPE(optionalOptionalOptionalAny, " [#] ");
+ EXPECT_GOOD_TYPE(optionalOptionalOptionalAny, " # ");
+ EXPECT_GOOD_TYPE(optionalOptionalOptionalAny, " [[[]]] ");
+ EXPECT_GOOD_TYPE(optionalOptionalOptionalAny, " [[{foo=bar}]] ");
+ EXPECT_BAD_TYPE(optionalOptionalOptionalAny, " [] ");
+ EXPECT_BAD_TYPE(optionalOptionalOptionalAny, " [[]] ");
+}
+
+TEST(TValidateLogicalTypeTest, TestListType)
+{
+ const auto listInt = ListLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64));
+ EXPECT_GOOD_TYPE(listInt, " [3] ");
+ EXPECT_GOOD_TYPE(listInt, " [5] ");
+ EXPECT_GOOD_TYPE(listInt, " [5;42;] ");
+
+ EXPECT_BAD_TYPE(listInt, " [5;#;] ");
+ EXPECT_BAD_TYPE(listInt, " {} ");
+}
+
+TEST(TValidateLogicalTypeTest, TestStructType)
+{
+ const auto struct1 = StructLogicalType({
+ {"number", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"english", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"russian", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ });
+
+ EXPECT_GOOD_TYPE(struct1, " [3; three; TRI ] ");
+ EXPECT_GOOD_TYPE(struct1, " [1; one; # ] ");
+ EXPECT_GOOD_TYPE(struct1, " [1; one ] ");
+
+ EXPECT_BAD_TYPE(struct1, " [ # ; three; TRI ] ");
+ EXPECT_BAD_TYPE(struct1, " [ 3 ; # ; TRI ] ");
+ EXPECT_BAD_TYPE(struct1, " [ 1 ] ");
+ EXPECT_BAD_TYPE(struct1, " [ ] ");
+
+ const auto struct2 = StructLogicalType({
+ {"key", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"subkey", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ {"value", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ });
+
+ EXPECT_GOOD_TYPE(struct2, " [k ; s ; v ] ");
+ EXPECT_GOOD_TYPE(struct2, " [# ; # ; #] ");
+ EXPECT_GOOD_TYPE(struct2, " [# ; # ;] ");
+ EXPECT_GOOD_TYPE(struct2, " [# ; ] ");
+ EXPECT_GOOD_TYPE(struct2, " [ ] ");
+ EXPECT_BAD_TYPE(struct2, " [ 2 ] ");
+}
+
+TEST(TValidateLogicalTypeTest, TestTupleType)
+{
+ const auto tuple1 = TupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)),
+ });
+
+ EXPECT_GOOD_TYPE(tuple1, " [3; three; TRI ] ");
+ EXPECT_GOOD_TYPE(tuple1, " [1; one; # ] ");
+
+ EXPECT_BAD_TYPE(tuple1, " [3u; three; TRI ] ");
+ EXPECT_BAD_TYPE(tuple1, " [1; one ] ");
+ EXPECT_BAD_TYPE(tuple1, " [ # ; three; TRI ] ");
+ EXPECT_BAD_TYPE(tuple1, " [ 3 ; # ; TRI ] ");
+ EXPECT_BAD_TYPE(tuple1, " [ 1 ] ");
+ EXPECT_BAD_TYPE(tuple1, " [ ] ");
+
+ const auto tuple2 = TupleLogicalType({
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)),
+ OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String)),
+ });
+
+ EXPECT_GOOD_TYPE(tuple2, " [k ; s ; v ] ");
+ EXPECT_GOOD_TYPE(tuple2, " [# ; # ; #] ");
+
+ EXPECT_BAD_TYPE(tuple2, " [# ; # ;] ");
+ EXPECT_BAD_TYPE(tuple2, " [# ; ] ");
+ EXPECT_BAD_TYPE(tuple2, " [ ] ");
+ EXPECT_BAD_TYPE(tuple2, " [ 2 ] ");
+}
+
+void TestVariantImpl(ELogicalMetatype metatype)
+{
+ auto createVariantType = [&] (std::vector<TStructField> fields) {
+ if (metatype == ELogicalMetatype::VariantStruct) {
+ return VariantStructLogicalType(std::move(fields));
+ } else {
+ YT_VERIFY(metatype == ELogicalMetatype::VariantTuple);
+ std::vector<TLogicalTypePtr> elements;
+ for (const auto& f : fields) {
+ elements.push_back(f.Type);
+ }
+ return VariantTupleLogicalType(std::move(elements));
+ }
+ };
+
+ auto variant1 = createVariantType({
+ {"field1", SimpleLogicalType(ESimpleLogicalValueType::Boolean)},
+ {"field2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ });
+
+ EXPECT_GOOD_TYPE(variant1, "[0; %true]");
+ EXPECT_GOOD_TYPE(variant1, "[1; \"false\"]");
+
+ EXPECT_BAD_TYPE(variant1, "[0; \"false\"]");
+ EXPECT_BAD_TYPE(variant1, "[0; %true; %true]");
+ EXPECT_BAD_TYPE(variant1, "[1; %false]");
+ EXPECT_BAD_TYPE(variant1, "[]");
+ EXPECT_BAD_TYPE(variant1, "[0]");
+ EXPECT_BAD_TYPE(variant1, "[1]");
+ EXPECT_BAD_TYPE(variant1, "[-1; %true]");
+ EXPECT_BAD_TYPE(variant1, "[-1; \"true\"]");
+ EXPECT_BAD_TYPE(variant1, "[2; \"false\"]");
+}
+
+TEST(TValidateLogicalTypeTest, TestVariantStructType)
+{
+ TestVariantImpl(ELogicalMetatype::VariantStruct);
+}
+
+TEST(TValidateLogicalTypeTest, TestVariantTupleType)
+{
+ TestVariantImpl(ELogicalMetatype::VariantTuple);
+}
+
+TEST(TValidateLogicalTypeTest, TestDictType)
+{
+ const auto stringToInt = DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ );
+
+ EXPECT_GOOD_TYPE(stringToInt, "[]");
+ EXPECT_GOOD_TYPE(stringToInt, "[[\"foo\"; 0]]");
+ EXPECT_GOOD_TYPE(stringToInt, "[[foo; 0;]]");
+
+ EXPECT_BAD_TYPE(stringToInt, " # ");
+ EXPECT_BAD_TYPE(stringToInt, " foo ");
+ EXPECT_BAD_TYPE(stringToInt, "[foo; 0]");
+ EXPECT_BAD_TYPE(stringToInt, "[[foo; 0; 0]]");
+ EXPECT_BAD_TYPE(stringToInt, "[[foo;]]");
+ EXPECT_BAD_TYPE(stringToInt, "{foo=0;}"); // <- This is not a dict!
+}
+
+TEST(TValidateLogicalTypeTest, TestTaggedType)
+{
+ const auto taggedDict = TaggedLogicalType(
+ "tag",
+ DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ )
+ );
+
+ EXPECT_GOOD_TYPE(taggedDict, "[[\"foo\"; 0]]");
+ EXPECT_BAD_TYPE(taggedDict, "[[foo; 0; 0]]");
+
+ auto taggedVariant = TaggedLogicalType(
+ "tag",
+ VariantTupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Boolean),
+ SimpleLogicalType(ESimpleLogicalValueType::String),
+ })
+ );
+
+ EXPECT_GOOD_TYPE(taggedVariant, "[0; %true]");
+ EXPECT_BAD_TYPE(taggedVariant, "[0; \"false\"]");
+
+ const auto taggedStruct = TaggedLogicalType(
+ "tag",
+ StructLogicalType({
+ {"number", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"english", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"russian", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::String))},
+ })
+ );
+
+ EXPECT_GOOD_TYPE(taggedStruct, " [3; three; TRI ] ");
+ EXPECT_GOOD_TYPE(taggedStruct, " [1; one; # ] ");
+ EXPECT_GOOD_TYPE(taggedStruct, " [1; one ] ");
+
+ EXPECT_BAD_TYPE(taggedStruct, " [ # ; three; TRI ] ");
+ EXPECT_BAD_TYPE(taggedStruct, " [ 3 ; # ; TRI ] ");
+ EXPECT_BAD_TYPE(taggedStruct, " [ 1 ] ");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableChunkFormat
diff --git a/yt/yt/client/unittests/value_examples.cpp b/yt/yt/client/unittests/value_examples.cpp
new file mode 100644
index 0000000000..c2714fc856
--- /dev/null
+++ b/yt/yt/client/unittests/value_examples.cpp
@@ -0,0 +1,147 @@
+#include "value_examples.h"
+
+#include "logical_type_shortcuts.h"
+
+#include <yt/yt/library/decimal/decimal.h>
+
+#include <cmath>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NLogicalTypeShortcuts;
+using namespace NNamedValue;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TValueExample::TValueExample(TLogicalTypePtr logicalType, TNamedValue::TValue value, TString prettyYson)
+ : LogicalType(std::move(logicalType))
+ , Value(std::move(value))
+ , PrettyYson(std::move(prettyYson))
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::vector<TValueExample> GetPrimitiveValueExamples()
+{
+ static const std::vector<TValueExample> valueExamples = {
+ TValueExample{Int8(), 0, "0"},
+ TValueExample{Int8(), -5, "-5"},
+ TValueExample{Int8(), 42, "42"},
+ TValueExample{Int8(), -128, "-128"},
+ TValueExample{Int8(), 127, "127"},
+
+ TValueExample{Int16(), 0, "0"},
+ TValueExample{Int16(), -6, "-6"},
+ TValueExample{Int16(), 43, "43"},
+ TValueExample{Int16(), 0x7FFF, "32767"},
+ TValueExample{Int16(), -0x8000, "-32768"},
+
+ TValueExample{Int32(), 0, "0"},
+ TValueExample{Int32(), -7, "-7"},
+ TValueExample{Int32(), 44, "44"},
+ TValueExample{Int32(), 0x7FFFFFFF, "2147483647"},
+ TValueExample{Int32(), -0x80000000ll, "-2147483648"},
+
+ TValueExample{Int64(), 0, "0"},
+ TValueExample{Int64(), -7, "-7"},
+ TValueExample{Int64(), 45, "45"},
+ TValueExample{Int64(), 0x7FFFFFFFFFFFFFFFll, "9223372036854775807"},
+ TValueExample{Int64(), i64(-0x8000000000000000ll), "-9223372036854775808"},
+
+ TValueExample{Uint8(), 0ull, "0u"},
+ TValueExample{Uint8(), 46ull, "46u"},
+ TValueExample{Uint8(), 255ull, "255u"},
+
+ TValueExample{Uint16(), 0ull, "0u"},
+ TValueExample{Uint16(), 47ull, "47u"},
+ TValueExample{Uint16(), 0xFFFFull, "65535u"},
+
+ TValueExample{Uint32(), 0ull, "0u"},
+ TValueExample{Uint32(), 48ull, "48u"},
+ TValueExample{Uint32(), 0xFFFFFFFFull, "4294967295u"},
+
+ TValueExample{Uint64(), 0ull, "0u"},
+ TValueExample{Uint64(), 49ull, "49u"},
+ TValueExample{Uint64(), 0xFFFFFFFFFFFFFFFFull, "18446744073709551615u"},
+
+ TValueExample{String(), "", R"("")"},
+ TValueExample{String(), "foo", R"("foo")"},
+ TValueExample{String(), TString(TStringBuf("\xf0\x00"sv)), R"("\xf0\x00")"},
+
+ TValueExample{Utf8(), "", R"("")"},
+ TValueExample{Utf8(), "bar", R"("bar")"},
+
+ TValueExample{Bool(), true, "%true"},
+ TValueExample{Bool(), false, "%false"},
+
+ // NB. .125 = 1 / 8 is
+ TValueExample{Double(), 3.125, "3.125"},
+ TValueExample{Double(), 2.775, "2.775"},
+ // TPrimitiveTypeExample{Double(), std::nan("1"), "%nan"},
+ TValueExample{Double(), INFINITY, "%inf"},
+ TValueExample{Double(), -INFINITY, "%-inf"},
+
+ TValueExample{Float(), 5.125, "5.125"},
+ TValueExample{Float(), 6.775, "6.775"},
+
+ TValueExample{Null(), nullptr, "#"},
+ TValueExample{Void(), nullptr, "#"},
+
+ TValueExample{Json(), "83", R"("83")"},
+ TValueExample{Json(), "[]", R"("[]")"},
+
+ TValueExample{
+ Uuid(),
+ TString(16, 0),
+ TString(TStringBuf(R"("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")"))
+ },
+ TValueExample{
+ Uuid(),
+ TString(TStringBuf("\x01\x23\x45\x67\x89\xAB\xCD\xEF\xFE\xDC\xBA\x98\x76\x54\x32\x10"sv)),
+ TString(TStringBuf(R"("\x01\x23\x45\x67\x89\xAB\xCD\xEF\xFE\xDC\xBA\x98\x76\x54\x32\x10")"))
+ },
+
+ TValueExample{Date(), 0ull, "0u"},
+ TValueExample{Date(), 18431ull, "18431u"},
+ TValueExample{Date(), 49672ull, "49672u"},
+
+ TValueExample{Datetime(), 0ull, "0u"},
+ TValueExample{Datetime(), 668800588ull, "668800588u"},
+ TValueExample{Datetime(), 4291747199ull, "4291747199u"},
+
+ TValueExample{Timestamp(), 0ull, "0u"},
+ TValueExample{Timestamp(), 2508452463052426ull, "2508452463052426u"},
+ TValueExample{Timestamp(), 4291747199999999ull, "4291747199999999u"},
+
+ TValueExample{Interval(), 0, "0"},
+ TValueExample{Timestamp(), 2208610308646589ll, "2208610308646589"},
+ TValueExample{Timestamp(), 1187314596653899ll, "1187314596653899"},
+ TValueExample{Timestamp(), 4291747199999999ll, "4291747199999999"},
+ TValueExample{Timestamp(), -4291747199999999ll, "-4291747199999999"},
+
+ TValueExample{Yson(), "qux", R"("qux")"},
+
+ TValueExample{Decimal(3, 2), NDecimal::TDecimal::TextToBinary("3.14", 3, 2), R"("\x80\x00\x01\x3a")"},
+ };
+
+ THashSet<ESimpleLogicalValueType> allValueTypes;
+ for (const auto value : TEnumTraits<ESimpleLogicalValueType>::GetDomainValues()) {
+ allValueTypes.insert(value);
+ }
+ for (const auto& example : valueExamples) {
+ if (example.LogicalType->GetMetatype() == ELogicalMetatype::Simple) {
+ allValueTypes.erase(example.LogicalType->AsSimpleTypeRef().GetElement());
+ }
+ }
+ if (!allValueTypes.empty()) {
+ THROW_ERROR_EXCEPTION("PrimitiveTypeExample variable doesn't contain values: %v",
+ allValueTypes);
+ }
+ return valueExamples;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/value_examples.h b/yt/yt/client/unittests/value_examples.h
new file mode 100644
index 0000000000..06644e2cd6
--- /dev/null
+++ b/yt/yt/client/unittests/value_examples.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <yt/yt/library/named_value/named_value.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+
+namespace NYT::NTableClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TValueExample
+{
+ TLogicalTypePtr LogicalType;
+ NNamedValue::TNamedValue::TValue Value;
+ TString PrettyYson;
+
+ TValueExample(TLogicalTypePtr logicalType, NNamedValue::TNamedValue::TValue value, TString prettyYson);
+};
+
+std::vector<TValueExample> GetPrimitiveValueExamples();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/web_json_writer_ut.cpp b/yt/yt/client/unittests/web_json_writer_ut.cpp
new file mode 100644
index 0000000000..a440002a8b
--- /dev/null
+++ b/yt/yt/client/unittests/web_json_writer_ut.cpp
@@ -0,0 +1,1570 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/formats/web_json_writer.h>
+
+#include <yt/yt/client/table_client/logical_type.h>
+#include <yt/yt/client/table_client/name_table.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <yt/yt/core/json/json_parser.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+#include <yt/yt/library/named_value/named_value.h>
+
+#include <limits>
+
+namespace NYT::NFormats {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NConcurrency;
+using namespace NTableClient;
+
+using NNamedValue::MakeRow;
+
+INodePtr ParseJsonToNode(TStringBuf string)
+{
+ TBuildingYsonConsumerViaTreeBuilder<INodePtr> builder(EYsonType::Node);
+ TMemoryInput stream(string);
+
+ // For plain (raw) JSON parsing we need to switch off
+ // "smart" attribute analysis and UTF-8 decoding.
+ auto config = New<NJson::TJsonFormatConfig>();
+ config->EncodeUtf8 = false;
+ config->Plain = true;
+
+ NJson::ParseJson(&stream, &builder, std::move(config));
+ return builder.Finish();
+}
+
+class TWriterForWebJson
+ : public ::testing::Test
+{
+protected:
+ TNameTablePtr NameTable_ = New<TNameTable>();
+ TWebJsonFormatConfigPtr Config_ = New<TWebJsonFormatConfig>();
+ TStringStream OutputStream_;
+ ISchemalessFormatWriterPtr Writer_;
+
+ void CreateStandardWriter(const std::vector<TTableSchemaPtr>& schemas = {New<TTableSchema>()})
+ {
+ Writer_ = CreateWriterForWebJson(
+ Config_,
+ NameTable_,
+ schemas,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&OutputStream_)));
+ }
+};
+
+TEST_F(TWriterForWebJson, Simple)
+{
+ Config_->MaxAllColumnNamesCount = 2;
+
+ CreateStandardWriter();
+
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_a", 100500u},
+ {"column_b", true},
+ {"column_c", "row1_c"},
+ {RowIndexColumnName, 0},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", "row2_c"},
+ {"column_b", "row2_b"},
+ {RowIndexColumnName, 1},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ WaitFor(Writer_->Close())
+ .ThrowOnError();
+
+ TString expectedOutput =
+ "{"
+ "\"rows\":["
+ "{"
+ "\"column_a\":{"
+ "\"$type\":\"uint64\","
+ "\"$value\":\"100500\""
+ "},"
+ "\"column_b\":{"
+ "\"$type\":\"boolean\","
+ "\"$value\":\"true\""
+ "},"
+ "\"column_c\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row1_c\""
+ "}"
+ "},"
+ "{"
+ "\"column_c\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row2_c\""
+ "},"
+ "\"column_b\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row2_b\""
+ "}"
+ "}"
+ "],"
+ "\"incomplete_columns\":\"false\","
+ "\"incomplete_all_column_names\":\"true\","
+ "\"all_column_names\":["
+ "\"column_a\","
+ "\"column_b\""
+ "]"
+ "}";
+
+ EXPECT_EQ(std::ssize(expectedOutput), Writer_->GetWrittenSize());
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TWriterForWebJson, SliceColumnsByMaxCount)
+{
+ Config_->MaxSelectedColumnCount = 2;
+
+ CreateStandardWriter();
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_a", "row1_a"},
+ {"column_b", "row1_b"},
+ {"column_c", "row1_c"},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", "row2_c"},
+ {"column_b", "row2_b"},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", "row3_c"},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close();
+
+ TString expectedOutput =
+ "{"
+ "\"rows\":["
+ "{"
+ "\"column_a\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row1_a\""
+ "},"
+ "\"column_b\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row1_b\""
+ "}"
+ "},"
+ "{"
+ "\"column_b\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row2_b\""
+ "}"
+ "},"
+ "{"
+ "}"
+ "],"
+ "\"incomplete_columns\":\"true\","
+ "\"incomplete_all_column_names\":\"false\","
+ "\"all_column_names\":["
+ "\"column_a\","
+ "\"column_b\","
+ "\"column_c\""
+ "]"
+ "}";
+
+ EXPECT_EQ(std::ssize(expectedOutput), Writer_->GetWrittenSize());
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TWriterForWebJson, SliceStrings)
+{
+ Config_->FieldWeightLimit = 6;
+
+ CreateStandardWriter();
+
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_b", "row1_b"},
+ {"column_c", "rooooow1_c"},
+ {"column_a", "row1_a"},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", "row2_c"},
+ {"column_b", "rooow2_b"},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", "row3_c"},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close();
+
+ TString expectedOutput =
+ "{"
+ "\"rows\":["
+ "{"
+ "\"column_b\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row1_b\""
+ "},"
+ "\"column_c\":{"
+ "\"$incomplete\":true,"
+ "\"$type\":\"string\","
+ "\"$value\":\"rooooo\""
+ "},"
+ "\"column_a\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row1_a\""
+ "}"
+ "},"
+ "{"
+ "\"column_c\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row2_c\""
+ "},"
+ "\"column_b\":{"
+ "\"$incomplete\":true,"
+ "\"$type\":\"string\","
+ "\"$value\":\"rooow2\""
+ "}"
+ "},"
+ "{"
+ "\"column_c\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row3_c\""
+ "}"
+ "}"
+ "],"
+ "\"incomplete_columns\":\"false\","
+ "\"incomplete_all_column_names\":\"false\","
+ "\"all_column_names\":["
+ "\"column_a\","
+ "\"column_b\","
+ "\"column_c\""
+ "]"
+ "}";
+
+ EXPECT_EQ(std::ssize(expectedOutput), Writer_->GetWrittenSize());
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TWriterForWebJson, ReplaceAnyWithNull)
+{
+ Config_->FieldWeightLimit = 8;
+
+ CreateStandardWriter();
+
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_b", EValueType::Any, "{key=a}"},
+ {"column_c", "row1_c"},
+ {"column_a", "row1_a"},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", EValueType::Any, "{key=aaaaaa}"},
+ {"column_b", "row2_b"},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", "row3_c"},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ WaitFor(Writer_->Close())
+ .ThrowOnError();
+
+ TString expectedOutput =
+ "{"
+ "\"rows\":["
+ "{"
+ "\"column_b\":{"
+ "\"key\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"a\""
+ "}"
+ "},"
+ "\"column_c\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row1_c\""
+ "},"
+ "\"column_a\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row1_a\""
+ "}"
+ "},"
+ "{"
+ "\"column_c\":{"
+ "\"$incomplete\":true,"
+ "\"$type\":\"any\","
+ "\"$value\":\"\""
+ "},"
+ "\"column_b\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row2_b\""
+ "}"
+ "},"
+ "{"
+ "\"column_c\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"row3_c\""
+ "}"
+ "}"
+ "],"
+ "\"incomplete_columns\":\"false\","
+ "\"incomplete_all_column_names\":\"false\","
+ "\"all_column_names\":["
+ "\"column_a\","
+ "\"column_b\","
+ "\"column_c\""
+ "]"
+ "}";
+
+ EXPECT_EQ(std::ssize(expectedOutput), Writer_->GetWrittenSize());
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TWriterForWebJson, SkipSystemColumns)
+{
+ Config_->SkipSystemColumns = false;
+
+ CreateStandardWriter();
+
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {TableIndexColumnName, 0},
+ {RowIndexColumnName, 1},
+ {TabletIndexColumnName, 2},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ WaitFor(Writer_->Close())
+ .ThrowOnError();
+
+ TString expectedOutput =
+ "{"
+ "\"rows\":["
+ "{"
+ "\"$$table_index\":{"
+ "\"$type\":\"int64\","
+ "\"$value\":\"0\""
+ "},"
+ "\"$$row_index\":{"
+ "\"$type\":\"int64\","
+ "\"$value\":\"1\""
+ "},"
+ "\"$$tablet_index\":{"
+ "\"$type\":\"int64\","
+ "\"$value\":\"2\""
+ "}"
+ "}"
+ "],"
+ "\"incomplete_columns\":\"false\","
+ "\"incomplete_all_column_names\":\"false\","
+ "\"all_column_names\":["
+ "\"$row_index\","
+ "\"$table_index\","
+ "\"$tablet_index\""
+ "]"
+ "}";
+
+ EXPECT_EQ(std::ssize(expectedOutput), Writer_->GetWrittenSize());
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TWriterForWebJson, SkipUnregisteredColumns)
+{
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ int keyDId = -1;
+ row.AddValue(MakeUnversionedBooleanValue(true, keyDId));
+ std::vector<TUnversionedRow> rows = {row.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ keyDId = NameTable_->RegisterName("column_d");
+
+ rows.clear();
+ row.Reset();
+ row.AddValue(MakeUnversionedBooleanValue(true, keyDId));
+ rows.push_back(row.GetRow());
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close();
+
+ TString expectedOutput =
+ "{"
+ "\"rows\":["
+ "{"
+ "},"
+ "{"
+ "\"column_d\":{"
+ "\"$type\":\"boolean\","
+ "\"$value\":\"true\""
+ "}"
+ "}"
+ "],"
+ "\"incomplete_columns\":\"false\","
+ "\"incomplete_all_column_names\":\"false\","
+ "\"all_column_names\":["
+ "\"column_d\""
+ "]"
+ "}";
+
+ EXPECT_EQ(std::ssize(expectedOutput), Writer_->GetWrittenSize());
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+TEST_F(TWriterForWebJson, SliceColumnsByName)
+{
+ Config_->ColumnNames = {
+ "column_b",
+ "column_c",
+ "$tablet_index"};
+ Config_->MaxSelectedColumnCount = 2;
+ Config_->SkipSystemColumns = false;
+
+ CreateStandardWriter();
+
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_a", 100500u},
+ {"column_b", 0.42},
+ {"column_c", "abracadabra"},
+ {TabletIndexColumnName, 10},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ WaitFor(Writer_->Close())
+ .ThrowOnError();
+ auto result = ParseJsonToNode(OutputStream_.Str());
+
+ TString expectedOutput =
+ "{"
+ "\"rows\":["
+ "{"
+ "\"column_b\":{"
+ "\"$type\":\"double\","
+ "\"$value\":\"0.42\""
+ "},"
+ "\"column_c\":{"
+ "\"$type\":\"string\","
+ "\"$value\":\"abracadabra\""
+ "},"
+ "\"$$tablet_index\":{"
+ "\"$type\":\"int64\","
+ "\"$value\":\"10\""
+ "}"
+ "}"
+ "],"
+ "\"incomplete_columns\":\"true\","
+ "\"incomplete_all_column_names\":\"false\","
+ "\"all_column_names\":["
+ "\"$tablet_index\","
+ "\"column_a\","
+ "\"column_b\","
+ "\"column_c\""
+ "]"
+ "}";
+
+ EXPECT_EQ(std::ssize(expectedOutput), Writer_->GetWrittenSize());
+ EXPECT_EQ(expectedOutput, OutputStream_.Str());
+}
+
+template <typename TValue>
+void CheckYqlValue(
+ const INodePtr& valueNode,
+ const TValue& expectedValue)
+{
+ using TDecayedValue = std::decay_t<TValue>;
+ if constexpr (std::is_convertible_v<TDecayedValue, TString>) {
+ ASSERT_EQ(valueNode->GetType(), ENodeType::String);
+ EXPECT_EQ(valueNode->GetValue<TString>(), expectedValue);
+ } else if constexpr (std::is_same_v<TDecayedValue, double>) {
+ ASSERT_EQ(valueNode->GetType(), ENodeType::String);
+ EXPECT_FLOAT_EQ(FromString<double>(valueNode->GetValue<TString>()), expectedValue);
+ } else if constexpr (std::is_same_v<TDecayedValue, bool>) {
+ ASSERT_EQ(valueNode->GetType(), ENodeType::Boolean);
+ EXPECT_EQ(valueNode->GetValue<bool>(), expectedValue);
+ } else if constexpr (std::is_same_v<TDecayedValue, INodePtr>) {
+ EXPECT_TRUE(AreNodesEqual(valueNode, expectedValue))
+ << "actualValueNode is " << ConvertToYsonString(valueNode, EYsonFormat::Pretty).AsStringBuf()
+ << "\nexpectedValue is " << ConvertToYsonString(expectedValue, EYsonFormat::Pretty).AsStringBuf();
+ } else {
+ static_assert(TDependentFalse<TDecayedValue>, "Type not allowed");
+ }
+}
+
+template <typename TType>
+void CheckYqlType(
+ const INodePtr& typeNode,
+ const TType& expectedType,
+ const std::vector<INodePtr>& yqlTypes)
+{
+ ASSERT_EQ(typeNode->GetType(), ENodeType::String);
+ auto typeIndexString = typeNode->GetValue<TString>();
+ auto typeIndex = FromString<int>(typeIndexString);
+ ASSERT_LT(typeIndex, static_cast<int>(yqlTypes.size()));
+ ASSERT_GE(typeIndex, 0);
+ const auto& yqlType = yqlTypes[typeIndex];
+ EXPECT_EQ(yqlType->GetType(), ENodeType::List);
+
+ auto expectedTypeNode = [&] () -> INodePtr {
+ using TDecayedType = std::decay_t<TType>;
+ if constexpr (std::is_convertible_v<TDecayedType, TString>) {
+ return ConvertToNode(TYsonString(TString(expectedType)));
+ } else if constexpr (std::is_same_v<TDecayedType, INodePtr>) {
+ return expectedType;
+ } else {
+ static_assert(TDependentFalse<TDecayedType>, "Type not allowed");
+ }
+ }();
+ EXPECT_TRUE(AreNodesEqual(yqlType, expectedTypeNode))
+ << "yqlType is " << ConvertToYsonString(yqlType, EYsonFormat::Pretty).AsStringBuf()
+ << "\nexpectedTypeNode is " << ConvertToYsonString(expectedTypeNode, EYsonFormat::Pretty).AsStringBuf();
+}
+
+template <typename TValue, typename TType>
+void CheckYqlTypeAndValue(
+ const INodePtr& row,
+ TStringBuf name,
+ const TType& expectedType,
+ const TValue& expectedValue,
+ const std::vector<INodePtr>& yqlTypes)
+{
+ ASSERT_EQ(row->GetType(), ENodeType::Map);
+ auto entry = row->AsMap()->FindChild(TString(name));
+ ASSERT_TRUE(entry);
+ ASSERT_EQ(entry->GetType(), ENodeType::List);
+ ASSERT_EQ(entry->AsList()->GetChildCount(), 2);
+ auto valueNode = entry->AsList()->GetChildOrThrow(0);
+ CheckYqlValue(valueNode, expectedValue);
+ auto typeNode = entry->AsList()->GetChildOrThrow(1);
+ CheckYqlType(typeNode, expectedType, yqlTypes);
+}
+
+#define CHECK_YQL_TYPE_AND_VALUE(row, name, expectedType, expectedValue, yqlTypes) \
+ do { \
+ SCOPED_TRACE(name); \
+ CheckYqlTypeAndValue(row, name, expectedType, expectedValue, yqlTypes); \
+ } while (0)
+
+TEST_F(TWriterForWebJson, YqlValueFormat_SimpleTypes)
+{
+ Config_->MaxAllColumnNamesCount = 2;
+ Config_->ValueFormat = EWebJsonValueFormat::Yql;
+
+ // We will emulate writing rows from two tables.
+ CreateStandardWriter(std::vector{New<TTableSchema>(), New<TTableSchema>()});
+
+ {
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_a", 100500u},
+ {"column_b", true},
+ {"column_c", "row1_c"},
+ {RowIndexColumnName, 0},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_c", "row2_c"},
+ {"column_b", "row2_b"},
+ {RowIndexColumnName, 1},
+ {TableIndexColumnName, 0},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_a", -100500},
+ {"column_b", EValueType::Any, "{x=2;y=3}"},
+ {"column_c", 2.71828},
+ {RowIndexColumnName, 1},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close().Get().ThrowOnError();
+ }
+
+ auto result = ParseJsonToNode(OutputStream_.Str());
+ ASSERT_EQ(result->GetType(), ENodeType::Map);
+
+ auto rows = result->AsMap()->FindChild("rows");
+ ASSERT_TRUE(rows);
+ auto incompleteColumns = result->AsMap()->FindChild("incomplete_columns");
+ ASSERT_TRUE(incompleteColumns);
+ auto incompleteAllColumnNames = result->AsMap()->FindChild("incomplete_all_column_names");
+ ASSERT_TRUE(incompleteAllColumnNames);
+ auto allColumnNames = result->AsMap()->FindChild("all_column_names");
+ ASSERT_TRUE(allColumnNames);
+ auto yqlTypeRegistry = result->AsMap()->FindChild("yql_type_registry");
+ ASSERT_TRUE(yqlTypeRegistry);
+
+ ASSERT_EQ(incompleteColumns->GetType(), ENodeType::String);
+ EXPECT_EQ(incompleteColumns->GetValue<TString>(), "false");
+
+ ASSERT_EQ(incompleteAllColumnNames->GetType(), ENodeType::String);
+ EXPECT_EQ(incompleteAllColumnNames->GetValue<TString>(), "true");
+
+ ASSERT_EQ(allColumnNames->GetType(), ENodeType::List);
+ std::vector<TString> allColumnNamesVector;
+ ASSERT_NO_THROW(allColumnNamesVector = ConvertTo<decltype(allColumnNamesVector)>(allColumnNames));
+ EXPECT_EQ(allColumnNamesVector, (std::vector<TString>{"column_a", "column_b"}));
+
+ ASSERT_EQ(yqlTypeRegistry->GetType(), ENodeType::List);
+ auto yqlTypes = ConvertTo<std::vector<INodePtr>>(yqlTypeRegistry);
+
+ ASSERT_EQ(rows->GetType(), ENodeType::List);
+ ASSERT_EQ(rows->AsList()->GetChildCount(), 3);
+
+ auto row1 = rows->AsList()->GetChildOrThrow(0);
+ auto row2 = rows->AsList()->GetChildOrThrow(1);
+ auto row3 = rows->AsList()->GetChildOrThrow(2);
+
+ ASSERT_EQ(row1->GetType(), ENodeType::Map);
+ EXPECT_EQ(row1->AsMap()->GetChildCount(), 3);
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_a", R"(["DataType"; "Uint64"])", "100500", yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_b", R"(["DataType"; "Boolean"])", true, yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_c", R"(["DataType"; "String"])", "row1_c", yqlTypes);
+
+ ASSERT_EQ(row2->GetType(), ENodeType::Map);
+ EXPECT_EQ(row2->AsMap()->GetChildCount(), 2);
+ CHECK_YQL_TYPE_AND_VALUE(row2, "column_b", R"(["DataType"; "String"])", "row2_b", yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row2, "column_c", R"(["DataType"; "String"])", "row2_c", yqlTypes);
+
+ ASSERT_EQ(row3->GetType(), ENodeType::Map);
+ EXPECT_EQ(row3->AsMap()->GetChildCount(), 3);
+ CHECK_YQL_TYPE_AND_VALUE(row3, "column_a", R"(["DataType"; "Int64"])", "-100500", yqlTypes);
+ auto row3BValue = ConvertToNode(TYsonString(TStringBuf(R"({
+ val = {
+ x = {
+ "$type" = "int64";
+ "$value" = "2";
+ };
+ y = {
+ "$type" = "int64";
+ "$value" = "3";
+ }
+ }
+ })")));
+ CHECK_YQL_TYPE_AND_VALUE(row3, "column_b", R"(["DataType"; "Yson"])", row3BValue, yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row3, "column_c", R"(["DataType"; "Double"])", 2.71828, yqlTypes);
+}
+
+TEST_F(TWriterForWebJson, ColumnNameEncoding)
+{
+ Config_->MaxAllColumnNamesCount = 2;
+ Config_->ValueFormat = EWebJsonValueFormat::Yql;
+
+ CreateStandardWriter();
+
+ {
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_a", 100500u},
+ {"column_non_ascii_\xd0\x81", -100500},
+ }).Get()
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close().Get().ThrowOnError();
+ }
+
+ auto result = ParseJsonToNode(OutputStream_.Str());
+ ASSERT_EQ(result->GetType(), ENodeType::Map);
+
+ auto rows = result->AsMap()->FindChild("rows");
+ ASSERT_TRUE(rows);
+ auto incompleteColumns = result->AsMap()->FindChild("incomplete_columns");
+ ASSERT_TRUE(incompleteColumns);
+ auto incompleteAllColumnNames = result->AsMap()->FindChild("incomplete_all_column_names");
+ ASSERT_TRUE(incompleteAllColumnNames);
+ auto allColumnNames = result->AsMap()->FindChild("all_column_names");
+ ASSERT_TRUE(allColumnNames);
+ auto yqlTypeRegistry = result->AsMap()->FindChild("yql_type_registry");
+ ASSERT_TRUE(yqlTypeRegistry);
+
+ ASSERT_EQ(allColumnNames->GetType(), ENodeType::List);
+ std::vector<TString> allColumnNamesVector;
+ ASSERT_NO_THROW(allColumnNamesVector = ConvertTo<decltype(allColumnNamesVector)>(allColumnNames));
+ EXPECT_EQ(allColumnNamesVector, (std::vector<TString>{"column_a", "column_non_ascii_\xc3\x90\xc2\x81"}));
+
+ ASSERT_EQ(yqlTypeRegistry->GetType(), ENodeType::List);
+ auto yqlTypes = ConvertTo<std::vector<INodePtr>>(yqlTypeRegistry);
+
+ ASSERT_EQ(rows->GetType(), ENodeType::List);
+ ASSERT_EQ(rows->AsList()->GetChildCount(), 1);
+
+ auto row1 = rows->AsList()->GetChildOrThrow(0);
+
+ ASSERT_EQ(row1->GetType(), ENodeType::Map);
+ EXPECT_EQ(row1->AsMap()->GetChildCount(), 2);
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_a", R"(["DataType"; "Uint64"])", "100500", yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_non_ascii_\xc3\x90\xc2\x81", R"(["DataType"; "Int64"])", "-100500", yqlTypes);
+}
+
+TEST_F(TWriterForWebJson, YqlValueFormat_ComplexTypes)
+{
+ Config_->ValueFormat = EWebJsonValueFormat::Yql;
+
+ auto firstSchema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"column_a", OptionalLogicalType(
+ ListLogicalType(MakeLogicalType(ESimpleLogicalValueType::Int64, true)))},
+ {"column_b", StructLogicalType({
+ {"key", MakeLogicalType(ESimpleLogicalValueType::String, true)},
+ {"value", MakeLogicalType(ESimpleLogicalValueType::String, true)},
+ {"variant_tuple", VariantTupleLogicalType({
+ MakeLogicalType(ESimpleLogicalValueType::Int8, true),
+ MakeLogicalType(ESimpleLogicalValueType::Boolean, false),
+ })},
+ {"variant_struct", VariantStructLogicalType({
+ {"a", MakeLogicalType(ESimpleLogicalValueType::Int8, true)},
+ {"b", MakeLogicalType(ESimpleLogicalValueType::Boolean, false)},
+ })},
+ {"dict", DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String)
+ )},
+ {"tagged", TaggedLogicalType(
+ "MyTag",
+ SimpleLogicalType(ESimpleLogicalValueType::Int64)
+ )},
+ {"timestamp", SimpleLogicalType(ESimpleLogicalValueType::Timestamp)},
+ {"date", SimpleLogicalType(ESimpleLogicalValueType::Date)},
+ {"datetime", SimpleLogicalType(ESimpleLogicalValueType::Datetime)},
+ {"interval", SimpleLogicalType(ESimpleLogicalValueType::Interval)},
+ {"json", SimpleLogicalType(ESimpleLogicalValueType::Json)},
+ {"float", SimpleLogicalType(ESimpleLogicalValueType::Float)},
+ })},
+ {"column_c", ListLogicalType(StructLogicalType({
+ {"very_optional_key", OptionalLogicalType(MakeLogicalType(ESimpleLogicalValueType::String, false))},
+ {"optional_value", MakeLogicalType(ESimpleLogicalValueType::String, false)},
+ }))},
+ });
+
+ auto secondSchema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"column_a", VariantTupleLogicalType({
+ SimpleLogicalType(ESimpleLogicalValueType::Null),
+ SimpleLogicalType(ESimpleLogicalValueType::Any),
+ })},
+ {"column_b", SimpleLogicalType(ESimpleLogicalValueType::Null)},
+ {"column_c", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Null))},
+ {"column_d", OptionalLogicalType(SimpleLogicalType(ESimpleLogicalValueType::Int64))},
+ });
+
+ auto firstColumnAType = ConvertToNode(TYsonString(TStringBuf(R"([
+ "OptionalType";
+ [
+ "ListType";
+ ["DataType"; "Int64"]
+ ]
+ ])")));
+ auto firstColumnBType = ConvertToNode(TYsonString(TStringBuf(R"([
+ "StructType";
+ [
+ [
+ "key";
+ ["DataType"; "String"]
+ ];
+ [
+ "value";
+ ["DataType"; "String"]
+ ];
+ [
+ "variant_tuple";
+ [
+ "VariantType";
+ [
+ "TupleType";
+ [
+ ["DataType"; "Int8"];
+ [
+ "OptionalType";
+ ["DataType"; "Boolean"]
+ ]
+ ]
+ ]
+ ]
+ ];
+ [
+ "variant_struct";
+ [
+ "VariantType";
+ [
+ "StructType";
+ [
+ [
+ "a";
+ ["DataType"; "Int8"]
+ ];
+ [
+ "b";
+ [
+ "OptionalType";
+ ["DataType"; "Boolean"]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ];
+ [
+ "dict";
+ [
+ "DictType";
+ ["DataType"; "Int64"];
+ ["DataType"; "String"]
+ ]
+ ];
+ [
+ "tagged";
+ [
+ "TaggedType";
+ "MyTag";
+ ["DataType"; "Int64"]
+ ]
+ ];
+ [
+ "timestamp";
+ ["DataType"; "Timestamp"]
+ ];
+ [
+ "date";
+ ["DataType"; "Date"]
+ ];
+ [
+ "datetime";
+ ["DataType"; "Datetime"]
+ ];
+ [
+ "interval";
+ ["DataType"; "Interval"]
+ ];
+ [
+ "json";
+ ["DataType"; "Json"]
+ ];
+ [
+ "float";
+ ["DataType"; "Float"]
+ ];
+ ]
+ ])")));
+ auto firstColumnCType = ConvertToNode(TYsonString(TStringBuf(R"([
+ "ListType";
+ [
+ "StructType";
+ [
+ [
+ "very_optional_key";
+ [
+ "OptionalType";
+ [
+ "OptionalType";
+ ["DataType"; "String"]
+ ]
+ ]
+ ];
+ [
+ "optional_value";
+ [
+ "OptionalType";
+ ["DataType"; "String"]
+ ]
+ ]
+ ]
+ ]
+ ])")));
+ auto secondColumnAType = ConvertToNode(TYsonString(TStringBuf(R"([
+ "VariantType";
+ [
+ "TupleType";
+ [
+ ["NullType"];
+ ["DataType"; "Yson"];
+ ]
+ ]
+ ])")));
+ auto secondColumnBType = ConvertToNode(TYsonString(TStringBuf(R"(["NullType"])")));
+ auto secondColumnCType = ConvertToNode(TYsonString(TStringBuf(R"([
+ "OptionalType";
+ [
+ "NullType";
+ ]
+ ])")));
+ auto secondColumnDType = ConvertToNode(TYsonString(TStringBuf(R"([
+ "OptionalType";
+ ["DataType"; "Int64"]
+ ])")));
+
+ CreateStandardWriter(std::vector{firstSchema, secondSchema});
+ {
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {"column_a", EValueType::Composite, R"([-1; -2; -5])"},
+ {
+ "column_b",
+ EValueType::Composite,
+ R"([
+ "key";
+ "value";
+ [0; 7];
+ [1; #];
+ [[1; "a"]; [2; "b"]];
+ 99;
+ 100u;
+ 101u;
+ 102u;
+ 103;
+ "[\"a\", {\"b\": 42}]";
+ -3.25;
+ ])",
+ },
+ {"column_c", EValueType::Composite, R"([[[#]; "value"]; [["key"]; #]])"},
+ {"column_d", -49},
+ {TableIndexColumnName, 0},
+ {RowIndexColumnName, 0},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_a", EValueType::Composite, R"([0; -2; -5; 177])"},
+ {
+ "column_b",
+ EValueType::Composite,
+ R"([
+ "key1";
+ "value1";
+ [1; %false];
+ [1; #];
+ [];
+ 199;
+ 0u;
+ 1101u;
+ 1102u;
+ 1103;
+ "null";
+ 0.0;
+ ])",
+ },
+ {"column_c", EValueType::Composite, R"([[#; #]; [["key1"]; #]])"},
+ {"column_d", 49u},
+ {RowIndexColumnName, 1},
+ }).Get(),
+ MakeRow(NameTable_, {
+ {"column_a", EValueType::Composite, "[]"},
+ {
+ "column_b",
+ EValueType::Composite,
+ R"([
+ "key2";
+ "value2";
+ [0; 127];
+ [1; %true];
+ [[0; ""]];
+ 399;
+ 30u;
+ 3101u;
+ 3202u;
+ 3103;
+ "{\"x\": false}";
+ 1e10;
+ ])"
+ },
+ {"column_c", EValueType::Composite, "[[[key]; #]]"},
+ {"column_d", "49"},
+ {RowIndexColumnName, 2},
+ }).Get(),
+
+ MakeRow(NameTable_, {
+ {"column_a", nullptr},
+ {
+ "column_b",
+ EValueType::Composite,
+ // First string is valid UTF-8, the second one should be Base64 encoded.
+ "["
+ "\"\xC3\xBF\";"
+ "\"\xFA\xFB\xFC\xFD\";"
+ R"(
+ [0; 127];
+ [1; %true];
+ [[-1; "-1"]; [0; ""]];
+ 499;
+ 40u;
+ 4101u;
+ 4202u;
+ 4103;
+ "{}";
+ -2.125;
+ ])",
+ },
+ {"column_c", EValueType::Composite, "[]"},
+ {"column_d", EValueType::Any, "{x=49}"},
+ {RowIndexColumnName, 3},
+ }).Get(),
+
+ // Here come rows from the second table.
+ MakeRow(NameTable_, {
+ {"column_a", EValueType::Composite, "[0; #]"},
+ {"column_b", nullptr},
+ {"column_c", nullptr},
+ {"column_d", -49},
+ {TableIndexColumnName, 1},
+ {RowIndexColumnName, 0},
+ }).Get(),
+
+ MakeRow(NameTable_, {
+ {"column_a", EValueType::Composite, "[1; {z=z}]"},
+ {"column_b", nullptr},
+ {"column_c", EValueType::Composite, "[#]"},
+ {"column_d", nullptr},
+ {TableIndexColumnName, 1},
+ {RowIndexColumnName, 1},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close().Get().ThrowOnError();
+ }
+
+ auto result = ParseJsonToNode(OutputStream_.Str());
+ ASSERT_EQ(result->GetType(), ENodeType::Map);
+
+ auto rows = result->AsMap()->FindChild("rows");
+ ASSERT_TRUE(rows);
+ auto incompleteColumns = result->AsMap()->FindChild("incomplete_columns");
+ ASSERT_TRUE(incompleteColumns);
+ auto incompleteAllColumnNames = result->AsMap()->FindChild("incomplete_all_column_names");
+ ASSERT_TRUE(incompleteAllColumnNames);
+ auto allColumnNames = result->AsMap()->FindChild("all_column_names");
+ ASSERT_TRUE(allColumnNames);
+ auto yqlTypeRegistry = result->AsMap()->FindChild("yql_type_registry");
+ ASSERT_TRUE(yqlTypeRegistry);
+
+ ASSERT_EQ(incompleteColumns->GetType(), ENodeType::String);
+ EXPECT_EQ(incompleteColumns->GetValue<TString>(), "false");
+
+ ASSERT_EQ(incompleteAllColumnNames->GetType(), ENodeType::String);
+ EXPECT_EQ(incompleteAllColumnNames->GetValue<TString>(), "false");
+
+ ASSERT_EQ(allColumnNames->GetType(), ENodeType::List);
+ std::vector<TString> allColumnNamesVector;
+ ASSERT_NO_THROW(allColumnNamesVector = ConvertTo<decltype(allColumnNamesVector)>(allColumnNames));
+ EXPECT_EQ(allColumnNamesVector, (std::vector<TString>{"column_a", "column_b", "column_c", "column_d"}));
+
+ ASSERT_EQ(yqlTypeRegistry->GetType(), ENodeType::List);
+ auto yqlTypes = ConvertTo<std::vector<INodePtr>>(yqlTypeRegistry);
+
+ ASSERT_EQ(rows->GetType(), ENodeType::List);
+ ASSERT_EQ(rows->AsList()->GetChildCount(), 6);
+
+ auto row1 = rows->AsList()->GetChildOrThrow(0);
+ auto row2 = rows->AsList()->GetChildOrThrow(1);
+ auto row3 = rows->AsList()->GetChildOrThrow(2);
+ auto row4 = rows->AsList()->GetChildOrThrow(3);
+ auto row5 = rows->AsList()->GetChildOrThrow(4);
+ auto row6 = rows->AsList()->GetChildOrThrow(5);
+
+ ASSERT_EQ(row1->GetType(), ENodeType::Map);
+ EXPECT_EQ(row1->AsMap()->GetChildCount(), 4);
+ auto row1AValue = ConvertToNode(TYsonString(TStringBuf(R"([{"val"=["-1"; "-2"; "-5"]}])")));
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_a", firstColumnAType, row1AValue, yqlTypes);
+ auto row1BValue = ConvertToNode(TYsonString(TStringBuf(
+ R"([
+ "key";
+ "value";
+ ["0"; "7"];
+ ["1"; #];
+ {"val"=[["1"; "a"]; ["2"; "b"]]};
+ "99";
+ "100";
+ "101";
+ "102";
+ "103";
+ "[\"a\", {\"b\": 42}]";
+ "-3.25";
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_b", firstColumnBType, row1BValue, yqlTypes);
+ auto row1CValue = ConvertToNode(TYsonString(TStringBuf(R"({
+ "val"=[
+ [[#]; ["value"]];
+ [[["key"]]; #]
+ ]
+ })")));
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_c", firstColumnCType, row1CValue, yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row1, "column_d", R"(["DataType"; "Int64"])", "-49", yqlTypes);
+
+ ASSERT_EQ(row2->GetType(), ENodeType::Map);
+ EXPECT_EQ(row2->AsMap()->GetChildCount(), 4);
+ auto row2AValue = ConvertToNode(TYsonString(TStringBuf(R"([{"val"=["0"; "-2"; "-5"; "177"]}])")));
+ CHECK_YQL_TYPE_AND_VALUE(row2, "column_a", firstColumnAType, row2AValue, yqlTypes);
+ auto row2BValue = ConvertToNode(TYsonString(TStringBuf(
+ R"([
+ "key1";
+ "value1";
+ ["1"; [%false]];
+ ["1"; #];
+ {"val"=[]};
+ "199";
+ "0";
+ "1101";
+ "1102";
+ "1103";
+ "null";
+ "0";
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row2, "column_b", firstColumnBType, row2BValue, yqlTypes);
+ auto row2CValue = ConvertToNode(TYsonString(TStringBuf(R"({
+ "val"=[
+ [#; #];
+ [[["key1"]]; #]
+ ]
+ })")));
+ CHECK_YQL_TYPE_AND_VALUE(row2, "column_c", firstColumnCType, row2CValue, yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row2, "column_d", R"(["DataType"; "Uint64"])", "49", yqlTypes);
+
+ ASSERT_EQ(row3->GetType(), ENodeType::Map);
+ EXPECT_EQ(row3->AsMap()->GetChildCount(), 4);
+ auto row3AValue = ConvertToNode(TYsonString(TStringBuf(R"([{"val"=[]}])")));
+ CHECK_YQL_TYPE_AND_VALUE(row3, "column_a", firstColumnAType, row3AValue, yqlTypes);
+ auto row3BValue = ConvertToNode(TYsonString(TStringBuf(
+ R"([
+ "key2";
+ "value2";
+ ["0"; "127"];
+ ["1"; [%true]];
+ {"val"=[["0"; ""]]};
+ "399";
+ "30";
+ "3101";
+ "3202";
+ "3103";
+ "{\"x\": false}";
+ "10000000000";
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row3, "column_b", firstColumnBType, row3BValue, yqlTypes);
+ auto row3CValue = ConvertToNode(TYsonString(TStringBuf(R"({
+ "val"=[
+ [[["key"]]; #]
+ ]
+ })")));
+ CHECK_YQL_TYPE_AND_VALUE(row3, "column_c", firstColumnCType, row3CValue, yqlTypes);
+ CHECK_YQL_TYPE_AND_VALUE(row3, "column_d", R"(["DataType"; "String"])", "49", yqlTypes);
+
+ ASSERT_EQ(row4->GetType(), ENodeType::Map);
+ EXPECT_EQ(row4->AsMap()->GetChildCount(), 4);
+ auto row4AValue = ConvertToNode(TYsonString(TStringBuf(R"(#)")));
+ CHECK_YQL_TYPE_AND_VALUE(row4, "column_a", firstColumnAType, row4AValue, yqlTypes);
+
+ auto row4BValue = ConvertToNode(TYsonString(TStringBuf(
+ "["
+ "\"\xC3\xBF\";"
+ R"(
+ {"b64" = %true; "val" = "+vv8/Q=="};
+ ["0"; "127"];
+ ["1"; [%true]];
+ {"val"=[["-1"; "-1"]; ["0"; ""]]};
+ "499";
+ "40";
+ "4101";
+ "4202";
+ "4103";
+ "{}";
+ "-2.125";
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row4, "column_b", firstColumnBType, row4BValue, yqlTypes);
+
+ auto row4CValue = ConvertToNode(TYsonString(TStringBuf(R"({"val"=[]})")));
+ CHECK_YQL_TYPE_AND_VALUE(row4, "column_c", firstColumnCType, row4CValue, yqlTypes);
+ auto row4DValue = ConvertToNode(TYsonString(TStringBuf(R"({
+ val = {
+ x = {
+ "$type" = "int64";
+ "$value" = "49";
+ }
+ }
+ })")));
+ CHECK_YQL_TYPE_AND_VALUE(row4, "column_d", R"(["DataType"; "Yson"])", row4DValue, yqlTypes);
+
+ // Here must come rows from the second table.
+
+ ASSERT_EQ(row5->GetType(), ENodeType::Map);
+ EXPECT_EQ(row5->AsMap()->GetChildCount(), 4);
+ auto row5AValue = ConvertToNode(TYsonString(TStringBuf(R"(["0"; #])")));
+ CHECK_YQL_TYPE_AND_VALUE(row5, "column_a", secondColumnAType, row5AValue, yqlTypes);
+ auto row5BValue = ConvertToNode(TYsonString(TStringBuf(R"(#)")));
+ CHECK_YQL_TYPE_AND_VALUE(row5, "column_b", secondColumnBType, row5BValue, yqlTypes);
+ auto row5CValue = ConvertToNode(TYsonString(TStringBuf(R"(#)")));
+ CHECK_YQL_TYPE_AND_VALUE(row5, "column_c", secondColumnCType, row5CValue, yqlTypes);
+ auto row5DValue = ConvertToNode(TYsonString(TStringBuf(R"(["-49"])")));
+ CHECK_YQL_TYPE_AND_VALUE(row5, "column_d", secondColumnDType, row5DValue, yqlTypes);
+
+ ASSERT_EQ(row6->GetType(), ENodeType::Map);
+ EXPECT_EQ(row6->AsMap()->GetChildCount(), 4);
+ auto row6AValue = ConvertToNode(TYsonString(TStringBuf(R"([
+ "1";
+ {
+ val = {
+ z = {
+ "$type" = "string";
+ "$value" = "z";
+ }
+ }
+ };
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row6, "column_a", secondColumnAType, row6AValue, yqlTypes);
+ auto row6BValue = ConvertToNode(TYsonString(TStringBuf(R"(#)")));
+ CHECK_YQL_TYPE_AND_VALUE(row6, "column_b", secondColumnBType, row6BValue, yqlTypes);
+ auto row6CValue = ConvertToNode(TYsonString(TStringBuf(R"([#])")));
+ CHECK_YQL_TYPE_AND_VALUE(row6, "column_c", secondColumnCType, row6CValue, yqlTypes);
+ auto row6DValue = ConvertToNode(TYsonString(TStringBuf(R"(#)")));
+ CHECK_YQL_TYPE_AND_VALUE(row6, "column_d", secondColumnDType, row6DValue, yqlTypes);
+}
+
+TEST_F(TWriterForWebJson, YqlValueFormat_Incomplete)
+{
+ Config_->ValueFormat = EWebJsonValueFormat::Yql;
+ Config_->FieldWeightLimit = 215;
+ Config_->StringWeightLimit = 10;
+
+ auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"column_a", StructLogicalType({
+ {"field1", SimpleLogicalType(ESimpleLogicalValueType::Int64)},
+ {"list", ListLogicalType(
+ VariantStructLogicalType({
+ {"a", DictLogicalType(
+ SimpleLogicalType(ESimpleLogicalValueType::Int64),
+ SimpleLogicalType(ESimpleLogicalValueType::String)
+ )},
+ {"b", SimpleLogicalType(ESimpleLogicalValueType::Any)},
+ })
+ )},
+ {"field2", SimpleLogicalType(ESimpleLogicalValueType::String)},
+ {"field3", MakeLogicalType(ESimpleLogicalValueType::Int64, false)},
+ })},
+ {"column_b", SimpleLogicalType(ESimpleLogicalValueType::Any)},
+ {"column_c", MakeLogicalType(ESimpleLogicalValueType::String, false)},
+ });
+
+ auto yqlTypeA = ConvertToNode(TYsonString(TStringBuf(R"([
+ "StructType";
+ [
+ [
+ "field1";
+ ["DataType"; "Int64"]
+ ];
+ [
+ "list";
+ [
+ "ListType";
+ [
+ "VariantType";
+ [
+ "StructType";
+ [
+ [
+ "a";
+ [
+ "DictType";
+ ["DataType"; "Int64"];
+ ["DataType"; "String"]
+ ]
+ ];
+ [
+ "b";
+ ["DataType"; "Yson"]
+ ];
+ ]
+ ]
+ ]
+ ]
+ ];
+ [
+ "field2";
+ ["DataType"; "String"]
+ ];
+ [
+ "field3";
+ [
+ "OptionalType";
+ ["DataType"; "Int64"]
+ ]
+ ];
+ ]
+ ])")));
+
+ auto yqlTypeB = ConvertToNode(TYsonString(TStringBuf(R"(["DataType"; "Yson"])")));
+ auto yqlTypeC = ConvertToNode(TYsonString(TStringBuf(R"(["OptionalType"; ["DataType"; "String"]])")));
+ {
+ CreateStandardWriter({schema});
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {
+ {
+ "column_a",
+ EValueType::Composite,
+ R"([
+ -1;
+ [
+ [
+ 0;
+ [
+ [-2; "UTF:)" + TString("\xF0\x90\x8D\x88") + "\xF0\x90\x8D\x88" + R"("];
+ [2; "!UTF:)" + TString("\xFA\xFB\xFC\xFD\xFA\xFB\xFC\xFD") + R"("];
+ [0; ""];
+ ]
+ ];
+ [
+ 1;
+ "{kinda_long_key = kinda_even_longer_value}"
+ ];
+ [
+ 0;
+ [
+ [0; "One more quite long string"];
+ [1; "One more quite long string"];
+ [2; "One more quite long string"];
+ [3; "One more quite long string"];
+ [4; "One more quite long string"];
+ [5; "One more quite long string"];
+ ]
+ ];
+ [
+ 1;
+ "{kinda_long_key = kinda_even_longer_value}"
+ ];
+ ];
+ "I'm short";
+ 424242238133245
+ ])"
+ },
+ {"column_b", EValueType::Any, "{kinda_long_key = kinda_even_longer_value}"},
+ {"column_c", "One more quite long string"},
+ }).Get(),
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close().Get().ThrowOnError();
+ }
+
+ auto result = ParseJsonToNode(OutputStream_.Str());
+ ASSERT_EQ(result->GetType(), ENodeType::Map);
+
+ auto rows = result->AsMap()->FindChild("rows");
+ ASSERT_TRUE(rows);
+ auto yqlTypeRegistry = result->AsMap()->FindChild("yql_type_registry");
+ ASSERT_TRUE(yqlTypeRegistry);
+
+ ASSERT_EQ(yqlTypeRegistry->GetType(), ENodeType::List);
+ auto yqlTypes = ConvertTo<std::vector<INodePtr>>(yqlTypeRegistry);
+
+ ASSERT_EQ(rows->GetType(), ENodeType::List);
+ ASSERT_EQ(rows->AsList()->GetChildCount(), 1);
+
+ auto row = rows->AsList()->GetChildOrThrow(0);
+ ASSERT_EQ(row->GetType(), ENodeType::Map);
+ EXPECT_EQ(row->AsMap()->GetChildCount(), 3);
+
+ auto rowAValue = ConvertToNode(TYsonString(R"([
+ "-1";
+ {
+ "inc" = %true;
+ "val" = [
+ [
+ "0";
+ {
+ "val" = [
+ ["-2"; {"inc"=%true; "val"="UTF:)" + TString("\xF0\x90\x8D\x88") + R"("}];
+ ["2"; {"inc"=%true; "b64"=%true; "val"="IVVURjr6"}];
+ ["0"; ""];
+ ]
+ }
+ ];
+ [
+ "1";
+ {"val"=""; "inc"=%true}
+ ];
+ [
+ "0";
+ {
+ "inc" = %true;
+ "val" = [
+ ["0"; {"val"="One more q"; "inc"=%true}];
+ ["1"; {"val"="One more "; "inc"=%true}];
+ ];
+ }
+ ];
+ ];
+ };
+ {
+ "val" = "";
+ "inc" = %true;
+ };
+ ["424242238133245"];
+ ])"));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_a", yqlTypeA, rowAValue, yqlTypes);
+
+ // Simple values are not truncated to |StringWeightLimit|
+ auto rowBValue = ConvertToNode(TYsonString(TStringBuf(R"({
+ val = {
+ kinda_long_key = {
+ "$type" = "string";
+ "$value" = kinda_even_longer_value;
+ }
+ }
+ })")));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_b", yqlTypeB, rowBValue, yqlTypes);
+ auto rowCValue = ConvertToNode(TYsonString(TStringBuf(R"(["One more quite long string"])")));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_c", yqlTypeC, rowCValue, yqlTypes);
+}
+
+
+TEST_F(TWriterForWebJson, YqlValueFormat_Any)
+{
+ Config_->ValueFormat = EWebJsonValueFormat::Yql;
+
+ auto schema = New<TTableSchema>(std::vector<TColumnSchema>{
+ {"column_a", MakeLogicalType(ESimpleLogicalValueType::Any, false)},
+ });
+
+ auto yqlTypeA = ConvertToNode(TYsonString(TStringBuf(R"([
+ "OptionalType";
+ ["DataType"; "Yson"]
+ ])")));
+
+ CreateStandardWriter({schema});
+ {
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {{"column_a", EValueType::Any, "{x=y;z=2}"}}).Get(),
+ MakeRow(NameTable_, {{"column_a", true}}).Get(),
+ MakeRow(NameTable_, {{"column_a", -42}}).Get(),
+ MakeRow(NameTable_, {{"column_a", 42u}}).Get(),
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close().Get().ThrowOnError();
+ }
+
+ auto result = ParseJsonToNode(OutputStream_.Str());
+ ASSERT_EQ(result->GetType(), ENodeType::Map);
+
+ auto rows = result->AsMap()->FindChild("rows");
+ ASSERT_TRUE(rows);
+ auto yqlTypeRegistry = result->AsMap()->FindChild("yql_type_registry");
+ ASSERT_TRUE(yqlTypeRegistry);
+
+ ASSERT_EQ(yqlTypeRegistry->GetType(), ENodeType::List);
+ auto yqlTypes = ConvertTo<std::vector<INodePtr>>(yqlTypeRegistry);
+
+ ASSERT_EQ(rows->GetType(), ENodeType::List);
+ ASSERT_EQ(rows->AsList()->GetChildCount(), 4);
+
+ {
+ auto row = rows->AsList()->GetChildOrThrow(0);
+ ASSERT_EQ(row->GetType(), ENodeType::Map);
+ auto rowAValue = ConvertToNode(TYsonString(TStringBuf(R"([
+ {
+ val = {
+ x = {
+ "$type" = "string";
+ "$value" = "y";
+ };
+ z = {
+ "$type" = "int64";
+ "$value" = "2";
+ }
+ }
+ }
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_a", yqlTypeA, rowAValue, yqlTypes);
+ }
+ {
+ auto row = rows->AsList()->GetChildOrThrow(1);
+ ASSERT_EQ(row->GetType(), ENodeType::Map);
+ auto rowAValue = ConvertToNode(TYsonString(TStringBuf(R"([
+ {
+ val = {
+ "$type" = "boolean";
+ "$value" = "true";
+ }
+ }
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_a", yqlTypeA, rowAValue, yqlTypes);
+ }
+ {
+ auto row = rows->AsList()->GetChildOrThrow(2);
+ ASSERT_EQ(row->GetType(), ENodeType::Map);
+ auto rowAValue = ConvertToNode(TYsonString(TStringBuf(R"([
+ {
+ val = {
+ "$type" = "int64";
+ "$value" = "-42";
+ }
+ }
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_a", yqlTypeA, rowAValue, yqlTypes);
+ }
+ {
+ auto row = rows->AsList()->GetChildOrThrow(3);
+ ASSERT_EQ(row->GetType(), ENodeType::Map);
+ auto rowAValue = ConvertToNode(TYsonString(TStringBuf(R"([
+ {
+ val = {
+ "$type" = "uint64";
+ "$value" = "42";
+ }
+ }
+ ])")));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_a", yqlTypeA, rowAValue, yqlTypes);
+ }
+}
+
+TEST_F(TWriterForWebJson, YqlValueFormat_CompositeNoSchema)
+{
+ Config_->ValueFormat = EWebJsonValueFormat::Yql;
+
+ auto schema = New<TTableSchema>();
+
+ auto yqlTypeA = ConvertToNode(TYsonString(TStringBuf(R"(["DataType"; "Yson"])")));
+
+ CreateStandardWriter({schema});
+ {
+ bool written = Writer_->Write({
+ MakeRow(NameTable_, {{"column_a", EValueType::Composite, "[1;2]"}}).Get(),
+ });
+ EXPECT_TRUE(written);
+ Writer_->Close().Get().ThrowOnError();
+ }
+
+ auto result = ParseJsonToNode(OutputStream_.Str());
+ ASSERT_EQ(result->GetType(), ENodeType::Map);
+
+ auto rows = result->AsMap()->FindChild("rows");
+ ASSERT_TRUE(rows);
+ auto yqlTypeRegistry = result->AsMap()->FindChild("yql_type_registry");
+ ASSERT_TRUE(yqlTypeRegistry);
+
+ ASSERT_EQ(yqlTypeRegistry->GetType(), ENodeType::List);
+ auto yqlTypes = ConvertTo<std::vector<INodePtr>>(yqlTypeRegistry);
+
+ ASSERT_EQ(rows->GetType(), ENodeType::List);
+ ASSERT_EQ(rows->AsList()->GetChildCount(), 1);
+
+ {
+ auto row = rows->AsList()->GetChildOrThrow(0);
+ ASSERT_EQ(row->GetType(), ENodeType::Map);
+ auto rowAValue = ConvertToNode(TYsonString(TStringBuf(R"({
+ "val" = [
+ {
+ "$type" = "int64";
+ "$value" = "1";
+ };
+ {
+ "$type" = "int64";
+ "$value" = "2";
+ }
+ ]
+ })")));
+ CHECK_YQL_TYPE_AND_VALUE(row, "column_a", yqlTypeA, rowAValue, yqlTypes);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/wire_protocol_ut.cpp b/yt/yt/client/unittests/wire_protocol_ut.cpp
new file mode 100644
index 0000000000..40845d5dbf
--- /dev/null
+++ b/yt/yt/client/unittests/wire_protocol_ut.cpp
@@ -0,0 +1,285 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/wire_protocol.h>
+
+#include <cstring>
+
+namespace NYT::NTableClient {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TWireProtocolTestTag
+{ };
+
+class TWireProtocolTest
+ : public ::testing::Test
+{
+public:
+ static std::vector<EValueType> GetValueTypes()
+ {
+ return {
+ EValueType::Null,
+ EValueType::Int64,
+ EValueType::Uint64,
+ EValueType::Double,
+ EValueType::Boolean,
+ EValueType::String,
+ EValueType::Any
+ };
+ }
+
+ static TUnversionedValue MakeValueSample(ui16 id, EValueType type, EValueFlags flags = EValueFlags::None)
+ {
+ switch (type) {
+ case EValueType::Null:
+ return MakeUnversionedNullValue(id, flags);
+ case EValueType::Int64:
+ return MakeUnversionedInt64Value(0x0123456789ABCDEFLL, id, flags);
+ case EValueType::Uint64:
+ return MakeUnversionedUint64Value(0xFEDCBA9876543210ULL, id, flags);
+ case EValueType::Double:
+ return MakeUnversionedDoubleValue(3.141592653589793, id, flags);
+ case EValueType::Boolean:
+ return MakeUnversionedBooleanValue(false, id, flags);
+ case EValueType::String:
+ return MakeUnversionedStringValue("s", id, flags);
+ case EValueType::Any:
+ return MakeUnversionedAnyValue("{}", id, flags);
+ default:
+ YT_ABORT();
+ }
+ }
+
+ static TUnversionedOwningRow MakeUnversionedRowSample()
+ {
+ TUnversionedOwningRowBuilder builder;
+ ui16 id = 0;
+ for (auto type : GetValueTypes()) {
+ for (auto flags : {EValueFlags::Aggregate, EValueFlags::None}) {
+ builder.AddValue(MakeValueSample(id, type, flags));
+ }
+ }
+ return builder.FinishRow();
+ }
+
+ static TUnversionedOwningRow MakeSchemafulRowSample()
+ {
+ TUnversionedOwningRowBuilder builder;
+ ui16 id = 0;
+ for (auto type : GetValueTypes()) {
+ // This one does not support aggregate flags.
+ builder.AddValue(MakeValueSample(id, type));
+ }
+ return builder.FinishRow();
+ }
+
+ static std::vector<ui32> ExtractSchemaData(TUnversionedRow row, EValueType nullType)
+ {
+ std::vector<ui32> result;
+ for (ui32 index = 0; index < row.GetCount(); ++index) {
+ ui32 tmp = static_cast<ui32>(row[index].Id);
+ if (row[index].Type == EValueType::Null) {
+ tmp |= static_cast<ui32>(nullType) << 16;
+ } else {
+ tmp |= static_cast<ui32>(row[index].Type) << 16;
+ }
+ result.push_back(tmp);
+ }
+ return result;
+ }
+
+ // We cannot use CompareRows, because it does not check aggregate bits and does not compare anys.
+ void CheckEquals(TUnversionedRow lhs, TUnversionedRow rhs)
+ {
+ ASSERT_EQ(lhs.GetCount(), rhs.GetCount()) << "rows have different length";
+
+ for (ui32 i = 0; i < lhs.GetCount(); ++i) {
+ const auto& lhsValue = lhs[i];
+ const auto& rhsValue = rhs[i];
+ SCOPED_TRACE(Format("#%v: LHS = %v ; RHS = %v", i, lhsValue, rhsValue));
+ EXPECT_TRUE(TBitwiseUnversionedValueEqual()(lhsValue, rhsValue));
+ }
+ }
+
+ void CheckEquals(const std::vector<unsigned char>& canonical, const TSharedRef& actual)
+ {
+ ASSERT_EQ(canonical.size(), actual.Size());
+
+ for (size_t i = 0; i < canonical.size(); ++i) {
+ auto canonicalByte = static_cast<unsigned char>(canonical[i]);
+ auto actualByte = static_cast<unsigned char>(actual[i]);
+ if (canonicalByte != 0xcf) { // 0xcf marks garbage due to alignment.
+ EXPECT_EQ(canonicalByte, actualByte);
+ }
+ }
+ }
+
+ static void Dump(const TSharedRef& blob)
+ {
+ Cerr << "=== BEGIN DUMP ===" << Endl;
+ for (size_t i = 0; i < blob.Size(); ++i) {
+ if (i % 16 == 0) {
+ Cerr << Endl;
+ }
+ Cerr << Format("0x%02x, ", static_cast<unsigned char>(blob.Begin()[i]));
+ }
+ Cerr << Endl << "=== END DUMP ===" << Endl;
+ }
+};
+
+TEST_F(TWireProtocolTest, UnversionedRow)
+{
+ auto originalRow = MakeUnversionedRowSample();
+
+ auto writer = CreateWireProtocolWriter();
+ writer->WriteUnversionedRow(originalRow);
+ auto blob = MergeRefsToRef<TWireProtocolTestTag>(writer->Finish());
+ Dump(blob);
+
+ auto reader = CreateWireProtocolReader(blob);
+ auto reconstructedRow = reader->ReadUnversionedRow(true);
+ CheckEquals(originalRow, reconstructedRow);
+
+ // This is a canonical dump. Do not break it.
+ const std::vector<unsigned char> canonicalBlob({
+ // value count
+ 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // one value per row
+ 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01,
+ 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01,
+ 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
+ 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40,
+ 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40,
+ 0x00, 0x00, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x01, 0x01, 0x00, 0x00, 0x00, 0x73, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x73, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0x00, 0x00, 0x11, 0x01, 0x02, 0x00, 0x00, 0x00, 0x7b, 0x7d, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7b, 0x7d, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ });
+
+ CheckEquals(canonicalBlob, blob);
+}
+
+TEST_F(TWireProtocolTest, SchemafulRow)
+{
+ auto originalRow = MakeSchemafulRowSample();
+
+ auto writer = CreateWireProtocolWriter();
+ writer->WriteSchemafulRow(originalRow);
+ auto blob = MergeRefsToRef<TWireProtocolTestTag>(writer->Finish());
+ Dump(blob);
+
+ auto reader = CreateWireProtocolReader(blob);
+ auto reconstructedRow = reader->ReadSchemafulRow(ExtractSchemaData(originalRow, EValueType::Int64), true);
+ CheckEquals(originalRow, reconstructedRow);
+
+ // This is a canonical dump. Do not break it.
+ const std::vector<unsigned char> canonicalBlob({
+ // value count
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // null bitmap
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // one value per row
+ 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01,
+ 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
+ 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x7d, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ });
+
+ CheckEquals(canonicalBlob, blob);
+}
+
+// Test that schemaful reader/writer properly treats null bitmap.
+TEST_F(TWireProtocolTest, Regression1)
+{
+ const std::vector<unsigned char> blob({
+ // value count = 4
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // null bitmap = 1 << 3
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // id = 0, type = int64, data = 1
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // id = 1, type = int64, data = 1
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // id = 2, type = string, data = "2"
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x32, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ // id = 3, type = string, data = (null)
+ });
+
+ const std::vector<ui32> blobSchemaData({
+ 0x00030000, // id = 0, type = int64
+ 0x00030001, // id = 1, type = int64
+ 0x00100002, // id = 2, type = int64
+ 0x00100003, // id = 3, type = int64
+ });
+
+ auto reader = CreateWireProtocolReader(TSharedRef::MakeCopy<TWireProtocolTestTag>(
+ TRef(blob.data(), blob.size())));
+ auto row = reader->ReadSchemafulRow(blobSchemaData, false);
+ EXPECT_TRUE(reader->GetCurrent() == reader->GetEnd());
+
+ ASSERT_EQ(static_cast<int>(row.GetCount()), 4);
+
+ EXPECT_EQ(row[0].Id, 0);
+ EXPECT_EQ(row[0].Type, EValueType::Int64);
+ EXPECT_EQ(row[0].Flags, EValueFlags::None);
+ EXPECT_EQ(row[0].Data.Int64, 1);
+
+ EXPECT_EQ(row[1].Id, 1);
+ EXPECT_EQ(row[1].Type, EValueType::Int64);
+ EXPECT_EQ(row[1].Flags, EValueFlags::None);
+ EXPECT_EQ(row[1].Data.Int64, 1);
+
+ EXPECT_EQ(row[2].Id, 2);
+ EXPECT_EQ(row[2].Type, EValueType::String);
+ EXPECT_EQ(row[2].Flags, EValueFlags::None);
+ EXPECT_EQ(static_cast<int>(row[2].Length), 1);
+ EXPECT_EQ(row[2].Data.String[0], '2');
+
+ EXPECT_EQ(row[3].Id, 3);
+ EXPECT_EQ(row[3].Type, EValueType::Null);
+ EXPECT_EQ(row[3].Flags, EValueFlags::None);
+}
+
+// Test sentinel values (min & max) serializability.
+TEST_F(TWireProtocolTest, Regression2)
+{
+ const std::vector<unsigned char> blob({
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // value count = 3
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // id = 0, type = null
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // id = 1, type = min
+ 0x02, 0x00, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, // id = 2, type = max
+ });
+
+ auto reader = CreateWireProtocolReader(TSharedRef::MakeCopy<TWireProtocolTestTag>(
+ TRef(blob.data(), blob.size())));
+ auto row = reader->ReadUnversionedRow(true);
+ EXPECT_TRUE(reader->GetCurrent() == reader->GetEnd());
+
+ ASSERT_EQ(static_cast<int>(row.GetCount()), 3);
+
+ EXPECT_EQ(row[0].Id, 0);
+ EXPECT_EQ(row[0].Type, EValueType::Null);
+ EXPECT_EQ(row[0].Flags, EValueFlags::None);
+
+ EXPECT_EQ(row[1].Id, 1);
+ EXPECT_EQ(row[1].Type, EValueType::Min);
+ EXPECT_EQ(row[1].Flags, EValueFlags::None);
+
+ EXPECT_EQ(row[2].Id, 2);
+ EXPECT_EQ(row[2].Type, EValueType::Max);
+ EXPECT_EQ(row[2].Flags, EValueFlags::None);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NTableClient
diff --git a/yt/yt/client/unittests/ya.make b/yt/yt/client/unittests/ya.make
new file mode 100644
index 0000000000..ab9f547e19
--- /dev/null
+++ b/yt/yt/client/unittests/ya.make
@@ -0,0 +1,82 @@
+GTEST(unittester-client)
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+ALLOCATOR(YT)
+
+PROTO_NAMESPACE(yt)
+
+SRCS(
+ protobuf_format_ut.proto
+
+ check_schema_compatibility_ut.cpp
+ check_type_compatibility_ut.cpp
+ chunk_replica_ut.cpp
+ column_sort_schema_ut.cpp
+ comparator_ut.cpp
+ composite_compare_ut.cpp
+ connection_ut.cpp
+ dsv_parser_ut.cpp
+ dsv_writer_ut.cpp
+ farm_fingerprint_stability_ut.cpp
+ key_bound_ut.cpp
+ key_bound_compressor_ut.cpp
+ key_helpers.cpp
+ key_ut.cpp
+ logical_type_ut.cpp
+ named_yson_token_ut.cpp
+ uuid_text_ut.cpp
+ time_text_ut.cpp
+ node_directory_ut.cpp
+ protobuf_format_ut.cpp
+ query_builder_ut.cpp
+ read_limit_ut.cpp
+ replication_progress_ut.cpp
+ row_helpers.cpp
+ row_ut.cpp
+ schemaful_dsv_parser_ut.cpp
+ schemaful_dsv_writer_ut.cpp
+ schema_ut.cpp
+ skiff_format_ut.cpp
+ skiff_yson_converter_ut.cpp
+ table_consumer_ut.cpp
+ unordered_reader_ut.cpp
+ unversioned_row_ut.cpp
+ validate_logical_type_ut.cpp
+ value_examples.cpp
+ web_json_writer_ut.cpp
+ wire_protocol_ut.cpp
+ yamred_dsv_parser_ut.cpp
+ yamred_dsv_writer_ut.cpp
+ yamr_parser_ut.cpp
+ yamr_writer_ut.cpp
+ ypath_ut.cpp
+ yson_helpers.cpp
+ zookeeper_bus_ut.cpp
+ zookeeper_protocol_ut.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ yt/yt/build
+ yt/yt/core/test_framework
+ yt/yt/core
+ yt/yt/client
+ yt/yt/client/formats
+ yt/yt/client/unittests/mock
+ yt/yt/library/named_value
+
+ yt/yt_proto/yt/formats
+)
+
+RESOURCE(
+ ${ARCADIA_ROOT}/library/cpp/type_info/ut/test-data/good-types.txt /types/good
+ ${ARCADIA_ROOT}/library/cpp/type_info/ut/test-data/bad-types.txt /types/bad
+)
+
+SIZE(MEDIUM)
+
+REQUIREMENTS(ram:12)
+
+END()
diff --git a/yt/yt/client/unittests/yamr_parser_ut.cpp b/yt/yt/client/unittests/yamr_parser_ut.cpp
new file mode 100644
index 0000000000..74b8f530a1
--- /dev/null
+++ b/yt/yt/client/unittests/yamr_parser_ut.cpp
@@ -0,0 +1,606 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/test_framework/yson_consumer_mock.h>
+
+#include <yt/yt/client/formats/yamr_parser.h>
+
+#include <yt/yt/core/yson/null_consumer.h>
+
+namespace NYT::NFormats {
+namespace {
+
+using namespace NYson;
+
+using ::testing::InSequence;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TYamrParserTest, Simple)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key1"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value1"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginAttributes());
+ EXPECT_CALL(Mock, OnKeyedItem("table_index"));
+ EXPECT_CALL(Mock, OnInt64Scalar(2));
+ EXPECT_CALL(Mock, OnEndAttributes());
+ EXPECT_CALL(Mock, OnEntity());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value2"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "key1\tvalue1\n"
+ "2\n"
+ "key2\tvalue2\n";
+
+ ParseYamr(input, &Mock);
+}
+
+TEST(TYamrParserTest, ValueWithTabs)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar(TStringBuf("key1\0", 5)));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value with \t and some other"));
+ EXPECT_CALL(Mock, OnEndMap());
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar(TStringBuf("another\0 value with \t", 21)));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input(
+ "key1\0\tvalue with \t and some other\n"
+ "key2\tanother\0 value with \t\n",
+ 34 +
+ 27);
+
+ ParseYamr(input, &Mock);
+}
+
+TEST(TYamrParserTest, SimpleWithSubkey)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key1"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("subkey1"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value1"));
+ EXPECT_CALL(Mock, OnEndMap());
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("subkey2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value2"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "key1\tsubkey1\tvalue1\n"
+ "key2\tsubkey2\tvalue2\n";
+
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = true;
+
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrParserTest, IncompleteRows)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key1"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("subkey1"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value1"));
+ EXPECT_CALL(Mock, OnEndMap());
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("subkey"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnEndMap());
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("subkey2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value2"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "key1\tsubkey1\tvalue1\n"
+ "key\tsubkey\n"
+ "key2\tsubkey2\tvalue2\n";
+
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = true;
+
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrParserTest, IncorrectIncompleteRows)
+{
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = false;
+
+ EXPECT_THROW(ParseYamr("\n", GetNullYsonConsumer(), config), std::exception);
+ EXPECT_THROW(ParseYamr("key\n", GetNullYsonConsumer(), config), std::exception);
+ EXPECT_THROW(ParseYamr("key\tvalue\nkey\n", GetNullYsonConsumer(), config), std::exception);
+}
+
+TEST(TYamrParserTest, TabsInValue)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("a\tb\\tc\t"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ auto config = New<TYamrFormatConfig>();
+ TString input = "key\ta\tb\\tc\t";
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrParserTest, Escaping)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("\tkey\t"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("\n"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("a\tb\t\n"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = true;
+ config->EnableEscaping = true;
+
+ TString input = "\\tkey\\t\t\\n\ta\tb\t\\n\n";
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrParserTest, CustomSeparators)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value"));
+ EXPECT_CALL(Mock, OnEndMap());
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value2"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ auto config = New<TYamrFormatConfig>();
+ config->RecordSeparator = 'Y';
+ config->FieldSeparator = 'X';
+
+ TString input = "keyXvalueYkey2Xvalue2Y";
+ ParseYamr(input, &Mock, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TYamrLenvalParserTest, Simple)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key1"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value1"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginAttributes());
+ EXPECT_CALL(Mock, OnKeyedItem("table_index"));
+ EXPECT_CALL(Mock, OnInt64Scalar(1));
+ EXPECT_CALL(Mock, OnEndAttributes());
+ EXPECT_CALL(Mock, OnEntity());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value2"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+ , 2 * (2 * 4 + 4 + 6) + 8 // all i32 + lengths of keys
+ );
+
+ auto config = New<TYamrFormatConfig>();
+ config->Lenval = true;
+
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrLenvalParserTest, SimpleWithSubkey)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key1"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("subkey1"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value1"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("subkey2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value2"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x07\x00\x00\x00" "subkey1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x07\x00\x00\x00" "subkey2"
+ "\x06\x00\x00\x00" "value2"
+ , 2 * (3 * 4 + 4 + 7 + 6) // all i32 + lengths of keys
+ );
+
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = true;
+ config->Lenval = true;
+
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrLenvalParserTest, EmptyFields)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = TString(
+ "\x00\x00\x00\x00"
+ "\x00\x00\x00\x00"
+ "\x00\x00\x00\x00"
+ , 3 * 4
+ );
+
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = true;
+ config->Lenval = true;
+
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrLenvalParserTest, HugeLength)
+{
+ TString input = TString(
+ "\xFF\xFF\xFF\xFF"
+ "\x00\x00\x00\x00"
+ "\x00\x00\x00\x00"
+ , 3 * 4
+ );
+
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = true;
+ config->Lenval = true;
+
+ EXPECT_THROW(ParseYamr(input, GetNullYsonConsumer(), config), std::exception);
+}
+
+TEST(TYamrLenvalParserTest, SimpleEndOfMessage)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key1"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value1"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginAttributes());
+ EXPECT_CALL(Mock, OnKeyedItem("table_index"));
+ EXPECT_CALL(Mock, OnInt64Scalar(1));
+ EXPECT_CALL(Mock, OnEndAttributes());
+ EXPECT_CALL(Mock, OnEntity());
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("key2"));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar("value2"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+
+ "\xfb\xff\xff\xff" "\x02\x00\x00\x00\x00\x00\x00\x00"
+ , 2 * (2 * 4 + 4 + 6) + 8 + 12 // all i32 + lengths of keys
+ );
+
+ auto config = New<TYamrFormatConfig>();
+ config->Lenval = true;
+ config->EnableEom = true;
+
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrLenvalParserTest, EmptyFieldsWithEOM)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnKeyedItem("value"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = TString(
+ "\x00\x00\x00\x00"
+ "\x00\x00\x00\x00"
+ "\x00\x00\x00\x00"
+ "\xfb\xff\xff\xff" "\x01\x00\x00\x00\x00\x00\x00\x00"
+ , 3 * 4 + 12
+ );
+
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = true;
+ config->Lenval = true;
+ config->EnableEom = true;
+
+ ParseYamr(input, &Mock, config);
+}
+
+TEST(TYamrParserTest, IncorrectPlaceOfEOM)
+{
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = false;
+ config->Lenval = true;
+ config->EnableEom = true;
+
+ TString input1 = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\xfb\xff\xff\xff" "\x02\x00\x00\x00\x00\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+ , 2 * (2 * 4 + 4 + 6) + 8 + 12 // all i32 + lengths of keys
+ );
+
+ TString input2 = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+
+ "\xfb\xff\xff\xff" "\x02\x00\x00\x00\x00\x00\x00\x00"
+
+ "\x06\x00\x00\x00" "value2"
+ , 2 * (2 * 4 + 4 + 6) + 8 + 12 // all i32 + lengths of keys
+ );
+
+ EXPECT_THROW(ParseYamr(input1, GetNullYsonConsumer(), config), std::exception);
+ EXPECT_THROW(ParseYamr(input2, GetNullYsonConsumer(), config), std::exception);
+}
+
+TEST(TYamrParserTest, IncorrectEOM)
+{
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = false;
+ config->Lenval = true;
+ config->EnableEom = true;
+
+ // Garbage after EOM marker
+ TString input1 = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\xfb\xff\xff\xff" "\x01\x00\x00\x00\x00\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+ , 2 * (2 * 4 + 4 + 6) + 8 + 12 // all i32 + lengths of keys
+ );
+
+ // Row count mismatch
+ TString input2 = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+
+ "\xfb\xff\xff\xff" "\x03\x00\x00\x00\x00\x00\x00\x00"
+ , 2 * (2 * 4 + 4 + 6) + 8 + 12 // all i32 + lengths of keys
+ );
+
+ // Missing EOM marker
+ TString input3 = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+
+ , 2 * (2 * 4 + 4 + 6) + 8 // all i32 + lengths of keys
+ );
+
+ // Missing EOM marker with empty fields
+ TString input4 = TString(
+ "\x00\x00\x00\x00"
+ "\x00\x00\x00\x00"
+ "\x00\x00\x00\x00"
+ , 3 * 4
+ );
+
+ EXPECT_THROW(ParseYamr(input1, GetNullYsonConsumer(), config), std::exception);
+ EXPECT_THROW(ParseYamr(input2, GetNullYsonConsumer(), config), std::exception);
+ EXPECT_THROW(ParseYamr(input3, GetNullYsonConsumer(), config), std::exception);
+ EXPECT_THROW(ParseYamr(input4, GetNullYsonConsumer(), config), std::exception);
+}
+
+TEST(TYamrParserTest, UnsupportedEOMInTextMode)
+{
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = false;
+ config->Lenval = false;
+ config->EnableEom = true;
+
+ TString input = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+
+ "\xfb\xff\xff\xff" "\x02\x00\x00\x00\x00\x00\x00\x00"
+ , 2 * (2 * 4 + 4 + 6) + 8 + 12 // all i32 + lengths of keys
+ );
+
+ EXPECT_THROW(ParseYamr(input, GetNullYsonConsumer(), config), std::exception);
+}
+
+TEST(TYamrParserTest, UnexpectedEOM)
+{
+ auto config = New<TYamrFormatConfig>();
+ config->HasSubkey = false;
+ config->Lenval = true;
+ config->EnableEom = false;
+
+ TString input = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xff\xff\xff\xff" "\x01\x00\x00\x00"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+
+ "\xfb\xff\xff\xff" "\x02\x00\x00\x00\x00\x00\x00\x00"
+ , 2 * (2 * 4 + 4 + 6) + 8 + 12 // all i32 + lengths of keys
+ );
+
+ EXPECT_THROW(ParseYamr(input, GetNullYsonConsumer(), config), std::exception);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/yamr_writer_ut.cpp b/yt/yt/client/unittests/yamr_writer_ut.cpp
new file mode 100644
index 0000000000..747f542807
--- /dev/null
+++ b/yt/yt/client/unittests/yamr_writer_ut.cpp
@@ -0,0 +1,644 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/client/formats/yamr_writer.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+namespace NYT::NFormats {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NConcurrency;
+using namespace NTableClient;
+
+class TSchemalessWriterForYamrTest
+ : public ::testing::Test
+{
+protected:
+ TNameTablePtr NameTable_;
+ int KeyId_;
+ int SubkeyId_;
+ int ValueId_;
+ int TableIndexId_;
+ int RangeIndexId_;
+ int RowIndexId_;
+
+ TYamrFormatConfigPtr Config_;
+
+ IUnversionedRowsetWriterPtr Writer_;
+
+ TStringStream OutputStream_;
+
+ TSchemalessWriterForYamrTest() {
+ NameTable_ = New<TNameTable>();
+ KeyId_ = NameTable_->RegisterName("key");
+ SubkeyId_ = NameTable_->RegisterName("subkey");
+ ValueId_ = NameTable_->RegisterName("value");
+ TableIndexId_ = NameTable_->RegisterName(TableIndexColumnName);
+ RowIndexId_ = NameTable_->RegisterName(RowIndexColumnName);
+ RangeIndexId_ = NameTable_->RegisterName(RangeIndexColumnName);
+
+ Config_ = New<TYamrFormatConfig>();
+ }
+
+ void CreateStandardWriter(TControlAttributesConfigPtr controlAttributes = New<TControlAttributesConfig>())
+ {
+ Writer_ = CreateSchemalessWriterForYamr(
+ Config_,
+ NameTable_,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&OutputStream_)),
+ false, /* enableContextSaving */
+ controlAttributes,
+ 0 /* keyColumnCount */);
+ }
+};
+
+TEST_F(TSchemalessWriterForYamrTest, Simple)
+{
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+
+ // Ignore system columns.
+ row1.AddValue(MakeUnversionedInt64Value(2, TableIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(42, RowIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(1, RangeIndexId_));
+
+ // Note that key and value follow not in order.
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output =
+ "key1\tvalue1\n"
+ "key2\tvalue2\n";
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, SimpleWithSubkey)
+{
+ Config_->HasSubkey = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+ row1.AddValue(MakeUnversionedStringValue("subkey1", SubkeyId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("subkey2", SubkeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output =
+ "key1\tsubkey1\tvalue1\n"
+ "key2\tsubkey2\tvalue2\n";
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, SubkeyCouldBeSkipped)
+{
+ Config_->HasSubkey = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("key", KeyId_));
+ row.AddValue(MakeUnversionedStringValue("value", ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = "key\t\tvalue\n";
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, SubkeyCouldBeNull)
+{
+ Config_->HasSubkey = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("key", KeyId_));
+ row.AddValue(MakeUnversionedSentinelValue(EValueType::Null, SubkeyId_));
+ row.AddValue(MakeUnversionedStringValue("value", ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = "key\t\tvalue\n";
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, NonNullTerminatedStrings)
+{
+ Config_->HasSubkey = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ const char* longString = "trashkeytrashsubkeytrashvalue";
+ row.AddValue(MakeUnversionedStringValue(TStringBuf(longString + 5, 3), KeyId_));
+ row.AddValue(MakeUnversionedStringValue(TStringBuf(longString + 13, 6), SubkeyId_));
+ row.AddValue(MakeUnversionedStringValue(TStringBuf(longString + 24, 5), ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = "key\tsubkey\tvalue\n";
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, SkippedKey)
+{
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("value", ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_FALSE(Writer_->Write(rows));
+
+ EXPECT_THROW(Writer_->Close()
+ .Get()
+ .ThrowOnError(), std::exception);
+}
+
+TEST_F(TSchemalessWriterForYamrTest, SkippedValue)
+{
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("key", KeyId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_FALSE(Writer_->Write(rows));
+
+ EXPECT_THROW(Writer_->Close()
+ .Get()
+ .ThrowOnError(), std::exception);
+}
+
+TEST_F(TSchemalessWriterForYamrTest, NotStringType) {
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("key", KeyId_));
+ row.AddValue(MakeUnversionedInt64Value(42, ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_FALSE(Writer_->Write(rows));
+
+ EXPECT_THROW(Writer_->Close()
+ .Get()
+ .ThrowOnError(), std::exception);
+}
+
+TEST_F(TSchemalessWriterForYamrTest, ExtraItem)
+{
+ int trashId = NameTable_->RegisterName("trash");
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("key", KeyId_));
+ row.AddValue(MakeUnversionedStringValue("value", ValueId_));
+ // This value will be ignored.
+ row.AddValue(MakeUnversionedStringValue("trash", trashId));
+ // This value will also be ignored because Config_->HasSubkey is off,
+ // despite the fact it has non-string type.
+ row.AddValue(MakeUnversionedInt64Value(42, SubkeyId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = "key\tvalue\n";
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, Escaping)
+{
+ Config_->HasSubkey = true;
+ Config_->EnableEscaping = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("\n", KeyId_));
+ row.AddValue(MakeUnversionedStringValue("\t", SubkeyId_));
+ row.AddValue(MakeUnversionedStringValue("\n", ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = "\\n\t\\t\t\\n\n";
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, SimpleWithTableIndex)
+{
+ Config_->EnableTableIndex = true;
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ controlAttributes->EnableTableIndex = true;
+ CreateStandardWriter(controlAttributes);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+ row1.AddValue(MakeUnversionedInt64Value(42, TableIndexId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+ row2.AddValue(MakeUnversionedInt64Value(42, TableIndexId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("key3", KeyId_));
+ row3.AddValue(MakeUnversionedStringValue("value3", ValueId_));
+ row3.AddValue(MakeUnversionedInt64Value(23, TableIndexId_));
+
+ rows = { row3.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output =
+ "42\n"
+ "key1\tvalue1\n"
+ "key2\tvalue2\n"
+ "23\n"
+ "key3\tvalue3\n";
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, SimpleWithRowIndexAndTableIndex)
+{
+ Config_->EnableTableIndex = true;
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ controlAttributes->EnableTableIndex = true;
+ controlAttributes->EnableRowIndex = true;
+ CreateStandardWriter(controlAttributes);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+ row1.AddValue(MakeUnversionedInt64Value(42, TableIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(0, RowIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(0, RangeIndexId_));
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("key3", KeyId_));
+ row3.AddValue(MakeUnversionedStringValue("value3", ValueId_));
+ row3.AddValue(MakeUnversionedInt64Value(5, RowIndexId_));
+ row3.AddValue(MakeUnversionedInt64Value(1, RangeIndexId_));
+ rows = { row3.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ TUnversionedRowBuilder row4;
+ row4.AddValue(MakeUnversionedStringValue("key4", KeyId_));
+ row4.AddValue(MakeUnversionedStringValue("value4", ValueId_));
+ row4.AddValue(MakeUnversionedInt64Value(23, TableIndexId_));
+ row4.AddValue(MakeUnversionedInt64Value(10, RowIndexId_));
+ row4.AddValue(MakeUnversionedInt64Value(2, RangeIndexId_));
+ rows = { row4.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output =
+ "42\n0\n"
+ "key1\tvalue1\n"
+ "key2\tvalue2\n"
+ "42\n5\n"
+ "key3\tvalue3\n"
+ "23\n10\n"
+ "key4\tvalue4\n";
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, Lenval)
+{
+ Config_->HasSubkey = true;
+ Config_->Lenval = true;
+ CreateStandardWriter();
+
+ // Note that order in both rows is unusual.
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("subkey1", SubkeyId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+ row2.AddValue(MakeUnversionedStringValue("subkey2", SubkeyId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x07\x00\x00\x00" "subkey1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x07\x00\x00\x00" "subkey2"
+ "\x06\x00\x00\x00" "value2"
+ , 2 * (3 * 4 + 4 + 6 + 7) // all i32 + lengths of keys
+ );
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, LenvalWithEmptyFields)
+{
+ Config_->HasSubkey = true;
+ Config_->Lenval = true;
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("subkey1", SubkeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row2.AddValue(MakeUnversionedStringValue("", SubkeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("key3", KeyId_));
+ row3.AddValue(MakeUnversionedStringValue("subkey3", SubkeyId_));
+ row3.AddValue(MakeUnversionedStringValue("", ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow(), row3.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = TString(
+ "\x00\x00\x00\x00" ""
+ "\x07\x00\x00\x00" "subkey1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x00\x00\x00\x00" ""
+ "\x06\x00\x00\x00" "value2"
+
+ "\x04\x00\x00\x00" "key3"
+ "\x07\x00\x00\x00" "subkey3"
+ "\x00\x00\x00\x00" ""
+
+ , 9 * 4 + (7 + 6) + (4 + 6) + (4 + 7) // all i32 + lengths of keys
+ );
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, LenvalWithKeySwitch)
+{
+ Config_->HasSubkey = true;
+ Config_->Lenval = true;
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ controlAttributes->EnableKeySwitch = true;
+
+ Writer_ = CreateSchemalessWriterForYamr(
+ Config_,
+ NameTable_,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&OutputStream_)),
+ false, /* enableContextSaving */
+ controlAttributes,
+ 1 /* keyColumnCount */);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("subkey1", SubkeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row2.AddValue(MakeUnversionedStringValue("subkey21", SubkeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value21", ValueId_));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row3.AddValue(MakeUnversionedStringValue("subkey22", SubkeyId_));
+ row3.AddValue(MakeUnversionedStringValue("value22", ValueId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow(), row3.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ TUnversionedRowBuilder row4;
+ row4.AddValue(MakeUnversionedStringValue("key3", KeyId_));
+ row4.AddValue(MakeUnversionedStringValue("subkey3", SubkeyId_));
+ row4.AddValue(MakeUnversionedStringValue("value3", ValueId_));
+
+ rows = { row4.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output = TString(
+ "\x04\x00\x00\x00" "key1"
+ "\x07\x00\x00\x00" "subkey1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\xfe\xff\xff\xff" // key switch
+
+ "\x04\x00\x00\x00" "key2"
+ "\x08\x00\x00\x00" "subkey21"
+ "\x07\x00\x00\x00" "value21"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x08\x00\x00\x00" "subkey22"
+ "\x07\x00\x00\x00" "value22"
+
+ "\xfe\xff\xff\xff"
+
+ "\x04\x00\x00\x00" "key3"
+ "\x07\x00\x00\x00" "subkey3"
+ "\x06\x00\x00\x00" "value3"
+
+ , 14 * 4 + (4 + 7 + 6) + (4 + 8 + 7) + (4 + 8 + 7) + (4 + 7 + 6) // all i32 + lengths of keys
+ );
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, LenvalWithTableIndex)
+{
+ Config_->EnableTableIndex = true;
+ Config_->Lenval = true;
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ controlAttributes->EnableTableIndex = true;
+ CreateStandardWriter(controlAttributes);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+ row1.AddValue(MakeUnversionedInt64Value(42, TableIndexId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+ row2.AddValue(MakeUnversionedInt64Value(42, TableIndexId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("key3", KeyId_));
+ row3.AddValue(MakeUnversionedStringValue("value3", ValueId_));
+ row3.AddValue(MakeUnversionedInt64Value(23, TableIndexId_));
+
+ rows = { row3.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output(
+ "\xff\xff\xff\xff" "\x2a\x00\x00\x00" // 42
+
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+
+ "\xff\xff\xff\xff" "\x17\x00\x00\x00" // 23
+
+ "\x04\x00\x00\x00" "key3"
+ "\x06\x00\x00\x00" "value3"
+ , 10 * 4 + 3 * (4 + 6));
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+TEST_F(TSchemalessWriterForYamrTest, LenvalWithRangeAndRowIndex)
+{
+ Config_->Lenval = true;
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ controlAttributes->EnableRowIndex = true;
+ controlAttributes->EnableRangeIndex = true;
+ CreateStandardWriter(controlAttributes);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("key1", KeyId_));
+ row1.AddValue(MakeUnversionedStringValue("value1", ValueId_));
+ row1.AddValue(MakeUnversionedInt64Value(42, RangeIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(23, RowIndexId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("key2", KeyId_));
+ row2.AddValue(MakeUnversionedStringValue("value2", ValueId_));
+ row2.AddValue(MakeUnversionedInt64Value(42, RangeIndexId_));
+ row2.AddValue(MakeUnversionedInt64Value(24, RowIndexId_));
+
+ std::vector<TUnversionedRow> rows = { row1.GetRow(), row2.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ TUnversionedRowBuilder row3;
+ row3.AddValue(MakeUnversionedStringValue("key3", KeyId_));
+ row3.AddValue(MakeUnversionedStringValue("value3", ValueId_));
+ row3.AddValue(MakeUnversionedInt64Value(42, RangeIndexId_));
+ row3.AddValue(MakeUnversionedInt64Value(25, RowIndexId_));
+
+ rows = { row3.GetRow() };
+ EXPECT_EQ(true, Writer_->Write(rows));
+
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString output(
+ "\xfd\xff\xff\xff" "\x2a\x00\x00\x00" // 42
+ "\xfc\xff\xff\xff" "\x17\x00\x00\x00\x00\x00\x00\x00" // 23
+
+ "\x04\x00\x00\x00" "key1"
+ "\x06\x00\x00\x00" "value1"
+
+ "\x04\x00\x00\x00" "key2"
+ "\x06\x00\x00\x00" "value2"
+
+ "\x04\x00\x00\x00" "key3"
+ "\x06\x00\x00\x00" "value3"
+ , 11 * 4 + 3 * (4 + 6));
+
+ EXPECT_EQ(output, OutputStream_.Str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/yamred_dsv_parser_ut.cpp b/yt/yt/client/unittests/yamred_dsv_parser_ut.cpp
new file mode 100644
index 0000000000..d29c9a4df6
--- /dev/null
+++ b/yt/yt/client/unittests/yamred_dsv_parser_ut.cpp
@@ -0,0 +1,187 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/test_framework/yson_consumer_mock.h>
+
+#include <yt/yt/client/formats/yamred_dsv_parser.h>
+
+namespace NYT::NFormats {
+namespace {
+
+using namespace NYson;
+
+using ::testing::InSequence;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TYamredDsvParserTest, Simple)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key_a"));
+ EXPECT_CALL(Mock, OnStringScalar("1"));
+ EXPECT_CALL(Mock, OnKeyedItem("key_b"));
+ EXPECT_CALL(Mock, OnStringScalar("2"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey_x"));
+ EXPECT_CALL(Mock, OnStringScalar("3"));
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("5"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar("6"));
+ EXPECT_CALL(Mock, OnEndMap());
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key_a"));
+ EXPECT_CALL(Mock, OnStringScalar("7"));
+ EXPECT_CALL(Mock, OnKeyedItem("key_b"));
+ EXPECT_CALL(Mock, OnStringScalar("8"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey_x"));
+ EXPECT_CALL(Mock, OnStringScalar("9"));
+ EXPECT_CALL(Mock, OnKeyedItem("b"));
+ EXPECT_CALL(Mock, OnStringScalar("max\tignat"));
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("100"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input =
+ "1 2\t3\ta=5\tb=6\n"
+ "7 8\t9\tb=max\\tignat\ta=100\n";
+
+ auto config = New<TYamredDsvFormatConfig>();
+ config->HasSubkey = true;
+ config->KeyColumnNames.push_back("key_a");
+ config->KeyColumnNames.push_back("key_b");
+ config->SubkeyColumnNames.push_back("subkey_x");
+
+ ParseYamredDsv(input, &Mock, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TYamredDsvParserTest, EmptyField)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar(""));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("0 1"));
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("b"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "\t0 1\ta=b\n";
+
+ auto config = New<TYamredDsvFormatConfig>();
+ config->HasSubkey = true;
+ config->KeyColumnNames.push_back("key");
+ config->SubkeyColumnNames.push_back("subkey");
+
+ ParseYamredDsv(input, &Mock, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TYamredDsvParserTest, Escaping)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("\t"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("0\n1"));
+ EXPECT_CALL(Mock, OnKeyedItem("a"));
+ EXPECT_CALL(Mock, OnStringScalar("\tb\nc"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = "\\t\t0\\n1\ta=\\tb\\nc\n";
+
+ auto config = New<TYamredDsvFormatConfig>();
+ config->HasSubkey = true;
+ config->EnableEscaping = true;
+ config->KeyColumnNames.push_back("key");
+ config->SubkeyColumnNames.push_back("subkey");
+
+ ParseYamredDsv(input, &Mock, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TYamredDsvParserTest, Lenval)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("a"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("bc"));
+ EXPECT_CALL(Mock, OnKeyedItem("d"));
+ EXPECT_CALL(Mock, OnStringScalar("e"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = TString(
+ "\x01\x00\x00\x00" "a"
+ "\x02\x00\x00\x00" "bc"
+ "\x03\x00\x00\x00" "d=e"
+ , 3 * 4 + 1 + 2 + 3
+ );
+
+ auto config = New<TYamredDsvFormatConfig>();
+ config->Lenval = true;
+ config->HasSubkey = true;
+ config->KeyColumnNames.push_back("key");
+ config->SubkeyColumnNames.push_back("subkey");
+
+ ParseYamredDsv(input, &Mock, config);
+}
+
+TEST(TYamredDsvParserTest, EOM)
+{
+ StrictMock<TMockYsonConsumer> Mock;
+ InSequence dummy;
+
+ EXPECT_CALL(Mock, OnListItem());
+ EXPECT_CALL(Mock, OnBeginMap());
+ EXPECT_CALL(Mock, OnKeyedItem("key"));
+ EXPECT_CALL(Mock, OnStringScalar("a"));
+ EXPECT_CALL(Mock, OnKeyedItem("subkey"));
+ EXPECT_CALL(Mock, OnStringScalar("bc"));
+ EXPECT_CALL(Mock, OnKeyedItem("d"));
+ EXPECT_CALL(Mock, OnStringScalar("e"));
+ EXPECT_CALL(Mock, OnEndMap());
+
+ TString input = TString(
+ "\x01\x00\x00\x00" "a"
+ "\x02\x00\x00\x00" "bc"
+ "\x03\x00\x00\x00" "d=e"
+ "\xfb\xff\xff\xff" "\x01\x00\x00\x00\x00\x00\x00\x00"
+ , 3 * 4 + 1 + 2 + 3 + 12
+ );
+
+ auto config = New<TYamredDsvFormatConfig>();
+ config->Lenval = true;
+ config->EnableEom = true;
+ config->HasSubkey = true;
+ config->KeyColumnNames.push_back("key");
+ config->SubkeyColumnNames.push_back("subkey");
+
+ ParseYamredDsv(input, &Mock, config);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/yamred_dsv_writer_ut.cpp b/yt/yt/client/unittests/yamred_dsv_writer_ut.cpp
new file mode 100644
index 0000000000..3537702fe1
--- /dev/null
+++ b/yt/yt/client/unittests/yamred_dsv_writer_ut.cpp
@@ -0,0 +1,425 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <yt/yt/client/formats/yamred_dsv_writer.h>
+
+#include <yt/yt/core/concurrency/async_stream.h>
+
+#include <util/string/vector.h>
+
+#include <cstdio>
+
+
+namespace NYT::NFormats {
+namespace {
+
+typedef TVector<TString> VectorStrok;
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NConcurrency;
+using namespace NTableClient;
+
+class TSchemalessWriterForYamredDsvTest
+ : public ::testing::Test
+{
+protected:
+ TNameTablePtr NameTable_;
+ TYamredDsvFormatConfigPtr Config_;
+ IUnversionedRowsetWriterPtr Writer_;
+
+ TStringStream OutputStream_;
+
+ int KeyAId_;
+ int KeyBId_;
+ int KeyCId_;
+ int ValueXId_;
+ int ValueYId_;
+ int TableIndexId_;
+ int RangeIndexId_;
+ int RowIndexId_;
+
+ TSchemalessWriterForYamredDsvTest()
+ {
+ NameTable_ = New<TNameTable>();
+ KeyAId_ = NameTable_->RegisterName("key_a");
+ KeyBId_ = NameTable_->RegisterName("key_b");
+ KeyCId_ = NameTable_->RegisterName("key_c");
+ ValueXId_ = NameTable_->RegisterName("value_x");
+ ValueYId_ = NameTable_->RegisterName("value_y");
+ TableIndexId_ = NameTable_->RegisterName(TableIndexColumnName);
+ RowIndexId_ = NameTable_->RegisterName(RowIndexColumnName);
+ RangeIndexId_ = NameTable_->RegisterName(RangeIndexColumnName);
+ Config_ = New<TYamredDsvFormatConfig>();
+ }
+
+ void CreateStandardWriter(TControlAttributesConfigPtr controlAttributes = New<TControlAttributesConfig>())
+ {
+ Writer_ = CreateSchemalessWriterForYamredDsv(
+ Config_,
+ NameTable_,
+ CreateAsyncAdapter(static_cast<IOutputStream*>(&OutputStream_)),
+ false, /* enableContextSaving */
+ controlAttributes,
+ 0 /* keyColumnCount */);
+ }
+
+ // Splits output into key and sorted vector of values that are entries of the last YAMR column.
+ // Returns true if success (there are >= 2 values after splitting by field separator), otherwise false.
+ bool ExtractKeyValue(TString output, TString& key, VectorStrok& value, char fieldSeparator = '\t')
+ {
+ char delimiter[2] = {fieldSeparator, 0};
+ // Splitting by field separator.
+ value = SplitString(output, delimiter, 0 /* maxFields */, KEEP_EMPTY_TOKENS);
+ // We should at least have key and the rest of values.
+ if (value.size() < 2)
+ return false;
+ key = value[0];
+ value.erase(value.begin());
+ std::sort(value.begin(), value.end());
+ return true;
+ }
+
+ // The same function as previous, version with subkey.
+ bool ExtractKeySubkeyValue(TString output, TString& key, TString& subkey, VectorStrok& value, char fieldSeparator = '\t')
+ {
+ char delimiter[2] = {fieldSeparator, 0};
+ // Splitting by field separator.
+ value = SplitString(output, delimiter, 0 /* maxFields */, KEEP_EMPTY_TOKENS);
+ // We should at least have key, subkey and the rest of values.
+ if (value.size() < 3)
+ return false;
+ key = value[0];
+ subkey = value[1];
+ value.erase(value.begin(), value.end());
+ std::sort(value.begin(), value.end());
+ return true;
+ }
+
+ // Compares output and expected output ignoring the order of entries in YAMR value column.
+ void CompareKeyValue(TString output, TString expected, char recordSeparator = '\n', char fieldSeparator = '\t')
+ {
+ char delimiter[2] = {recordSeparator, 0};
+ VectorStrok outputRows = SplitString(output, delimiter, 0 /* maxFields */ , KEEP_EMPTY_TOKENS);
+ VectorStrok expectedRows = SplitString(expected, delimiter, 0 /* maxFields */, KEEP_EMPTY_TOKENS);
+ EXPECT_EQ(outputRows.size(), expectedRows.size());
+ // Since there is \n after each row, there will be an extra empty string in both vectors.
+ EXPECT_EQ(outputRows.back(), "");
+ ASSERT_EQ(expectedRows.back(), "");
+ outputRows.pop_back();
+ expectedRows.pop_back();
+
+ TString outputKey;
+ TString expectedKey;
+ VectorStrok outputValue;
+ VectorStrok expectedValue;
+ for (int rowIndex = 0; rowIndex < static_cast<int>(outputRows.size()); rowIndex++) {
+ EXPECT_TRUE(ExtractKeyValue(outputRows[rowIndex], outputKey, outputValue, fieldSeparator));
+ ASSERT_TRUE(ExtractKeyValue(expectedRows[rowIndex], expectedKey, expectedValue, fieldSeparator));
+ EXPECT_EQ(outputKey, expectedKey);
+ EXPECT_EQ(outputValue, expectedValue);
+ }
+ }
+
+ // The same function as previous, version with subkey.
+ void CompareKeySubkeyValue(TString output, TString expected, char recordSeparator = '\n', char fieldSeparator = '\t')
+ {
+ char delimiter[2] = {recordSeparator, 0};
+ VectorStrok outputRows = SplitString(output, delimiter, 0 /* maxFields */ , KEEP_EMPTY_TOKENS);
+ VectorStrok expectedRows = SplitString(expected, delimiter, 0 /* maxFields */, KEEP_EMPTY_TOKENS);
+ EXPECT_EQ(outputRows.size(), expectedRows.size());
+ // Since there is \n after each row, there will be an extra empty string in both vectors.
+ EXPECT_EQ(outputRows.back(), "");
+ ASSERT_EQ(expectedRows.back(), "");
+ outputRows.pop_back();
+ expectedRows.pop_back();
+
+ TString outputKey;
+ TString expectedKey;
+ TString outputSubkey;
+ TString expectedSubkey;
+ VectorStrok outputValue;
+ VectorStrok expectedValue;
+ for (int rowIndex = 0; rowIndex < static_cast<int>(outputRows.size()); rowIndex++) {
+ EXPECT_TRUE(ExtractKeySubkeyValue(outputRows[rowIndex], outputKey, outputSubkey, outputValue, fieldSeparator));
+ ASSERT_TRUE(ExtractKeySubkeyValue(expectedRows[rowIndex], expectedKey, expectedSubkey, expectedValue, fieldSeparator));
+ EXPECT_EQ(outputKey, expectedKey);
+ EXPECT_EQ(outputSubkey, expectedSubkey);
+ EXPECT_EQ(outputValue, expectedValue);
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, Simple)
+{
+ Config_->KeyColumnNames.emplace_back("key_a");
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("a1", KeyAId_));
+ row1.AddValue(MakeUnversionedStringValue("x", ValueXId_));
+ row1.AddValue(MakeUnversionedSentinelValue(EValueType::Null, ValueYId_));
+
+ // Ignore system columns.
+ row1.AddValue(MakeUnversionedInt64Value(2, TableIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(42, RowIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(1, RangeIndexId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("a2", KeyAId_));
+ row2.AddValue(MakeUnversionedStringValue("y", ValueYId_));
+ row2.AddValue(MakeUnversionedStringValue("b", KeyBId_));
+
+ std::vector<TUnversionedRow> rows = {row1.GetRow(), row2.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput =
+ "a1\tvalue_x=x\n"
+ "a2\tvalue_y=y\tkey_b=b\n";
+
+ TString output = OutputStream_.Str();
+
+ CompareKeyValue(expectedOutput, output);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, SimpleWithSubkey)
+{
+ Config_->HasSubkey = true;
+ Config_->KeyColumnNames.emplace_back("key_a");
+ Config_->KeyColumnNames.emplace_back("key_b");
+ Config_->SubkeyColumnNames.emplace_back("key_c");
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("a", KeyAId_));
+ row1.AddValue(MakeUnversionedStringValue("b1", KeyBId_));
+ row1.AddValue(MakeUnversionedStringValue("c", KeyCId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("a", KeyAId_));
+ row2.AddValue(MakeUnversionedStringValue("b2", KeyBId_));
+ row2.AddValue(MakeUnversionedStringValue("c", KeyCId_));
+
+ std::vector<TUnversionedRow> rows = {row1.GetRow(), row2.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput =
+ "a b1\tc\t\n"
+ "a b2\tc\t\n";
+
+ TString output = OutputStream_.Str();
+
+ CompareKeySubkeyValue(expectedOutput, output);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, Lenval)
+{
+ Config_->Lenval = true;
+ Config_->HasSubkey = true;
+ Config_->EnableTableIndex = true;
+ Config_->KeyColumnNames.emplace_back("key_a");
+ Config_->KeyColumnNames.emplace_back("key_b");
+ Config_->SubkeyColumnNames.emplace_back("key_c");
+
+ auto controlAttributes = New<TControlAttributesConfig>();
+ controlAttributes->EnableTableIndex = true;
+ controlAttributes->EnableRowIndex = true;
+ controlAttributes->EnableRangeIndex = true;
+ CreateStandardWriter(controlAttributes);
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("a", KeyAId_));
+ row1.AddValue(MakeUnversionedStringValue("b1", KeyBId_));
+ row1.AddValue(MakeUnversionedStringValue("c", KeyCId_));
+ row1.AddValue(MakeUnversionedStringValue("x", ValueXId_));
+
+ row1.AddValue(MakeUnversionedInt64Value(42, TableIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(23, RangeIndexId_));
+ row1.AddValue(MakeUnversionedInt64Value(17, RowIndexId_));
+
+ TUnversionedRowBuilder row2;
+ row2.AddValue(MakeUnversionedStringValue("a", KeyAId_));
+ row2.AddValue(MakeUnversionedStringValue("b2", KeyBId_));
+ row2.AddValue(MakeUnversionedStringValue("c", KeyCId_));
+
+ row2.AddValue(MakeUnversionedInt64Value(42, TableIndexId_));
+ row2.AddValue(MakeUnversionedInt64Value(23, RangeIndexId_));
+ row2.AddValue(MakeUnversionedInt64Value(18, RowIndexId_));
+
+ std::vector<TUnversionedRow> rows = {row1.GetRow(), row2.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput = TString(
+ "\xff\xff\xff\xff" "\x2a\x00\x00\x00" // Table index.
+ "\xfd\xff\xff\xff" "\x17\x00\x00\x00" // Range index.
+ "\xfc\xff\xff\xff" "\x11\x00\x00\x00\x00\x00\x00\x00" // Row index.
+
+ "\x04\x00\x00\x00" "a b1"
+ "\x01\x00\x00\x00" "c"
+ "\x09\x00\x00\x00" "value_x=x"
+
+ "\x04\x00\x00\x00" "a b2"
+ "\x01\x00\x00\x00" "c"
+ "\x00\x00\x00\x00" "",
+
+ 13 * 4 + 4 + 1 + 9 + 4 + 1 + 0
+ );
+
+ TString output = OutputStream_.Str();
+ EXPECT_EQ(expectedOutput, output)
+ << "expected length: " << expectedOutput.length()
+ << ", "
+ << "actual length: " << output.length();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, Escaping)
+{
+ Config_->KeyColumnNames.emplace_back("key_a");
+ Config_->KeyColumnNames.emplace_back("key_b");
+ int columnWithEscapedNameId = NameTable_->GetIdOrRegisterName("value\t_t");
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("a\n", KeyAId_));
+ row1.AddValue(MakeUnversionedStringValue("\nb\t", KeyBId_));
+ row1.AddValue(MakeUnversionedStringValue("\nva\\lue\t", columnWithEscapedNameId));
+
+ std::vector<TUnversionedRow> rows = {row1.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput = "a\\n \\nb\\t\tvalue\\t_t=\\nva\\\\lue\\t\n";
+ TString output = OutputStream_.Str();
+
+ EXPECT_EQ(expectedOutput, output);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, SkippedKey)
+{
+ Config_->KeyColumnNames.emplace_back("key_a");
+ Config_->KeyColumnNames.emplace_back("key_b");
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("b", KeyBId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_FALSE(Writer_->Write(rows));
+
+ EXPECT_THROW(Writer_->Close()
+ .Get()
+ .ThrowOnError(), std::exception);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, SkippedSubkey)
+{
+ Config_->HasSubkey = true;
+ Config_->KeyColumnNames.emplace_back("key_a");
+ Config_->SubkeyColumnNames.emplace_back("key_c");
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedStringValue("a", KeyAId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_FALSE(Writer_->Write(rows));
+
+ EXPECT_THROW(Writer_->Close()
+ .Get()
+ .ThrowOnError(), std::exception);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, NonStringValues)
+{
+ Config_->HasSubkey = true;
+ Config_->KeyColumnNames.emplace_back("key_a");
+ Config_->SubkeyColumnNames.emplace_back("key_c");
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row;
+ row.AddValue(MakeUnversionedInt64Value(-42, KeyAId_));
+ row.AddValue(MakeUnversionedUint64Value(18, KeyCId_));
+ row.AddValue(MakeUnversionedBooleanValue(true, KeyBId_));
+ row.AddValue(MakeUnversionedDoubleValue(3.14, ValueXId_));
+ row.AddValue(MakeUnversionedStringValue("yt", ValueYId_));
+
+ std::vector<TUnversionedRow> rows = { row.GetRow() };
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput = "-42\t18\tkey_b=true\tvalue_x=3.14\tvalue_y=yt\n";
+ TString output = OutputStream_.Str();
+
+ EXPECT_EQ(expectedOutput, output);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TSchemalessWriterForYamredDsvTest, ErasingSubkeyColumnsWhenHasSubkeyIsFalse)
+{
+ Config_->KeyColumnNames.emplace_back("key_a");
+ Config_->SubkeyColumnNames.emplace_back("key_b");
+ // Config->HasSubkey = false by default.
+ CreateStandardWriter();
+
+ TUnversionedRowBuilder row1;
+ row1.AddValue(MakeUnversionedStringValue("a", KeyAId_));
+ row1.AddValue(MakeUnversionedStringValue("b", KeyBId_));
+ row1.AddValue(MakeUnversionedStringValue("c", KeyCId_));
+ row1.AddValue(MakeUnversionedStringValue("x", ValueXId_));
+
+ std::vector<TUnversionedRow> rows = {row1.GetRow()};
+
+ EXPECT_EQ(true, Writer_->Write(rows));
+ Writer_->Close()
+ .Get()
+ .ThrowOnError();
+
+ TString expectedOutput = "a\tkey_c=c\tvalue_x=x\n";
+ TString output = OutputStream_.Str();
+
+ EXPECT_EQ(expectedOutput, output);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NFormats
diff --git a/yt/yt/client/unittests/ypath_ut.cpp b/yt/yt/client/unittests/ypath_ut.cpp
new file mode 100644
index 0000000000..3a81f5e43c
--- /dev/null
+++ b/yt/yt/client/unittests/ypath_ut.cpp
@@ -0,0 +1,957 @@
+#include "key_helpers.h"
+
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/ypath/rich.h>
+
+#include <yt/yt/client/chunk_client/helpers.h>
+
+#include <yt/yt/client/table_client/comparator.h>
+#include <yt/yt/client/table_client/key_bound.h>
+
+#include <yt/yt/core/yson/parser.h>
+#include <yt/yt/core/yson/writer.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/ephemeral_node_factory.h>
+#include <yt/yt/core/ytree/node.h>
+#include <yt/yt/core/ytree/tree_builder.h>
+#include <yt/yt/core/ytree/tree_visitor.h>
+#include <yt/yt/core/ytree/ypath_client.h>
+#include <yt/yt/core/ytree/ypath_service.h>
+
+#include <util/string/vector.h>
+
+namespace NYT::NYTree {
+namespace {
+
+using namespace NYson;
+using namespace NYPath;
+using namespace NChunkClient;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TYPathTest
+ : public ::testing::Test
+{
+public:
+ IYPathServicePtr RootService;
+
+ void SetUp() override
+ {
+ RootService = GetEphemeralNodeFactory()->CreateMap();
+ }
+
+ static TYsonString TextifyYson(const TYsonString& data)
+ {
+ return ConvertToYsonString(data, NYson::EYsonFormat::Text);
+ }
+
+ void Set(const TYPath& path, const TString& value)
+ {
+ SyncYPathSet(RootService, path, TYsonString(value));
+ }
+
+ void Remove(const TYPath& path)
+ {
+ SyncYPathRemove(RootService, path);
+ }
+
+ TYsonString Get(const TYPath& path)
+ {
+ return TextifyYson(SyncYPathGet(RootService, path));
+ }
+
+ std::vector<TString> List(const TYPath& path)
+ {
+ return SyncYPathList(RootService, path);
+ }
+
+ void Check(const TYPath& path, const TString& expected)
+ {
+ TYsonString output = Get(path);
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(TYsonString(expected)),
+ ConvertToNode(output)));
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TYPathTest, MapModification)
+{
+ Set("/map", "{hello=world; list=[0;a;{}]; n=1}");
+
+ Set("/map/hello", "not_world");
+ Check("", "{map={hello=not_world;list=[0;a;{}];n=1}}");
+
+ Set("/map/list/2/some", "value");
+ Check("", "{map={hello=not_world;list=[0;a;{some=value}];n=1}}");
+
+ Remove("/map/n");
+ Check("", "{map={hello=not_world;list=[0;a;{some=value}]}}");
+
+ Set("/map/list", "[]");
+ Check("", "{map={hello=not_world;list=[]}}");
+
+ Remove("/map/hello");
+ Check("", "{map={list=[]}}");
+
+ Remove("/map");
+ Check("", "{}");
+}
+
+TEST_F(TYPathTest, ListModification)
+{
+ Set("/list", "[1;2;3]");
+ Check("", "{list=[1;2;3]}");
+ Check("/list", "[1;2;3]");
+ Check("/list/0", "1");
+ Check("/list/1", "2");
+ Check("/list/2", "3");
+ Check("/list/-1", "3");
+ Check("/list/-2", "2");
+ Check("/list/-3", "1");
+
+ Set("/list/end", "4");
+ Check("/list", "[1;2;3;4]");
+
+ Set("/list/end", "5");
+ Check("/list", "[1;2;3;4;5]");
+
+ Set("/list/2", "100");
+ Check("/list", "[1;2;100;4;5]");
+
+ Set("/list/-2", "3");
+ Check("/list", "[1;2;100;3;5]");
+
+ Remove("/list/4");
+ Check("/list", "[1;2;100;3]");
+
+ Remove("/list/2");
+ Check("/list", "[1;2;3]");
+
+ Remove("/list/-1");
+ Check("/list", "[1;2]");
+
+ Set("/list/before:0", "0");
+ Check("/list", "[0;1;2]");
+
+ Set("/list/after:1", "3");
+ Check("/list", "[0;1;3;2]");
+
+ Set("/list/after:-1", "4");
+ Check("/list", "[0;1;3;2;4]");
+
+ Set("/list/before:-1", "5");
+ Check("/list", "[0;1;3;2;5;4]");
+
+ Set("/list/begin", "6");
+ Check("/list", "[6;0;1;3;2;5;4]");
+}
+
+TEST_F(TYPathTest, ListReassignment)
+{
+ Set("/list", "[a;b;c]");
+ Set("/list", "[1;2;3]");
+
+ Check("", "{list=[1;2;3]}");
+}
+
+TEST_F(TYPathTest, Clear)
+{
+ Set("/my", "{list=<type=list>[1;2];map=<type=map>{a=1;b=2}}");
+
+ Remove("/my/list/*");
+ Check("/my/list", "<type=list>[]");
+ Check("/my/list/@", "{type=list}");
+
+ Remove("/my/map/*");
+ Check("/my/map", "<type=map>{}");
+ Check("/my/map/@", "{type=map}");
+}
+
+TEST_F(TYPathTest, Ls)
+{
+ Set("", "{a={x1={y1=1}};b={x2={y2=2}};c={x3={y3=3}};d={x4={y4=4}}}");
+
+ Remove("/b");
+ Set("/e", "5");
+
+ auto result = List("");
+ std::sort(result.begin(), result.end());
+
+ std::vector<TString> expected;
+ expected.push_back("a");
+ expected.push_back("c");
+ expected.push_back("d");
+ expected.push_back("e");
+
+ EXPECT_EQ(expected, result);
+}
+
+TEST_F(TYPathTest, LsOnUnsupportedNodes)
+{
+ EXPECT_ANY_THROW({
+ Set("list", "[1; 2; 3; 4]");
+ List("list");
+ });
+
+ EXPECT_ANY_THROW({
+ Set("str", "aaa");
+ List("str");
+ });
+
+ EXPECT_ANY_THROW({
+ Set("int", "42");
+ List("int");
+ });
+
+ EXPECT_ANY_THROW({
+ Set("double", "3.14");
+ List("double");
+ });
+
+ EXPECT_ANY_THROW({
+ Set("entity", "#");
+ List("entity");
+ });
+}
+
+TEST_F(TYPathTest, Attributes)
+{
+ Set("/root", "<attr=100;mode=rw> {nodes=[1; 2]}");
+ Check("/root/@", "{attr=100;mode=rw}");
+ Check("/root/@attr", "100");
+
+ Set("/root/value", "<>500");
+ Check("/root/value", "500");
+
+ Remove("/root/@*");
+ Check("/root/@", "{}");
+
+ Remove("/root/nodes");
+ Remove("/root/value");
+ Check("", "{root={}}");
+
+ Set("/root/2", "<author=ignat> #");
+ Check("", "{root={\"2\"=<author=ignat>#}}");
+ Check("/root/2/@", "{author=ignat}");
+ Check("/root/2/@author", "ignat");
+
+ // note: empty attributes are shown when nested
+ Set("/root/3", "<dir=<file=<>-100>#>#");
+ Check("/root/3/@", "{dir=<file=<>-100>#}");
+ Check("/root/3/@dir/@", "{file=<>-100}");
+ Check("/root/3/@dir/@file", "<>-100");
+ Check("/root/3/@dir/@file/@", "{}");
+}
+
+TEST_F(TYPathTest, RemoveAll)
+{
+ // from map
+ Set("/map", "{foo=bar;key=value}");
+ Remove("/map/*");
+ Check("/map", "{}");
+
+ // from list
+ Set("/list", "[10;20;30]");
+ Remove("/list/*");
+ Check("/list", "[]");
+
+ // from attributes
+ Set("/attr", "<foo=bar;key=value>42");
+ Remove("/attr/@*");
+ Check("/attr/@", "{}");
+}
+
+TEST_F(TYPathTest, InvalidCases)
+{
+ Set("/root", "{}");
+
+ // exception when setting attributes
+ EXPECT_ANY_THROW(Set("/root/some", "[10; {key=value;foo=<attr=42a>bar}]"));
+ Check("/root", "{}");
+
+ EXPECT_ANY_THROW(Set("/a/b", "1")); // /a must exist
+ EXPECT_ANY_THROW(Set("a", "{}")); // must start with '/'
+ EXPECT_ANY_THROW(Set("/root/", "{}")); // cannot end with '/'
+ EXPECT_ANY_THROW(Set("", "[]")); // change the type of root
+ EXPECT_ANY_THROW(Remove("")); // remove the root
+ EXPECT_ANY_THROW(Get("/b")); // get non-existent path
+
+ // get non-existent attribute from non-existent node
+ EXPECT_ANY_THROW(Get("/b/@some"));
+
+ // get non-existent attribute from existent node
+ EXPECT_ANY_THROW({
+ Set("/c", "{}");
+ Get("/c/@some");
+ });
+
+ // remove non-existing child
+ EXPECT_ANY_THROW(Remove("/a"));
+}
+
+TEST_F(TYPathTest, ParseRichYPath1)
+{
+ auto path = NYPath::TRichYPath::Parse("<a=b>//home/ignat{a,b}[1:2]");
+ EXPECT_EQ(path.GetPath(), "//home/ignat");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(TYsonString(TStringBuf("{a=b;columns=[a;b]; ranges=[{upper_limit={key=[2]};lower_limit={key=[1]}}]}")))));
+}
+
+TEST_F(TYPathTest, ParseRichYPath2)
+{
+ auto path = NYPath::TRichYPath::Parse("<a=b>//home");
+ EXPECT_EQ(path.GetPath(), "//home");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(TYsonString(TStringBuf("{a=b}")))));
+}
+
+TEST_F(TYPathTest, ParseRichYPath3)
+{
+ auto path = NYPath::TRichYPath::Parse("//home");
+ EXPECT_EQ(path.GetPath(), "//home");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(EmptyAttributes())));
+}
+
+TEST_F(TYPathTest, ParseRichYPath4)
+{
+ auto path = NYPath::TRichYPath::Parse("//home[:]");
+ EXPECT_EQ(path.GetPath(), "//home");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(TYsonString(TStringBuf("{ranges=[{}]}")))));
+}
+
+TEST_F(TYPathTest, ParseRichYPath5)
+{
+ auto path = NYPath::TRichYPath::Parse("//home[(x, y):(a, b)]");
+ EXPECT_EQ(path.GetPath(), "//home");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(TYsonString(TStringBuf(
+ "{ranges=[{lower_limit={key=[x;y]};upper_limit={key=[a;b]}}]}")))));
+}
+
+TEST_F(TYPathTest, ParseRichYPath6)
+{
+ auto path = NYPath::TRichYPath::Parse("//home[#1:#2,x:y]");
+ EXPECT_EQ(path.GetPath(), "//home");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(TYsonString(TStringBuf(
+ "{ranges=["
+ "{lower_limit={row_index=1};upper_limit={row_index=2}};"
+ "{lower_limit={key=[x]};upper_limit={key=[y]}}"
+ "]}")))));
+}
+
+TEST_F(TYPathTest, ParseRichYPath7)
+{
+ auto path = NYPath::TRichYPath::Parse("//home[x:#1000]");
+ EXPECT_EQ(path.GetPath(), "//home");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(TYsonString(TStringBuf(
+ "{ranges=["
+ "{lower_limit={key=[x]};upper_limit={row_index=1000}};"
+ "]}")))));
+}
+
+TEST_F(TYPathTest, ParseRichYPath8)
+{
+ auto path = NYPath::TRichYPath::Parse(" <a=b> //home");
+ EXPECT_EQ(path.GetPath(), "//home");
+ EXPECT_TRUE(
+ AreNodesEqual(
+ ConvertToNode(path.Attributes()),
+ ConvertToNode(TYsonString(TStringBuf("{a=b}")))));
+}
+
+TEST_F(TYPathTest, ParseRichYPath9)
+{
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("@home"),
+ std::exception,
+ "does not start with a valid root-designator");
+}
+
+TEST_F(TYPathTest, ParseRichYPath10)
+{
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse(" \n <a=b>\n//home"),
+ std::exception,
+ "does not start with a valid root-designator");
+}
+
+TEST_F(TYPathTest, ParseRichYPath11)
+{
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse(" \n//home"),
+ std::exception,
+ "does not start with a valid root-designator");
+}
+
+TEST_F(TYPathTest, IgnoreAmpersand1)
+{
+ Set("&/a", "b");
+ Check("/a", "b");
+ Check("&/a", "b");
+}
+
+TEST_F(TYPathTest, IgnoreAmpersand2)
+{
+ Set("/list", "[]");
+ Set("/list&/end", "0");
+ Check("/list", "[0]");
+}
+
+TEST_F(TYPathTest, IgnoreAmpersand3)
+{
+ Set("/map", "{}");
+ Set("/map/@attr", "value");
+ Check("/map&/@attr", "value");
+}
+
+TEST_F(TYPathTest, Cluster)
+{
+ {
+ auto path = TRichYPath::Parse("mycluster://home/mytable");
+ EXPECT_EQ("//home/mytable", path.GetPath());
+ EXPECT_EQ("mycluster", path.GetCluster());
+ }
+
+ {
+ auto path = TRichYPath::Parse("<cluster=first_cluster> second_cluster://home/mytable");
+ EXPECT_EQ("//home/mytable", path.GetPath());
+ EXPECT_EQ("second_cluster", path.GetCluster());
+ }
+
+ {
+ auto path = TRichYPath::Parse(" <> mycluster://home/mytable");
+ EXPECT_EQ("//home/mytable", path.GetPath());
+ EXPECT_EQ("mycluster", path.GetCluster());
+ }
+
+ {
+ auto path = TRichYPath::Parse("long-cluster-name_with_underscores://home/long-table-name");
+ EXPECT_EQ("//home/long-table-name", path.GetPath());
+ EXPECT_EQ("long-cluster-name_with_underscores", path.GetCluster());
+ }
+
+ {
+ auto path = TRichYPath::Parse("//home/mytable");
+ EXPECT_EQ("//home/mytable", path.GetPath());
+ EXPECT_FALSE(path.GetCluster().has_value());
+ }
+
+ {
+ auto path = TRichYPath::Parse("//path:with:colons/my:table");
+ EXPECT_EQ("//path:with:colons/my:table", path.GetPath());
+ EXPECT_FALSE(path.GetCluster().has_value());
+ }
+
+ {
+ auto path = TRichYPath::Parse("//path-with-dashes/my-table");
+ EXPECT_EQ("//path-with-dashes/my-table", path.GetPath());
+ EXPECT_FALSE(path.GetCluster().has_value());
+ }
+
+ {
+ // NB: There doesn't seem to be a feasible way to check this without interpreting the actual tokens.
+ auto path = TRichYPath::Parse("replica://primary://tmp/queue");
+ EXPECT_EQ("//primary://tmp/queue", path.GetPath());
+ EXPECT_EQ("replica", path.GetCluster());
+ }
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("bad+cluster!name://home/mytable"),
+ std::exception,
+ "illegal symbol");
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(TRichYPath::Parse("://home/mytable"),
+ std::exception,
+ "cannot be empty");
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("localhost:1234://queue"),
+ std::exception,
+ "does not start with a valid root-designator");
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("pythia:markov://queue"),
+ std::exception,
+ "does not start with a valid root-designator");
+}
+
+TEST_F(TYPathTest, NewReadRanges)
+{
+ TComparator comparatorAsc1({ESortOrder::Ascending});
+ TComparator comparatorAsc2({ESortOrder::Ascending, ESortOrder::Ascending});
+ TComparator comparatorDesc1({ESortOrder::Descending});
+ TComparator comparatorDesc2({ESortOrder::Descending, ESortOrder::Descending});
+
+ auto makeRow = [&] (const std::vector<int> values) {
+ std::vector<TUnversionedValue> unversionedValues;
+ unversionedValues.reserve(values.size());
+ for (int value : values) {
+ unversionedValues.emplace_back(MakeUnversionedInt64Value(value));
+ }
+ return MakeRow(std::move(unversionedValues));
+ };
+
+ {
+ std::vector<TReadRange> ranges(1);
+ // No ranges.
+ EXPECT_EQ(
+ ranges,
+ TRichYPath::Parse("//t").GetNewRanges());
+ }
+ {
+ // Some row index ranges in short form.
+ std::vector<TReadRange> ranges(2);
+ ranges[0].LowerLimit().SetRowIndex(1);
+ ranges[0].UpperLimit().SetRowIndex(2);
+
+ ranges[1].LowerLimit().SetRowIndex(3);
+
+ EXPECT_EQ(
+ ranges,
+ TRichYPath::Parse("//t[#1, #3:]").GetNewRanges());
+ }
+
+ {
+ // Key range without provided comparator is not allowed.
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("//t[foo]").GetNewRanges(),
+ std::exception,
+ "for an unsorted object");
+ }
+
+ {
+ // Some key ranges in short form.
+ std::vector<TReadRange> ranges(2);
+ // (123,456):789
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({123, 456});
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() < makeRow({789});
+ // (424,242)
+ ranges[1].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({424, 242});
+ ranges[1].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeRow({424, 242});
+
+ auto ypath = TRichYPath::Parse("//t[(123,456):789, (424,242)]");
+
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparatorAsc2));
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparatorDesc2));
+ }
+
+ {
+ // Same key ranges in short form, but in context of shorter key prefix.
+ // Inclusiveness of some key bounds is toggled.
+ std::vector<TReadRange> ranges(2);
+ // (123,456):789
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() > makeRow({123});
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() < makeRow({789});
+ // (424,242); in ascending case it transforms into empty range.
+ ranges[1].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() > makeRow({});
+ ranges[1].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() < makeRow({});
+
+ auto ypath = TRichYPath::Parse("//t[(123,456):789, (424,242)]");
+
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparatorAsc1));
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ ypath.GetNewRanges(comparatorDesc1),
+ std::exception,
+ "Read limit key cannot be longer");
+ }
+
+ {
+ // Short key in context of a longer key prefix.
+ std::vector<TReadRange> ranges(2);
+ // (123):456
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({123});
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() < makeRow({456});
+ // (789)
+ ranges[1].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({789});
+ ranges[1].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeRow({789});
+
+ auto ypath = TRichYPath::Parse("//t[(123):456, (789)]");
+
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparatorAsc2));
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparatorDesc2));
+ }
+
+ {
+ // Some row index ranges in full form.
+ std::vector<TReadRange> ranges(2);
+ // #42:#57
+ ranges[0].LowerLimit().SetRowIndex(42);
+ ranges[0].UpperLimit().SetRowIndex(57);
+ // #123
+ ranges[1].LowerLimit().SetRowIndex(123);
+ ranges[1].UpperLimit().SetRowIndex(124);
+
+ auto ypath = TRichYPath::Parse(
+ "<ranges=["
+ "{lower_limit={row_index=42};upper_limit={row_index=57}};"
+ "{exact={row_index=123}};"
+ "]>//t");
+
+ EXPECT_EQ(ranges, ypath.GetNewRanges());
+ }
+
+ {
+ // Lower limit simultaneously with exact.
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("<ranges=[{lower_limit={};exact={}}]>//t").GetNewRanges(),
+ std::exception,
+ "Exact limit cannot be specified simultaneously");
+ }
+
+ {
+ // Key bound in exact limit.
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse(R"(<ranges=[{exact={key_bound=["<="; []]}}]>//t)").GetNewRanges(),
+ std::exception,
+ "Key bound cannot be specified in exact");
+ }
+
+ {
+ // Key and key bound together, first case is regular for backward-compatible serialization,
+ // second contains incorrect key which should be ignored.
+ std::vector<TReadRange> ranges(2);
+ // (42):
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({42});
+
+ // (57):(23)
+
+ ranges[1].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({57});
+ ranges[1].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() < makeRow({23});
+
+ auto ypath = TRichYPath::Parse(
+ "<ranges=["
+ R"({lower_limit={key_bound=[">=";[42]]; key=[42]}};)"
+ R"({lower_limit={key_bound=[">=";[57]]; key=[123;asd]}; upper_limit={key_bound=["<"; [23]]; key=[]}};)"
+ "]>//t");
+
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorAsc1));
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorDesc1));
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorAsc2));
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorDesc2));
+ }
+
+ {
+ // Key with sentinels (ok for ascending and not ok for descending).
+ std::vector<TReadRange> ranges(1);
+ // (42):(57,<type=max>#)
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({42});
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeRow({57});
+
+ auto ypath = TRichYPath::Parse("<ranges=[{lower_limit={key=[42]};upper_limit={key=[57;<type=max>#]}}]>//tmp/t");
+
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorAsc2));
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ ypath.GetNewRanges(comparatorDesc2),
+ std::exception,
+ "Sentinel values are not allowed");
+ }
+
+ {
+ // Key with sentinels (ok for ascending and not ok for descending).
+ std::vector<TReadRange> ranges(1);
+ // (42):(57,<type=max>#)
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({42});
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeRow({57});
+
+ auto ypath = TRichYPath::Parse("<ranges=[{lower_limit={key=[42]};upper_limit={key=[57;<type=max>#]}}]>//tmp/t");
+
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorAsc2));
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ ypath.GetNewRanges(comparatorDesc2),
+ std::exception,
+ "Sentinel values are not allowed");
+ }
+
+ {
+ // Correct usage of key bounds for previous case.
+ std::vector<TReadRange> ranges(1);
+ // >=(42):<=(57)
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({42});
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeRow({57});
+
+ auto ypath = TRichYPath::Parse(
+ R"(<ranges=[{lower_limit={key_bound=[">=";[42]]};upper_limit={key_bound=["<=";[57]];}}]>//tmp/t)");
+
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorAsc2));
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorDesc2));
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorAsc1));
+ EXPECT_EQ(ranges, ypath.GetNewRanges(comparatorDesc1));
+ }
+
+ {
+ std::vector<TReadRange> ranges(1);
+ // >=(42,57) with shorter comparator, i.e not ok both for ascending and descending sort order.
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({42, 57});
+
+ auto ypath = TRichYPath::Parse(R"(<ranges=[{lower_limit={key_bound=[">="; [42;57]]};}]>//tmp/t)");
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ ypath.GetNewRanges(comparatorAsc1),
+ std::exception,
+ "Key bound length must not exceed");
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ ypath.GetNewRanges(comparatorDesc1),
+ std::exception,
+ "Key bound length must not exceed");
+ }
+
+ {
+ // Exact consisting of multiple independent selectors.
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("<ranges=[{exact={row_index=42;chunk_index=23}}]>//tmp/t").GetNewRanges(),
+ std::exception,
+ "Exact read limit must have exactly one independent selector");
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("<ranges=[{exact={tablet_index=42;chunk_index=23}}]>//tmp/t").GetNewRanges(),
+ std::exception,
+ "Exact read limit must have exactly one independent selector");
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ TRichYPath::Parse("<ranges=[{exact={tablet_index=42;row_index=12;chunk_index=23}}]>//tmp/t").GetNewRanges(),
+ std::exception,
+ "Exact read limit must have exactly one independent selector");
+ }
+ {
+ // Exact consisting of tablet_index or tablet_index + row_index (which is OK).
+ std::vector<TReadRange> ranges(2);
+ // tablet_index = 2
+ ranges[0].LowerLimit().SetTabletIndex(2);
+ ranges[0].UpperLimit().SetTabletIndex(3);
+ // tablet_index = 2; row_index = 42;
+ ranges[1].LowerLimit().SetTabletIndex(2);
+ ranges[1].LowerLimit().SetRowIndex(42);
+ ranges[1].UpperLimit().SetTabletIndex(2);
+ ranges[1].UpperLimit().SetRowIndex(43);
+
+ auto ypath = TRichYPath::Parse(
+ "<ranges=["
+ "{exact={tablet_index=2}};"
+ "{exact={tablet_index=2;row_index=42}};"
+ "]>//tmp/t");
+ }
+
+ {
+ // Short key in context of a longer key prefix, long key in context of a shorter key prefix
+ // and a key of proper length but with sentinels; long form. With ascending comparator
+ // we should replace latter two ranges with an empty range.
+ std::vector<TReadRange> ranges(3);
+ // [(42)]
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeRow({42});
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeRow({42});
+ // [(42, 43, 44)]
+ ranges[1].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() > makeRow({});
+ ranges[1].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() < makeRow({});
+ // [(42, <type=max>#)]
+ ranges[2].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() > makeRow({});
+ ranges[2].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() < makeRow({});
+
+ auto ypath = TRichYPath::Parse(
+ "<ranges=["
+ "{exact={key=[42];}};"
+ "{exact={key=[42;43;44];}};"
+ "{exact={key=[42;<type=max>#];}};"
+ "]>//tmp/t");
+
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparatorAsc2));
+ }
+}
+
+TEST_F(TYPathTest, RangesTypeHintsInt64)
+{
+ // Verify that int64 key in YPath can be implicitly converted to uint64 and double.
+
+ TComparator comparator({ESortOrder::Ascending, ESortOrder::Ascending});
+
+ auto makeMixedRow = [&] (ui64 first, double second) {
+ std::vector<TUnversionedValue> unversionedValues;
+ unversionedValues.emplace_back(MakeUnversionedUint64Value(first));
+ unversionedValues.emplace_back(MakeUnversionedDoubleValue(second));
+ return MakeRow(std::move(unversionedValues));
+ };
+
+ std::vector<TReadRange> ranges(2);
+ // [(42, 43)]
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeMixedRow(42u, 43.0);
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeMixedRow(42u, 43.0);
+ // [(39, 45)]
+ ranges[1].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeMixedRow(39u, 45.0);
+ ranges[1].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeMixedRow(39u, 45.0);
+
+ std::vector<TReadRange> smallRanges{ranges[0]};
+
+ auto ypath = TRichYPath::Parse(
+ "<ranges=["
+ "{exact={key=[42;43];}};"
+ "{exact={key=[39;45];}};"
+ "]>//tmp/t");
+
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparator, {NTableClient::EValueType::Uint64, NTableClient::EValueType::Double}));
+
+ auto smallYpath = TRichYPath::Parse("//tmp/t[(42, 43)]");
+ EXPECT_EQ(
+ smallRanges,
+ smallYpath.GetNewRanges(comparator, {NTableClient::EValueType::Uint64, NTableClient::EValueType::Double}));
+}
+
+TEST_F(TYPathTest, RangesTypeHintsUint64)
+{
+ // Verify that uint64 key in YPath can be implicitly converted to int64 and double.
+
+ TComparator comparator({ESortOrder::Ascending, ESortOrder::Ascending});
+
+ auto makeMixedRow = [&] (i64 first, double second) {
+ std::vector<TUnversionedValue> unversionedValues;
+ unversionedValues.emplace_back(MakeUnversionedInt64Value(first));
+ unversionedValues.emplace_back(MakeUnversionedDoubleValue(second));
+ return MakeRow(std::move(unversionedValues));
+ };
+
+ std::vector<TReadRange> ranges(2);
+ // [(42, 43)]
+ ranges[0].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeMixedRow(42, 43.0);
+ ranges[0].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeMixedRow(42, 43.0);
+ // [(39, 45)]
+ ranges[1].LowerLimit().KeyBound() = TOwningKeyBound::FromRow() >= makeMixedRow(39, 45.0);
+ ranges[1].UpperLimit().KeyBound() = TOwningKeyBound::FromRow() <= makeMixedRow(39, 45.0);
+
+ std::vector<TReadRange> smallRanges{ranges[0]};
+
+ auto ypath = TRichYPath::Parse(
+ "<ranges=["
+ "{exact={key=[42u;43u];}};"
+ "{exact={key=[39u;45u];}};"
+ "]>//tmp/t");
+
+ EXPECT_EQ(
+ ranges,
+ ypath.GetNewRanges(comparator, {NTableClient::EValueType::Int64, NTableClient::EValueType::Double}));
+
+ auto smallYpath = TRichYPath::Parse("//tmp/t[(42u, 43u)]");
+ EXPECT_EQ(
+ smallRanges,
+ smallYpath.GetNewRanges(comparator, {NTableClient::EValueType::Int64, NTableClient::EValueType::Double}));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRichYPathToStringTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<TString>
+{ };
+
+TEST_P(TRichYPathToStringTest, TestRichYPathToString)
+{
+ auto path = NYPath::TRichYPath::Parse(GetParam());
+ auto newPathString = ToString(path);
+ auto parsedPath = NYPath::TRichYPath::Parse(newPathString);
+ EXPECT_EQ(path.GetPath(), parsedPath.GetPath());
+ EXPECT_TRUE(path.Attributes() == parsedPath.Attributes());
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ TRichYPathToStringTest,
+ TRichYPathToStringTest,
+ ::testing::Values(
+ "//home/ignat",
+ "<a=b>//home",
+ "<a=b>//home/ignat{a,b}[1:2]",
+ "//home[#1:#2,x:y]",
+ "<a=b;c=d>//home{a,b}[(x, y):(a, b),#1:#2]",
+ "<ranges={lower_limit={chunk_index=10}}>//home/ignat/my_table{}",
+ "<a=b; columns=[key1;key2;key3]>//tmp/[0, (3, abc, true), :12u]"
+));
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TEmbeddedYPathOpsTest
+ : public ::testing::Test
+{
+public:
+ static INodePtr ParseNode(const TString& data)
+ {
+ return ConvertToNode(TYsonString(data));
+ }
+
+ static void ExpectEqual(INodePtr node, const TString& ysonString)
+ {
+ EXPECT_EQ(ConvertToYsonString(node, EYsonFormat::Text).AsStringBuf(), ysonString);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TEmbeddedYPathOpsTest, SimpleMap)
+{
+ const auto node = ParseNode(R""({foo={bar="baz"}})"");
+ ExpectEqual(GetNodeByYPath(node, "/foo/bar"), R""("baz")"");
+
+ EXPECT_THROW(GetNodeByYPath(node, "/foo/qux"), std::exception);
+ EXPECT_THROW(GetNodeByYPath(node, "/foo/bar/qux"), std::exception);
+}
+
+TEST_F(TEmbeddedYPathOpsTest, SimpleList)
+{
+ const auto node = ParseNode(R""({home={roizner={list=[100; 500; {foo=bar}; 42]}}})"");
+ ExpectEqual(GetNodeByYPath(node, "/home/roizner/list/1"), "500");
+ ExpectEqual(GetNodeByYPath(node, "/home/roizner/list/-1"), "42");
+ ExpectEqual(GetNodeByYPath(node, "/home/roizner/list/2/foo"), R""("bar")"");
+
+ EXPECT_THROW(GetNodeByYPath(node, "/home/roizner/list/4"), std::exception);
+ EXPECT_THROW(GetNodeByYPath(node, "/home/roizner/list/-5"), std::exception);
+}
+
+TEST_F(TEmbeddedYPathOpsTest, attributes)
+{
+ const auto node = ParseNode(R""({home=<account=sys>{dir1=<account=root;user_attr=<omg="embedded attributes">{foo=bar}>{};dir2={}}})"");
+ ExpectEqual(GetNodeByYPath(node, "/home/dir1/@account"), R""("root")"");
+ ExpectEqual(GetNodeByYPath(node, "/home/dir1/@user_attr/foo"), R""("bar")"");
+ ExpectEqual(GetNodeByYPath(node, "/home/dir1/@user_attr/@omg"), R""("embedded attributes")"");
+ ExpectEqual(GetNodeByYPath(node, "/home/dir1/@"), R""({"account"="root";"user_attr"=<"omg"="embedded attributes";>{"foo"="bar";};})"");
+
+ EXPECT_THROW(GetNodeByYPath(node, "/home/dir1/@user_attr/bar"), std::exception);
+ EXPECT_THROW(GetNodeByYPath(node, "/home/dir2/@account"), std::exception);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NYTree
diff --git a/yt/yt/client/unittests/yson_helpers.cpp b/yt/yt/client/unittests/yson_helpers.cpp
new file mode 100644
index 0000000000..669585caf7
--- /dev/null
+++ b/yt/yt/client/unittests/yson_helpers.cpp
@@ -0,0 +1,29 @@
+#include "yson_helpers.h"
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/node.h>
+#include <yt/yt/core/yson/string.h>
+
+namespace NYT {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString CanonizeYson(TStringBuf input)
+{
+ auto node = ConvertToNode(TYsonString(input));
+ auto binaryYson = ConvertToYsonString(node);
+
+ TStringStream out;
+ {
+ TYsonWriter writer(&out, NYson::EYsonFormat::Pretty);
+ ParseYsonStringBuffer(binaryYson.AsStringBuf(), EYsonType::Node, &writer);
+ }
+ return out.Str();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/client/unittests/yson_helpers.h b/yt/yt/client/unittests/yson_helpers.h
new file mode 100644
index 0000000000..3cab460345
--- /dev/null
+++ b/yt/yt/client/unittests/yson_helpers.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <util/generic/string.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString CanonizeYson(TStringBuf yson);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT \ No newline at end of file
diff --git a/yt/yt/client/unittests/zookeeper_bus_ut.cpp b/yt/yt/client/unittests/zookeeper_bus_ut.cpp
new file mode 100644
index 0000000000..559f503037
--- /dev/null
+++ b/yt/yt/client/unittests/zookeeper_bus_ut.cpp
@@ -0,0 +1,165 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/zookeeper/packet.h>
+
+#include <yt/yt/core/bus/bus.h>
+#include <yt/yt/core/bus/client.h>
+#include <yt/yt/core/bus/server.h>
+
+#include <yt/yt/core/bus/tcp/config.h>
+#include <yt/yt/core/bus/tcp/client.h>
+#include <yt/yt/core/bus/tcp/server.h>
+
+#include <yt/yt/core/net/socket.h>
+
+#include <library/cpp/testing/common/network.h>
+
+#include <library/cpp/yt/threading/event_count.h>
+
+namespace NYT::NZookeeper {
+namespace {
+
+using namespace NBus;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TSharedRefArray CreateMessage(int size)
+{
+ auto data = TSharedMutableRef::Allocate(size);
+ return TSharedRefArray(TSharedRef(data));
+}
+
+TSharedRefArray Serialize(TString message)
+{
+ return TSharedRefArray(TSharedRef::FromString(message));
+}
+
+TString Deserialize(TSharedRefArray message)
+{
+ YT_ASSERT(message.Size() == 1);
+ const auto& part = message[0];
+ return TString(part.Begin(), part.Size());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TReplyingBusHandler
+ : public IMessageHandler
+{
+public:
+ TReplyingBusHandler(TString message)
+ : Message_(std::move(message))
+ { }
+
+ void HandleMessage(
+ TSharedRefArray message,
+ IBusPtr replyBus) noexcept override
+ {
+ EXPECT_EQ(1, std::ssize(message));
+ auto replyMessage = Serialize(Message_);
+ replyBus->Send(replyMessage, NBus::TSendOptions(EDeliveryTrackingLevel::None));
+ }
+
+private:
+ const TString Message_;
+};
+
+class TCheckingBusHandler
+ : public IMessageHandler
+{
+public:
+ explicit TCheckingBusHandler(int numRepliesWaiting, TString message)
+ : Message_(std::move(message))
+ , NumRepliesWaiting(numRepliesWaiting)
+ { }
+
+ void WaitUntilDone()
+ {
+ Event_.Wait();
+ }
+
+private:
+ const TString Message_;
+
+ std::atomic<int> NumRepliesWaiting;
+ NThreading::TEvent Event_;
+
+ void HandleMessage(
+ TSharedRefArray message,
+ IBusPtr /*replyBus*/) noexcept override
+ {
+ auto value = Deserialize(message);
+ EXPECT_EQ(Message_, value);
+
+ if (--NumRepliesWaiting == 0) {
+ Event_.NotifyAll();
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TZookeeperBusTest
+ : public testing::Test
+{
+public:
+ NTesting::TPortHolder Port;
+ TString Address;
+
+ TZookeeperBusTest()
+ {
+ Port = NTesting::GetFreePort();
+ Address = Format("localhost:%v", Port);
+ }
+
+ IBusServerPtr StartBusServer(IMessageHandlerPtr handler)
+ {
+ auto config = TBusServerConfig::CreateTcp(Port);
+ auto server = CreateBusServer(
+ config,
+ GetZookeeperPacketTranscoderFactory());
+ server->Start(handler);
+ return server;
+ }
+
+ void TestReplies(int numRequests, const TString& message)
+ {
+ auto server = StartBusServer(New<TReplyingBusHandler>(message));
+ auto client = CreateBusClient(
+ TBusClientConfig::CreateTcp(Address),
+ GetZookeeperPacketTranscoderFactory());
+ auto handler = New<TCheckingBusHandler>(numRequests, message);
+ auto bus = client->CreateBus(handler);
+
+ std::vector<TFuture<void>> results;
+ for (int i = 0; i < numRequests; ++i) {
+ if (auto result = bus->Send(CreateMessage(10), NBus::TSendOptions(EDeliveryTrackingLevel::None))) {
+ results.push_back(result);
+ }
+ }
+
+ for (const auto& result : results) {
+ auto error = result.Get();
+ EXPECT_TRUE(error.IsOK());
+ }
+
+ handler->WaitUntilDone();
+
+ server->Stop()
+ .Get()
+ .ThrowOnError();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TZookeeperBusTest, Simple)
+{
+ TestReplies(1, "42");
+ TestReplies(100, "abacaba");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/client/unittests/zookeeper_protocol_ut.cpp b/yt/yt/client/unittests/zookeeper_protocol_ut.cpp
new file mode 100644
index 0000000000..03539fc3c0
--- /dev/null
+++ b/yt/yt/client/unittests/zookeeper_protocol_ut.cpp
@@ -0,0 +1,106 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/client/zookeeper/protocol.h>
+
+namespace NYT::NZookeeper {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TZookeeperProtocolTest, Simple)
+{
+ const TString LongString{100'000, 'a'};
+
+ auto writer = CreateZookeeperProtocolWriter();
+ writer->WriteByte('$');
+ writer->WriteInt(42);
+ writer->WriteInt(std::numeric_limits<int>::min());
+ writer->WriteInt(std::numeric_limits<int>::max());
+ writer->WriteLong(123);
+ writer->WriteLong(std::numeric_limits<i64>::min());
+ writer->WriteLong(std::numeric_limits<i64>::max());
+ writer->WriteBool(false);
+ writer->WriteBool(true);
+ writer->WriteString("abacaba");
+ writer->WriteString(LongString);
+ writer->WriteString("");
+
+ auto data = writer->Finish();
+ auto reader = CreateZookeeperProtocolReader(std::move(data));
+ EXPECT_EQ('$', reader->ReadByte());
+ EXPECT_EQ(42, reader->ReadInt());
+ EXPECT_EQ(std::numeric_limits<int>::min(), reader->ReadInt());
+ EXPECT_EQ(std::numeric_limits<int>::max(), reader->ReadInt());
+ EXPECT_EQ(123, reader->ReadLong());
+ EXPECT_EQ(std::numeric_limits<i64>::min(), reader->ReadLong());
+ EXPECT_EQ(std::numeric_limits<i64>::max(), reader->ReadLong());
+ EXPECT_EQ(false, reader->ReadBool());
+ EXPECT_EQ(true, reader->ReadBool());
+ EXPECT_EQ("abacaba", reader->ReadString());
+ EXPECT_EQ(LongString, reader->ReadString());
+
+ EXPECT_FALSE(reader->IsFinished());
+ EXPECT_THROW_WITH_SUBSTRING(reader->ValidateFinished(), "Expected end of stream");
+
+ TString longString;
+ longString.resize(1'000'000);
+ reader->ReadString(&longString);
+ EXPECT_EQ(LongString, LongString);
+
+ EXPECT_TRUE(reader->IsFinished());
+ reader->ValidateFinished();
+}
+
+TEST(TZookeeperProtocolTest, WriterReallocation)
+{
+ constexpr int Count = 954023;
+
+ auto writer = CreateZookeeperProtocolWriter();
+ for (int i = 0; i < Count; ++i) {
+ writer->WriteLong(i);
+ }
+
+ auto data = writer->Finish();
+ EXPECT_EQ(8u * Count, data.Size());
+
+ auto reader = CreateZookeeperProtocolReader(std::move(data));
+ for (int i = 0; i < Count; ++i) {
+ EXPECT_EQ(i, reader->ReadLong());
+ }
+ reader->ValidateFinished();
+}
+
+TEST(TZookeeperProtocolTest, CorruptedInput)
+{
+ {
+ auto reader = CreateZookeeperProtocolReader(TSharedRef::MakeEmpty());
+ EXPECT_THROW_WITH_SUBSTRING(reader->ReadInt(), "Premature end of stream");
+ EXPECT_THROW_WITH_SUBSTRING(reader->ReadString(), "Premature end of stream");
+ }
+ {
+ auto writer = CreateZookeeperProtocolWriter();
+ writer->WriteString(TString{100'000, 'a'});
+ auto data = writer->Finish();
+ data = data.Slice(0, 12345);
+ auto reader = CreateZookeeperProtocolReader(std::move(data));
+ EXPECT_THROW_WITH_SUBSTRING(reader->ReadString(), "Premature end of stream");
+ }
+ {
+ auto writer = CreateZookeeperProtocolWriter();
+ writer->WriteLong(1234);
+ writer->WriteLong(std::numeric_limits<i64>::max());
+ writer->WriteLong('a');
+ writer->WriteLong('b');
+
+ auto data = writer->Finish();
+ auto reader = CreateZookeeperProtocolReader(std::move(data));
+ EXPECT_EQ(1234, reader->ReadLong());
+ // NB: Offset + Size may overflow here if implemented carelessly.
+ EXPECT_THROW_WITH_SUBSTRING(reader->ReadString(), "Premature end of stream");
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/client/ya.make b/yt/yt/client/ya.make
new file mode 100644
index 0000000000..7025b59424
--- /dev/null
+++ b/yt/yt/client/ya.make
@@ -0,0 +1,221 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+INCLUDE(../RpcProxyProtocolVersion.txt)
+
+PROTO_NAMESPACE(yt)
+
+SRCS(
+ api/config.cpp
+ api/client.cpp
+ api/client_common.cpp
+ api/client_cache.cpp
+ api/delegating_client.cpp
+ api/etc_client.cpp
+ api/journal_client.cpp
+ api/operation_client.cpp
+ api/security_client.cpp
+ api/table_client.cpp
+ api/query_tracker_client.cpp
+ api/helpers.cpp
+ api/internal_client.cpp
+ api/operation_archive_schema.cpp
+ api/public.cpp
+ api/rowset.cpp
+ api/skynet.cpp
+ api/transaction.cpp
+ api/persistent_queue.cpp
+ api/sticky_transaction_pool.cpp
+
+ api/rpc_proxy/address_helpers.cpp
+ api/rpc_proxy/public.cpp
+ api/rpc_proxy/config.cpp
+ api/rpc_proxy/helpers.cpp
+ api/rpc_proxy/client_impl.cpp
+ api/rpc_proxy/client_base.cpp
+ api/rpc_proxy/connection.cpp
+ api/rpc_proxy/connection_impl.cpp
+ api/rpc_proxy/file_reader.cpp
+ api/rpc_proxy/file_writer.cpp
+ api/rpc_proxy/journal_reader.cpp
+ api/rpc_proxy/journal_writer.cpp
+ api/rpc_proxy/table_mount_cache.cpp
+ api/rpc_proxy/table_reader.cpp
+ api/rpc_proxy/table_writer.cpp
+ api/rpc_proxy/timestamp_provider.cpp
+ api/rpc_proxy/transaction.cpp
+ api/rpc_proxy/transaction_impl.cpp
+ api/rpc_proxy/row_stream.cpp
+ api/rpc_proxy/wire_row_stream.cpp
+
+ election/public.cpp
+
+ hive/timestamp_map.cpp
+
+ hydra/version.cpp
+
+ chaos_client/helpers.cpp
+ chaos_client/replication_card.cpp
+ chaos_client/replication_card_cache.cpp
+ chaos_client/replication_card_serialization.cpp
+
+ chunk_client/chunk_replica.cpp
+ chunk_client/config.cpp
+ chunk_client/data_statistics.cpp
+ chunk_client/helpers.cpp
+ chunk_client/public.cpp
+ chunk_client/read_limit.cpp
+ chunk_client/ready_event_reader_base.cpp
+
+ converters/boolean_converter.cpp
+ converters/converter.cpp
+ converters/floating_point_converter.cpp
+ converters/helper.cpp
+ converters/integer_converter.cpp
+ converters/null_converter.cpp
+ converters/string_converter.cpp
+
+ journal_client/public.cpp
+ journal_client/config.cpp
+
+ cypress_client/public.cpp
+
+ node_tracker_client/node_directory.cpp
+ node_tracker_client/helpers.cpp
+ node_tracker_client/public.cpp
+
+ object_client/public.cpp
+ object_client/helpers.cpp
+
+ scheduler/operation_id_or_alias.cpp
+ scheduler/operation_cache.cpp
+
+ security_client/acl.cpp
+ security_client/access_control.cpp
+ security_client/public.cpp
+ security_client/helpers.cpp
+
+ table_client/public.cpp
+ table_client/adapters.cpp
+ table_client/table_output.cpp
+ table_client/blob_reader.cpp
+ table_client/check_schema_compatibility.cpp
+ table_client/chunk_stripe_statistics.cpp
+ table_client/config.cpp
+ table_client/column_rename_descriptor.cpp
+ table_client/column_sort_schema.cpp
+ table_client/comparator.cpp
+ table_client/key.cpp
+ table_client/key_bound.cpp
+ table_client/key_bound_compressor.cpp
+ table_client/pipe.cpp
+ table_client/versioned_row.cpp
+ table_client/unversioned_row.cpp
+ table_client/unversioned_value.cpp
+ table_client/versioned_reader.cpp
+ table_client/row_base.cpp
+ table_client/row_batch.cpp
+ table_client/row_buffer.cpp
+ table_client/schema.cpp
+ table_client/schema_serialization_helpers.cpp
+ table_client/serialize.cpp
+ table_client/logical_type.cpp
+ table_client/name_table.cpp
+ table_client/wire_protocol.cpp
+ table_client/columnar_statistics.cpp
+ table_client/helpers.cpp
+ table_client/value_consumer.cpp
+ table_client/table_consumer.cpp
+ table_client/schemaless_row_reorderer.cpp
+ table_client/unordered_schemaful_reader.cpp
+ table_client/validate_logical_type.cpp
+ table_client/composite_compare.cpp
+ table_client/columnar.cpp
+ table_client/record_codegen_cpp.cpp
+ table_client/record_helpers.cpp
+
+ tablet_client/config.cpp
+ tablet_client/table_mount_cache_detail.cpp
+ tablet_client/table_mount_cache.cpp
+ tablet_client/public.cpp
+ tablet_client/helpers.cpp
+
+ queue_client/common.cpp
+ queue_client/config.cpp
+ queue_client/consumer_client.cpp
+ queue_client/partition_reader.cpp
+ queue_client/queue_rowset.cpp
+
+ ypath/rich.cpp
+ ypath/parser_detail.cpp
+
+ transaction_client/batching_timestamp_provider.cpp
+ transaction_client/config.cpp
+ transaction_client/helpers.cpp
+ transaction_client/noop_timestamp_provider.cpp
+ transaction_client/remote_timestamp_provider.cpp
+ transaction_client/timestamp_provider_base.cpp
+
+ misc/config.cpp
+ misc/io_tags.cpp
+ misc/method_helpers.cpp
+ misc/workload.cpp
+
+ job_tracker_client/public.cpp
+ job_tracker_client/helpers.cpp
+
+ query_client/query_builder.cpp
+ query_client/query_statistics.cpp
+
+ complex_types/check_yson_token.cpp
+ complex_types/check_type_compatibility.cpp
+ complex_types/infinite_entity.cpp
+ complex_types/yson_format_conversion.cpp
+ complex_types/uuid_text.cpp
+ complex_types/time_text.cpp
+
+ zookeeper/packet.cpp
+ zookeeper/protocol.cpp
+ zookeeper/requests.cpp
+)
+
+SRCS(
+ ${YT_SRCS}
+ yt/yt/client/api/rpc_proxy/protocol_version_variables.h.in
+)
+
+PEERDIR(
+ yt/yt/client/query_tracker_client
+ yt/yt/core
+ yt/yt/core/http
+ yt/yt/library/auth
+ yt/yt/library/decimal
+ yt/yt/library/re2
+ yt/yt/library/erasure
+ yt/yt/library/numeric
+ yt/yt/library/quantile_digest
+ yt/yt_proto/yt/client
+ library/cpp/json
+ contrib/libs/pfr
+)
+
+END()
+
+RECURSE(
+ arrow
+ driver
+ federated
+ hedging
+)
+
+RECURSE_FOR_TESTS(
+ table_client/unittests
+ unittests
+)
+
+IF (NOT OPENSOURCE)
+ RECURSE(
+ table_client/benchmark
+ )
+ENDIF()
diff --git a/yt/yt/client/ypath/parser_detail.cpp b/yt/yt/client/ypath/parser_detail.cpp
new file mode 100644
index 0000000000..996dd52159
--- /dev/null
+++ b/yt/yt/client/ypath/parser_detail.cpp
@@ -0,0 +1,460 @@
+#include "parser_detail.h"
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+
+#include <yt/yt/core/ypath/tokenizer.h>
+
+#include <yt/yt/core/misc/parser_helpers.h>
+
+#include <yt/yt/core/yson/token.h>
+#include <yt/yt/core/yson/tokenizer.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NYPath {
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const NYson::ETokenType BeginColumnSelectorToken = NYson::ETokenType::LeftBrace;
+const NYson::ETokenType EndColumnSelectorToken = NYson::ETokenType::RightBrace;
+const NYson::ETokenType ColumnSeparatorToken = NYson::ETokenType::Comma;
+const NYson::ETokenType BeginRowSelectorToken = NYson::ETokenType::LeftBracket;
+const NYson::ETokenType EndRowSelectorToken = NYson::ETokenType::RightBracket;
+const NYson::ETokenType RowIndexMarkerToken = NYson::ETokenType::Hash;
+const NYson::ETokenType BeginTupleToken = NYson::ETokenType::LeftParenthesis;
+const NYson::ETokenType EndTupleToken = NYson::ETokenType::RightParenthesis;
+const NYson::ETokenType KeySeparatorToken = NYson::ETokenType::Comma;
+const NYson::ETokenType RangeToken = NYson::ETokenType::Colon;
+const NYson::ETokenType RangeSeparatorToken = NYson::ETokenType::Comma;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ThrowUnexpectedToken(const TToken& token)
+{
+ THROW_ERROR_EXCEPTION("Unexpected token %Qv",
+ token);
+}
+
+TString ParseAttributes(const TString& str, const IAttributeDictionaryPtr& attributes)
+{
+ int spaceCount = 0;
+ {
+ size_t index = 0;
+ while (index < str.size() && IsSpace(str[index])) {
+ ++index;
+ }
+ if (index == str.size() || str[index] != TokenTypeToChar(NYson::ETokenType::LeftAngle)) {
+ return str;
+ }
+ spaceCount = index;
+ }
+
+ NYson::TTokenizer tokenizer(TStringBuf(str).SubStr(spaceCount));
+ tokenizer.ParseNext();
+ if (tokenizer.CurrentToken().GetType() != NYson::ETokenType::LeftAngle) {
+ ThrowUnexpectedToken(tokenizer.CurrentToken());
+ }
+
+ int depth = 0;
+ int attrStartPosition = spaceCount + 1;
+
+ while (true) {
+ switch (tokenizer.CurrentToken().GetType()) {
+ case NYson::ETokenType::LeftAngle:
+ ++depth;
+ break;
+ case NYson::ETokenType::RightAngle:
+ --depth;
+ break;
+ default:
+ break;
+ }
+
+ if (depth == 0) {
+ break;
+ }
+
+ if (!tokenizer.ParseNext()) {
+ THROW_ERROR_EXCEPTION("Unmatched '<' in YPath");
+ }
+ }
+
+ int attrEndPosition = spaceCount + tokenizer.GetPosition() - 1;
+ int pathStartPosition = attrEndPosition + 1;
+
+ TYsonString attrYson(
+ str.substr(attrStartPosition, attrEndPosition - attrStartPosition),
+ NYson::EYsonType::MapFragment);
+ attributes->MergeFrom(*ConvertToAttributes(attrYson));
+
+ return TrimLeadingWhitespaces(str.substr(pathStartPosition));
+}
+
+bool IsValidClusterSymbol(char c)
+{
+ return IsAsciiAlnum(c) || c == '-' || c == '_';
+}
+
+bool IsRootDesignator(char c)
+{
+ return c == '/' || c == '#';
+}
+
+bool StartsWithRootDesignator(TStringBuf str)
+{
+ size_t nonSpaceIndex = str.find_first_not_of(' ');
+ if (nonSpaceIndex != TStringBuf::npos && !IsRootDesignator(str[nonSpaceIndex])) {
+ return false;
+ }
+
+ return true;
+}
+
+TString ParseCluster(TString str, const IAttributeDictionaryPtr& attributes)
+{
+ if (str.empty()) {
+ return str;
+ }
+
+ if (StartsWithRootDesignator(str)) {
+ return str;
+ }
+
+ auto clusterSeparatorIndex = str.find_first_of(':');
+ if (clusterSeparatorIndex == TString::npos) {
+ THROW_ERROR_EXCEPTION(
+ "Path %Qv does not start with a valid root-designator, cluster://path short-form assumed; "
+ "no \':\' separator symbol found to parse cluster",
+ str);
+ }
+
+ const auto clusterName = str.substr(0, clusterSeparatorIndex);
+
+ if (clusterName.empty()) {
+ THROW_ERROR_EXCEPTION(
+ "Path %Qv does not start with a valid root-designator, cluster://path short-form assumed; "
+ "cluster name cannot be empty",
+ str);
+ }
+
+ auto illegalSymbolIt = std::find_if_not(clusterName.begin(), clusterName.end(), &IsValidClusterSymbol);
+ if (illegalSymbolIt != clusterName.end()) {
+ THROW_ERROR_EXCEPTION(
+ "Path %Qv does not start with a valid root-designator, cluster://path short-form assumed; "
+ "cluster name contains illegal symbol %Qv",
+ str,
+ *illegalSymbolIt);
+ }
+
+ auto remainingString = str.substr(clusterSeparatorIndex + 1);
+
+ if (!StartsWithRootDesignator(remainingString)) {
+ THROW_ERROR_EXCEPTION(
+ "Path %Qv does not start with a valid root-designator, cluster://path short-form assumed; "
+ "path %Qv after cluster-separator does not start with a valid root-designator",
+ str,
+ remainingString);
+ }
+
+ attributes->Set("cluster", clusterName);
+ return remainingString;
+
+
+ // NB. If the path had attributes, then the leading spaces must be removed in preceding ParseAttributes()
+ // call. We can encounter the path with leading spaces here if it didn't have attributes and passed through
+ // ParseAttributes() unchanged. In this case, the path is most likely incorrect, so it returns unchanged from
+ // this function. For example, " <> cluster://path" will become "cluster://path" after call to ParseAttributes(),
+ // and "//path" after this function. But " cluster://path" will pass unchanged throughout ParseAttributes(), and
+ // will remain " cluster://path" after this function.
+ size_t index = 0;
+ size_t clusterStart = index;
+ while (index < str.size() && (IsAsciiAlnum(str[index]) || str[index] == '-' || str[index] == '_')) {
+ ++index;
+ }
+ if (index >= str.size() || str[index] != ':') {
+ // Not a cluster name, so return the string as-is.
+ return str;
+ }
+ size_t clusterEnd = index;
+ if (clusterStart == clusterEnd) {
+ THROW_ERROR_EXCEPTION("Cluster name cannot be empty");
+ }
+ attributes->Set("cluster", str.substr(clusterStart, clusterEnd - clusterStart));
+ ++index;
+ return str.substr(index);
+}
+
+void ParseColumns(NYson::TTokenizer& tokenizer, IAttributeDictionary* attributes)
+{
+ if (tokenizer.GetCurrentType() != BeginColumnSelectorToken) {
+ return;
+ }
+
+ std::vector<TString> columns;
+
+ tokenizer.ParseNext();
+ while (tokenizer.GetCurrentType() != EndColumnSelectorToken) {
+ TString begin;
+ switch (tokenizer.GetCurrentType()) {
+ case NYson::ETokenType::String:
+ begin.assign(tokenizer.CurrentToken().GetStringValue());
+ tokenizer.ParseNext();
+ break;
+ default:
+ ThrowUnexpectedToken(tokenizer.CurrentToken());
+ YT_ABORT();
+ }
+
+ columns.push_back(begin);
+
+ switch (tokenizer.GetCurrentType()) {
+ case ColumnSeparatorToken:
+ tokenizer.ParseNext();
+ break;
+ case EndColumnSelectorToken:
+ break;
+ default:
+ ThrowUnexpectedToken(tokenizer.CurrentToken());
+ YT_ABORT();
+ }
+ }
+ tokenizer.ParseNext();
+
+ attributes->Set("columns", ConvertToYsonString(columns));
+}
+
+void ParseKeyPart(
+ NYson::TTokenizer& tokenizer,
+ TUnversionedOwningRowBuilder* rowBuilder)
+{
+ // We don't fill id here, because key part columns are well known.
+ // Also we don't have a name table for them :)
+ TUnversionedValue value;
+
+ switch (tokenizer.GetCurrentType()) {
+ case NYson::ETokenType::String: {
+ auto str = tokenizer.CurrentToken().GetStringValue();
+ value = MakeUnversionedStringValue(str);
+ break;
+ }
+
+ case NYson::ETokenType::Int64: {
+ value = MakeUnversionedInt64Value(tokenizer.CurrentToken().GetInt64Value());
+ break;
+ }
+
+ case NYson::ETokenType::Uint64: {
+ value = MakeUnversionedUint64Value(tokenizer.CurrentToken().GetUint64Value());
+ break;
+ }
+
+ case NYson::ETokenType::Double: {
+ value = MakeUnversionedDoubleValue(tokenizer.CurrentToken().GetDoubleValue());
+ break;
+ }
+
+ case NYson::ETokenType::Boolean: {
+ value = MakeUnversionedBooleanValue(tokenizer.CurrentToken().GetBooleanValue());
+ break;
+ }
+
+ case NYson::ETokenType::Hash: {
+ value = MakeUnversionedSentinelValue(EValueType::Null);
+ break;
+ }
+
+ default:
+ ThrowUnexpectedToken(tokenizer.CurrentToken());
+ break;
+ }
+ rowBuilder->AddValue(value);
+ tokenizer.ParseNext();
+}
+
+// NB: since our final result while parsing YPath is an attribute dictionary, we intentionally refrain
+// from using TReadLimit (or even TLegacyReadLimit) as an intermediate structure here. YPath short
+// form is parsed using merely NYTree primitives and unversioned rows.
+
+//! This enum and the following structure define one of the following row limit specifications:
+//! - (None): empty limit;
+//! - (RowIndex): row index;
+//! - (Key): row (note that in short form this key may not include min/max sentinels).
+DEFINE_ENUM(EYPathLimitKind,
+ (None)
+ (RowIndex)
+ (Key)
+);
+
+//! This is an intermediate representation used only while parsing rich YPath short form.
+struct TYPathLimit
+{
+ EYPathLimitKind Kind = EYPathLimitKind::None;
+ //! Actual row for (Key) and null row for (RowIndex) and (None).
+ TUnversionedOwningRow Row;
+ //! Actual row index for (RowIndex) and std::nullopt for (Key), and (None).
+ std::optional<i64> RowIndex;
+};
+
+void Serialize(const TYPathLimit& limit, IYsonConsumer* consumer)
+{
+ switch (limit.Kind) {
+ case EYPathLimitKind::RowIndex:
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("row_index").Value(limit.RowIndex)
+ .EndMap();
+ return;
+ case EYPathLimitKind::Key:
+ BuildYsonFluently(consumer)
+ .BeginMap()
+ .Item("key").Value(limit.Row)
+ .EndMap();
+ return;
+ default:
+ // None is not handled by this function.
+ YT_ABORT();
+ }
+}
+
+void ParseRowLimit(
+ NYson::TTokenizer& tokenizer,
+ std::vector<NYson::ETokenType> separators,
+ TYPathLimit* limit)
+{
+ if (std::find(separators.begin(), separators.end(), tokenizer.GetCurrentType()) != separators.end()) {
+ return;
+ }
+
+ TUnversionedOwningRowBuilder rowBuilder;
+ bool hasKeyLimit = false;
+ switch (tokenizer.GetCurrentType()) {
+ case RowIndexMarkerToken:
+ tokenizer.ParseNext();
+ limit->RowIndex = tokenizer.CurrentToken().GetInt64Value();
+ limit->Kind = EYPathLimitKind::RowIndex;
+ tokenizer.ParseNext();
+ break;
+
+ case BeginTupleToken:
+ tokenizer.ParseNext();
+ hasKeyLimit = true;
+ while (tokenizer.GetCurrentType() != EndTupleToken) {
+ ParseKeyPart(tokenizer, &rowBuilder);
+ switch (tokenizer.GetCurrentType()) {
+ case KeySeparatorToken:
+ tokenizer.ParseNext();
+ break;
+ case EndTupleToken:
+ break;
+ default:
+ ThrowUnexpectedToken(tokenizer.CurrentToken());
+ YT_ABORT();
+ }
+ }
+ tokenizer.ParseNext();
+ break;
+
+ default:
+ ParseKeyPart(tokenizer, &rowBuilder);
+ hasKeyLimit = true;
+ break;
+ }
+
+ if (hasKeyLimit) {
+ auto key = rowBuilder.FinishRow();
+ limit->Row = key;
+ limit->Kind = EYPathLimitKind::Key;
+ }
+
+ tokenizer.CurrentToken().ExpectTypes(separators);
+}
+
+void ParseRowRanges(NYson::TTokenizer& tokenizer, IAttributeDictionary* attributes)
+{
+ if (tokenizer.GetCurrentType() == BeginRowSelectorToken) {
+ tokenizer.ParseNext();
+
+ std::vector<INodePtr> ranges;
+
+ bool finished = false;
+ while (!finished) {
+ auto rangeNode = GetEphemeralNodeFactory()->CreateMap();
+
+ TYPathLimit lowerLimit;
+ TYPathLimit upperLimit;
+ bool isTwoSideRange = false;
+ ParseRowLimit(
+ tokenizer,
+ {RangeToken, RangeSeparatorToken, EndRowSelectorToken},
+ &lowerLimit);
+ if (tokenizer.GetCurrentType() == RangeToken) {
+ isTwoSideRange = true;
+ tokenizer.ParseNext();
+
+ ParseRowLimit(
+ tokenizer,
+ {RangeSeparatorToken, EndRowSelectorToken},
+ &upperLimit);
+ }
+
+ if (isTwoSideRange) {
+ if (lowerLimit.Kind != EYPathLimitKind::None) {
+ rangeNode->AddChild("lower_limit", ConvertToNode(lowerLimit));
+ }
+ if (upperLimit.Kind != EYPathLimitKind::None) {
+ rangeNode->AddChild("upper_limit", ConvertToNode(upperLimit));
+ }
+ } else {
+ if (lowerLimit.Kind != EYPathLimitKind::None) {
+ rangeNode->AddChild("exact", ConvertToNode(lowerLimit));
+ } else {
+ // Universal limit is represented by an empty map, so do nothing.
+ }
+ }
+
+ if (tokenizer.CurrentToken().GetType() == EndRowSelectorToken) {
+ finished = true;
+ }
+ tokenizer.ParseNext();
+
+ ranges.emplace_back(std::move(rangeNode));
+ }
+
+ attributes->Set("ranges", ConvertToYsonString(ranges));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRichYPath ParseRichYPathImpl(const TString& str)
+{
+ auto attributes = CreateEphemeralAttributes();
+
+ auto strWithoutAttributes = ParseAttributes(str, attributes);
+ strWithoutAttributes = ParseCluster(std::move(strWithoutAttributes), attributes);
+ TTokenizer ypathTokenizer(strWithoutAttributes);
+
+ while (ypathTokenizer.GetType() != ETokenType::EndOfStream && ypathTokenizer.GetType() != ETokenType::Range) {
+ ypathTokenizer.Advance();
+ }
+ auto path = TYPath(ypathTokenizer.GetPrefix());
+ auto rangeStr = ypathTokenizer.GetToken();
+
+ if (ypathTokenizer.GetType() == ETokenType::Range) {
+ NYson::TTokenizer ysonTokenizer(rangeStr);
+ ysonTokenizer.ParseNext();
+ ParseColumns(ysonTokenizer, attributes.Get());
+ ParseRowRanges(ysonTokenizer, attributes.Get());
+ ysonTokenizer.CurrentToken().ExpectType(NYson::ETokenType::EndOfStream);
+ }
+ return TRichYPath(path, *attributes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYPath
diff --git a/yt/yt/client/ypath/parser_detail.h b/yt/yt/client/ypath/parser_detail.h
new file mode 100644
index 0000000000..953df87034
--- /dev/null
+++ b/yt/yt/client/ypath/parser_detail.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "rich.h"
+
+namespace NYT::NYPath {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRichYPath ParseRichYPathImpl(const TString& str);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYPath
diff --git a/yt/yt/client/ypath/public.h b/yt/yt/client/ypath/public.h
new file mode 100644
index 0000000000..2726c549a1
--- /dev/null
+++ b/yt/yt/client/ypath/public.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <yt/yt/core/ypath/public.h>
+
+namespace NYT::NYPath {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((InvalidReadRange) (2700))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TRichYPath;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYPath
diff --git a/yt/yt/client/ypath/rich.cpp b/yt/yt/client/ypath/rich.cpp
new file mode 100644
index 0000000000..ac68331821
--- /dev/null
+++ b/yt/yt/client/ypath/rich.cpp
@@ -0,0 +1,741 @@
+#include "rich.h"
+
+#include "parser_detail.h"
+
+#include <yt/yt/client/chunk_client/read_limit.h>
+
+#include <yt/yt/client/table_client/column_sort_schema.h>
+#include <yt/yt/client/table_client/column_rename_descriptor.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NYPath {
+
+using namespace NYTree;
+using namespace NYson;
+using namespace NChunkClient;
+using namespace NTableClient;
+using namespace NSecurityClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRichYPath::TRichYPath()
+{ }
+
+TRichYPath::TRichYPath(const TRichYPath& other)
+ : Path_(other.Path_)
+ , Attributes_(other.Attributes_ ? other.Attributes_->Clone() : nullptr)
+{ }
+
+TRichYPath::TRichYPath(const char* path)
+ : Path_(path)
+{
+ *this = Normalize();
+}
+
+TRichYPath::TRichYPath(const TYPath& path)
+ : Path_(path)
+{
+ *this = Normalize();
+}
+
+TRichYPath::TRichYPath(TRichYPath&& other)
+ : Path_(std::move(other.Path_))
+ , Attributes_(std::move(other.Attributes_))
+{ }
+
+TRichYPath::TRichYPath(const TYPath& path, const IAttributeDictionary& attributes)
+ : Path_(path)
+ , Attributes_(attributes.Clone())
+{ }
+
+const TYPath& TRichYPath::GetPath() const
+{
+ return Path_;
+}
+
+void TRichYPath::SetPath(const TYPath& path)
+{
+ Path_ = path;
+}
+
+const IAttributeDictionary& TRichYPath::Attributes() const
+{
+ return Attributes_ ? *Attributes_ : EmptyAttributes();
+}
+
+IAttributeDictionary& TRichYPath::Attributes()
+{
+ if (!Attributes_) {
+ Attributes_ = CreateEphemeralAttributes();
+ }
+ return *Attributes_;
+}
+
+TRichYPath& TRichYPath::operator = (const TRichYPath& other)
+{
+ if (this != &other) {
+ Path_ = other.Path_;
+ Attributes_ = other.Attributes_ ? other.Attributes_->Clone() : nullptr;
+ }
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator== (const TRichYPath& lhs, const TRichYPath& rhs)
+{
+ return lhs.GetPath() == rhs.GetPath() && lhs.Attributes() == rhs.Attributes();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+void AppendAttributes(TStringBuilderBase* builder, const IAttributeDictionary& attributes, EYsonFormat ysonFormat)
+{
+ TString attrString;
+ TStringOutput output(attrString);
+ TYsonWriter writer(&output, ysonFormat, EYsonType::MapFragment);
+
+ BuildYsonAttributesFluently(&writer)
+ .Items(attributes);
+
+ if (!attrString.empty()) {
+ builder->AppendChar(TokenTypeToChar(NYson::ETokenType::LeftAngle));
+ builder->AppendString(attrString);
+ builder->AppendChar(TokenTypeToChar(NYson::ETokenType::RightAngle));
+ }
+}
+
+template <class TFunc>
+auto RunAttributeAccessor(const TRichYPath& path, const TString& key, TFunc accessor) -> decltype(accessor())
+{
+ try {
+ return accessor();
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION("Error parsing attribute %Qv of rich YPath %v",
+ key,
+ path.GetPath()) << ex;
+ }
+}
+
+template <class T>
+T GetAttribute(const TRichYPath& path, const TString& key, const T& defaultValue)
+{
+ return RunAttributeAccessor(path, key, [&] () {
+ return path.Attributes().Get(key, defaultValue);
+ });
+}
+
+template <class T>
+typename TOptionalTraits<T>::TOptional FindAttribute(const TRichYPath& path, const TString& key)
+{
+ return RunAttributeAccessor(path, key, [&] () {
+ return path.Attributes().Find<T>(key);
+ });
+}
+
+TYsonString FindAttributeYson(const TRichYPath& path, const TString& key)
+{
+ return RunAttributeAccessor(path, key, [&] () {
+ return path.Attributes().FindYson(key);
+ });
+}
+
+} // namespace
+
+TRichYPath TRichYPath::Parse(const TString& str)
+{
+ return ParseRichYPathImpl(str);
+}
+
+TRichYPath TRichYPath::Normalize() const
+{
+ auto parsed = TRichYPath::Parse(Path_);
+ parsed.Attributes().MergeFrom(Attributes());
+ return parsed;
+}
+
+void TRichYPath::Save(TStreamSaveContext& context) const
+{
+ using NYT::Save;
+ Save(context, Path_);
+ Save(context, Attributes_);
+}
+
+void TRichYPath::Load(TStreamLoadContext& context)
+{
+ using NYT::Load;
+ Load(context, Path_);
+ Load(context, Attributes_);
+}
+
+bool TRichYPath::GetAppend(bool defaultValue) const
+{
+ return GetAttribute(*this, "append", defaultValue);
+}
+
+void TRichYPath::SetAppend(bool value)
+{
+ Attributes().Set("append", value);
+}
+
+bool TRichYPath::GetTeleport() const
+{
+ return GetAttribute(*this, "teleport", false);
+}
+
+bool TRichYPath::GetPrimary() const
+{
+ return GetAttribute(*this, "primary", false);
+}
+
+bool TRichYPath::GetForeign() const
+{
+ return GetAttribute(*this, "foreign", false);
+}
+
+void TRichYPath::SetForeign(bool value)
+{
+ Attributes().Set("foreign", value);
+}
+
+std::optional<std::vector<TString>> TRichYPath::GetColumns() const
+{
+ if (Attributes().Contains("channel")) {
+ THROW_ERROR_EXCEPTION("Deprecated attribute \"channel\" in YPath");
+ }
+ return FindAttribute<std::vector<TString>>(*this, "columns");
+}
+
+void TRichYPath::SetColumns(const std::vector<TString>& columns)
+{
+ Attributes().Set("columns", columns);
+}
+
+std::vector<NChunkClient::TLegacyReadRange> TRichYPath::GetRanges() const
+{
+ // COMPAT(ignat): top-level "lower_limit" and "upper_limit" are processed for compatibility.
+ auto optionalLowerLimit = FindAttribute<TLegacyReadLimit>(*this, "lower_limit");
+ auto optionalUpperLimit = FindAttribute<TLegacyReadLimit>(*this, "upper_limit");
+ auto optionalRanges = FindAttribute<std::vector<TLegacyReadRange>>(*this, "ranges");
+
+ if (optionalLowerLimit || optionalUpperLimit) {
+ if (optionalRanges) {
+ THROW_ERROR_EXCEPTION("YPath cannot be annotated with both multiple (\"ranges\" attribute) "
+ "and single (\"lower_limit\" or \"upper_limit\" attributes) ranges");
+ }
+ return std::vector<TLegacyReadRange>({
+ TLegacyReadRange(
+ optionalLowerLimit.value_or(TLegacyReadLimit()),
+ optionalUpperLimit.value_or(TLegacyReadLimit())
+ )});
+ } else {
+ return optionalRanges.value_or(std::vector<TLegacyReadRange>({TLegacyReadRange()}));
+ }
+}
+
+TUnversionedValue TryConvertValue(const TUnversionedValue& from, EValueType dstType)
+{
+ if (from.Type != NTableClient::EValueType::Int64 && from.Type != NTableClient::EValueType::Uint64) {
+ return from;
+ }
+
+ auto convert = [&] (auto srcValue, bool isSigned) {
+ const ui64 maxInt64 = std::numeric_limits<i64>::max();
+ switch (dstType) {
+ case EValueType::Uint64:
+ if (isSigned && srcValue < 0) {
+ // Casting from negative int64 to uint64 doesn't seem to be a valid thing expected by user,
+ // so we just return without conversion.
+ return from;
+ }
+ return MakeUnversionedUint64Value(static_cast<ui64>(srcValue));
+ case EValueType::Int64:
+ if (!isSigned && static_cast<ui64>(srcValue) > maxInt64) {
+ // The unsigned value is too large to fit into int64. We also don't perform the conversion.
+ return from;
+ }
+ return MakeUnversionedInt64Value(static_cast<i64>(srcValue));
+ case EValueType::Double:
+ return MakeUnversionedDoubleValue(static_cast<double>(srcValue));
+ default:
+ return from;
+ }
+ YT_ABORT();
+ };
+
+ switch (from.Type) {
+ case NTableClient::EValueType::Int64:
+ return convert(from.Data.Int64, true);
+ case NTableClient::EValueType::Uint64:
+ return convert(from.Data.Uint64, false);
+ default:
+ return from;
+ }
+ YT_ABORT();
+}
+
+//! Return read range corresponding to given map node.
+NChunkClient::TReadRange RangeNodeToReadRange(
+ const NTableClient::TComparator& comparator,
+ const IMapNodePtr& rangeNode,
+ const NTableClient::TKeyColumnTypes& conversionTypeHints)
+{
+ auto lowerLimitNode = rangeNode->FindChild("lower_limit");
+ auto upperLimitNode = rangeNode->FindChild("upper_limit");
+ auto exactNode = rangeNode->FindChild("exact");
+
+ if (exactNode && (lowerLimitNode || upperLimitNode)) {
+ THROW_ERROR_EXCEPTION("Exact limit cannot be specified simultaneously with lower or upper limits");
+ }
+
+ auto deserializeLimit = [&] (const IMapNodePtr& limitNode, TReadLimit& readLimit, bool isUpper, bool isExact) {
+ auto keyNode = limitNode->FindChild("key");
+ if (keyNode) {
+ limitNode->RemoveChild(keyNode);
+ }
+
+ auto keyBoundNode = limitNode->FindChild("key_bound");
+
+ // Check that key bound is not specified in exact clause.
+ if (keyBoundNode && isExact) {
+ THROW_ERROR_EXCEPTION("Key bound cannot be specified in exact limit, specify lower or upper limit instead");
+ }
+
+ // Check that key or key bound do not appear for unsorted tables.
+ if ((keyNode || keyBoundNode) && !comparator) {
+ THROW_ERROR_EXCEPTION("Cannot use key or key bound in read limit for an unsorted object");
+ }
+
+ // NB: for the sake of compatibility, we support specifying both key and key bound in read limit.
+ // In this case we consider only key bound and completely ignore key.
+
+ if (keyNode && !keyBoundNode) {
+ // Before deserializing, we may need to transform legacy key into key bound.
+ auto owningKey = ConvertTo<TUnversionedOwningRow>(keyNode);
+ TOwningKeyBound keyBound;
+
+ // Perform type conversion, if required.
+ if (!conversionTypeHints.empty()) {
+ TUnversionedOwningRowBuilder newOwningKey;
+ const int typedKeyCount = std::min(owningKey.GetCount(), static_cast<int>(conversionTypeHints.size()));
+ for (int i = 0; i < typedKeyCount; ++i) {
+ newOwningKey.AddValue(TryConvertValue(owningKey[i], conversionTypeHints[i]));
+ }
+ for (int i = typedKeyCount; i < owningKey.GetCount(); ++i) {
+ newOwningKey.AddValue(owningKey[i]);
+ }
+ owningKey = newOwningKey.FinishRow();
+ }
+
+ bool containsSentinels = std::find_if(
+ owningKey.begin(),
+ owningKey.end(),
+ [&] (const TUnversionedValue& value) {
+ return IsSentinelType(value.Type);
+ }) != owningKey.end();
+
+ // For tables with descending sort order we are allowed to impose stricter rules regarding
+ // what can be specified as a "key" in a read limit.
+ if (comparator.HasDescendingSortOrder()) {
+ // First of all, we do not want to state if <min> is less than any value in descending sort order
+ // or not. That's why we simply prohibit keys with sentinels when comparator is not totally ascending.
+ if (containsSentinels) {
+ THROW_ERROR_EXCEPTION(
+ "Sentinel values are not allowed in read limit key when at least one column "
+ "has descending sort order");
+ }
+ // Also, we prohibit keys that are longer than comparator.
+ if (owningKey.GetCount() > comparator.GetLength()) {
+ THROW_ERROR_EXCEPTION(
+ "Read limit key cannot be longer than schema key prefix when at least one column "
+ "has descending sort order");
+ }
+
+ keyBound = TOwningKeyBound::FromRow(owningKey, /* isInclusive */ !isUpper, isUpper);
+ } else {
+ // Existing code may specify arbitrary garbage as legacy keys and we have no solution other
+ // than interpreting it as a key bound using interop method.
+
+ if (isExact && (owningKey.GetCount() > comparator.GetLength() || containsSentinels)) {
+ // NB: there are two tricky cases when read limit is exact:
+ // - (1) if specified key is longer than comparator. Recall that (in old terms)
+ // there may be no keys between (foo, bar) and (foo, bar, <max>) in a table with single
+ // key column.
+ // - (2) if specified key contains sentinels.
+ //
+ // For non-totally ascending comparators we throw error right in deserializeLimit in
+ // both cases.
+ //
+ // For totally ascending comparators we can't throw error and we must somehow
+ // represent an empty range because there may be already existing code that specifies
+ // such limits and expects the empty result, but not an error.
+ //
+ // That's why we replace such key with (empty) key bound >[] to make sure this range
+ // will not contain any key. Also, note that simply skipping such range is incorrect
+ // as it would break range indices (kudos to ifsmirnov@ for this nice observation).
+ keyBound = TOwningKeyBound::MakeEmpty(/* isUpper */ false);
+ } else {
+ keyBound = KeyBoundFromLegacyRow(owningKey, isUpper, comparator.GetLength());
+ }
+ }
+ limitNode->AddChild("key_bound", ConvertToNode(keyBound));
+ }
+
+ // We got rid of "key" map node key, so we may just use Deserialize which simply constructs
+ // read limit from node representation.
+ Deserialize(readLimit, limitNode);
+
+ // Note that "key_bound" is either specified explicitly or obtained from "key" at this point.
+ // For the former case, we did not yet perform validation of key bound sanity, we will do that
+ // later. For the latter case the check is redundant, but let's perform it anyway.
+
+ // If key bound was explicitly specified (i.e. we did not transform legacy key into key bound),
+ // we still need to check that key bound length is no more than comparator length.
+ if (readLimit.KeyBound() && readLimit.KeyBound().Prefix.GetCount() > comparator.GetLength()) {
+ THROW_ERROR_EXCEPTION("Key bound length must not exceed schema key prefix length");
+ }
+
+ // Check that key bound is of correct direction.
+ if (readLimit.KeyBound() && readLimit.KeyBound().IsUpper != isUpper) {
+ // Key bound for exact limit is formed only by us and it has always a correct (lower) direction.
+ YT_VERIFY(!isExact);
+ THROW_ERROR_EXCEPTION(
+ "Key bound for %v limit has wrong relation type %Qv",
+ isUpper ? "upper" : "lower",
+ readLimit.KeyBound().GetRelation());
+ }
+
+ // Validate that exact limit contains at most one independent selector.
+ if (exactNode && (readLimit.HasIndependentSelectors() || readLimit.IsTrivial())) {
+ THROW_ERROR_EXCEPTION("Exact read limit must have exactly one independent selector specified");
+ }
+ };
+
+ TReadRange result;
+
+ if (lowerLimitNode) {
+ deserializeLimit(lowerLimitNode->AsMap(), result.LowerLimit(), /* isUpper */ false, /* isExact */ false);
+ }
+ if (upperLimitNode) {
+ deserializeLimit(upperLimitNode->AsMap(), result.UpperLimit(), /* isUpper */ true, /* isExact */ false);
+ }
+ if (exactNode) {
+ // Exact limit is transformed into pair of lower and upper limit. First, deserialize exact limit
+ // as if it was lower limit.
+ deserializeLimit(exactNode->AsMap(), result.LowerLimit(), /* isUpper */ false, /* isExact */ true);
+
+ // Now copy lower limit to upper limit and either increment single integer selector, or
+ // invert key bound selector.
+ result.UpperLimit() = result.LowerLimit().ToExactUpperCounterpart();
+ }
+
+ return result;
+}
+
+std::vector<NChunkClient::TReadRange> TRichYPath::GetNewRanges(
+ const NTableClient::TComparator& comparator,
+ const NTableClient::TKeyColumnTypes& conversionTypeHints) const
+{
+ // TODO(max42): YT-14242. Top-level "lower_limit" and "upper_limit" are processed for compatibility.
+ // But we should deprecate this one day.
+ auto optionalRangeNodes = FindAttribute<std::vector<IMapNodePtr>>(*this, "ranges");
+ auto optionalLowerLimitNode = FindAttribute<IMapNodePtr>(*this, "lower_limit");
+ auto optionalUpperLimitNode = FindAttribute<IMapNodePtr>(*this, "upper_limit");
+
+ if (optionalLowerLimitNode || optionalUpperLimitNode) {
+ if (optionalRangeNodes) {
+ THROW_ERROR_EXCEPTION("YPath cannot be annotated with both multiple (\"ranges\" attribute) "
+ "and single (\"lower_limit\" or \"upper_limit\" attributes) ranges");
+ }
+ THashMap<TString, IMapNodePtr> rangeNode;
+ if (optionalLowerLimitNode) {
+ rangeNode["lower_limit"] = optionalLowerLimitNode;
+ }
+ if (optionalUpperLimitNode) {
+ rangeNode["upper_limit"] = optionalUpperLimitNode;
+ }
+ optionalRangeNodes = {ConvertToNode(rangeNode)->AsMap()};
+ }
+
+ if (!optionalRangeNodes) {
+ return {TReadRange()};
+ }
+
+ std::vector<NChunkClient::TReadRange> readRanges;
+
+ for (const auto& rangeNode : *optionalRangeNodes) {
+ // For the sake of unchanged range node in error message.
+ auto rangeNodeCopy = ConvertToNode(rangeNode);
+ try {
+ readRanges.emplace_back(RangeNodeToReadRange(comparator, rangeNode, conversionTypeHints));
+ } catch (const std::exception& ex) {
+ THROW_ERROR_EXCEPTION(
+ NYPath::EErrorCode::InvalidReadRange, "Invalid read range")
+ << TErrorAttribute("range", rangeNodeCopy)
+ << ex;
+ }
+ }
+
+ return readRanges;
+}
+
+void TRichYPath::SetRanges(const std::vector<NChunkClient::TReadRange>& ranges)
+{
+ Attributes().Set("ranges", ranges);
+ // COMPAT(ignat)
+ Attributes().Remove("lower_limit");
+ Attributes().Remove("upper_limit");
+}
+
+bool TRichYPath::HasNontrivialRanges() const
+{
+ auto optionalLowerLimit = FindAttribute<TLegacyReadLimit>(*this, "lower_limit");
+ auto optionalUpperLimit = FindAttribute<TLegacyReadLimit>(*this, "upper_limit");
+ auto optionalRanges = FindAttribute<std::vector<TLegacyReadRange>>(*this, "ranges");
+
+ return optionalLowerLimit || optionalUpperLimit || optionalRanges;
+}
+
+std::optional<TString> TRichYPath::GetFileName() const
+{
+ return FindAttribute<TString>(*this, "file_name");
+}
+
+std::optional<bool> TRichYPath::GetExecutable() const
+{
+ return FindAttribute<bool>(*this, "executable");
+}
+
+TYsonString TRichYPath::GetFormat() const
+{
+ return FindAttributeYson(*this, "format");
+}
+
+TTableSchemaPtr TRichYPath::GetSchema() const
+{
+ return RunAttributeAccessor(*this, "schema", [&] {
+ auto schema = FindAttribute<TTableSchemaPtr>(*this, "schema");
+ if (schema) {
+ ValidateTableSchema(*schema);
+ }
+ return schema;
+ });
+}
+
+std::optional<TColumnRenameDescriptors> TRichYPath::GetColumnRenameDescriptors() const
+{
+ return FindAttribute<TColumnRenameDescriptors>(*this, "rename_columns");
+}
+
+TSortColumns TRichYPath::GetSortedBy() const
+{
+ return GetAttribute(*this, "sorted_by", TSortColumns());
+}
+
+void TRichYPath::SetSortedBy(const TSortColumns& value)
+{
+ if (value.empty()) {
+ Attributes().Remove("sorted_by");
+ } else {
+ Attributes().Set("sorted_by", value);
+ }
+}
+
+std::optional<i64> TRichYPath::GetRowCountLimit() const
+{
+ return RunAttributeAccessor(*this, "row_count_limit", [&] {
+ auto rowCountLimit = FindAttribute<i64>(*this, "row_count_limit");
+ if (rowCountLimit && *rowCountLimit < 0) {
+ THROW_ERROR_EXCEPTION("Row count limit should be non-negative")
+ << TErrorAttribute("row_count_limit", rowCountLimit);
+ }
+ return rowCountLimit;
+ });
+}
+
+std::optional<NTransactionClient::TTimestamp> TRichYPath::GetTimestamp() const
+{
+ return FindAttribute<NTransactionClient::TTimestamp>(*this, "timestamp");
+}
+
+std::optional<NTransactionClient::TTimestamp> TRichYPath::GetRetentionTimestamp() const
+{
+ return FindAttribute<NTransactionClient::TTimestamp>(*this, "retention_timestamp");
+}
+
+std::optional<NTransactionClient::TTimestamp> TRichYPath::GetOutputTimestamp() const
+{
+ return FindAttribute<NTransactionClient::TTimestamp>(*this, "output_timestamp");
+}
+
+std::optional<NTableClient::EOptimizeFor> TRichYPath::GetOptimizeFor() const
+{
+ return FindAttribute<NTableClient::EOptimizeFor>(*this, "optimize_for");
+}
+
+std::optional<NChunkClient::EChunkFormat> TRichYPath::GetChunkFormat() const
+{
+ return FindAttribute<NChunkClient::EChunkFormat>(*this, "chunk_format");
+}
+
+std::optional<NCompression::ECodec> TRichYPath::GetCompressionCodec() const
+{
+ return FindAttribute<NCompression::ECodec>(*this, "compression_codec");
+}
+
+std::optional<NErasure::ECodec> TRichYPath::GetErasureCodec() const
+{
+ return FindAttribute<NErasure::ECodec>(*this, "erasure_codec");
+}
+
+bool TRichYPath::GetAutoMerge() const
+{
+ return GetAttribute<bool>(*this, "auto_merge", true);
+}
+
+std::optional<NObjectClient::TTransactionId> TRichYPath::GetTransactionId() const
+{
+ return FindAttribute<NObjectClient::TTransactionId>(*this, "transaction_id");
+}
+
+std::optional<std::vector<TSecurityTag>> TRichYPath::GetSecurityTags() const
+{
+ return FindAttribute<std::vector<TSecurityTag>>(*this, "security_tags");
+}
+
+bool TRichYPath::GetBypassArtifactCache() const
+{
+ return GetAttribute<bool>(*this, "bypass_artifact_cache", false);
+}
+
+ETableSchemaModification TRichYPath::GetSchemaModification() const
+{
+ return GetAttribute<ETableSchemaModification>(
+ *this,
+ "schema_modification",
+ ETableSchemaModification::None);
+}
+
+bool TRichYPath::GetPartiallySorted() const
+{
+ return GetAttribute<bool>(*this, "partially_sorted", false);
+}
+
+std::optional<bool> TRichYPath::GetChunkUniqueKeys() const
+{
+ return FindAttribute<bool>(*this, "chunk_unique_keys");
+}
+
+std::optional<bool> TRichYPath::GetCopyFile() const
+{
+ return FindAttribute<bool>(*this, "copy_file");
+}
+
+std::optional<TSortColumns> TRichYPath::GetChunkSortColumns() const
+{
+ return FindAttribute<TSortColumns>(*this, "chunk_sort_columns");
+}
+
+std::optional<TString> TRichYPath::GetCluster() const
+{
+ return FindAttribute<TString>(*this, "cluster");
+}
+
+void TRichYPath::SetCluster(const TString& value)
+{
+ Attributes().Set("cluster", value);
+}
+
+std::optional<std::vector<TString>> TRichYPath::GetClusters() const
+{
+ return FindAttribute<std::vector<TString>>(*this, "clusters");
+}
+
+void TRichYPath::SetClusters(const std::vector<TString>& value)
+{
+ Attributes().Set("clusters", value);
+}
+
+bool TRichYPath::GetCreate() const
+{
+ return GetAttribute<bool>(*this, "create", false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ConvertToString(const TRichYPath& path, EYsonFormat ysonFormat)
+{
+ TStringBuilder builder;
+ AppendAttributes(&builder, path.Attributes(), ysonFormat);
+ builder.AppendString(path.GetPath());
+ return builder.Flush();
+}
+
+TString ToString(const TRichYPath& path)
+{
+ // NB: we intentionally use Text format since string-representation of rich ypath should be readable.
+ return ConvertToString(path, EYsonFormat::Text);
+}
+
+std::vector<TRichYPath> Normalize(const std::vector<TRichYPath>& paths)
+{
+ std::vector<TRichYPath> result;
+ for (const auto& path : paths) {
+ result.push_back(path.Normalize());
+ }
+ return result;
+}
+
+void Serialize(const TRichYPath& richPath, IYsonConsumer* consumer)
+{
+ BuildYsonFluently(consumer)
+ .BeginAttributes()
+ .Items(richPath.Attributes())
+ .EndAttributes()
+ .Value(richPath.GetPath());
+}
+
+void Deserialize(TRichYPath& richPath, INodePtr node)
+{
+ if (node->GetType() != ENodeType::String) {
+ THROW_ERROR_EXCEPTION("YPath can only be parsed from %Qlv but got %Qlv",
+ ENodeType::String,
+ node->GetType());
+ }
+ richPath.SetPath(node->GetValue<TString>());
+ richPath.Attributes().Clear();
+ richPath.Attributes().MergeFrom(node->Attributes());
+ richPath = richPath.Normalize();
+}
+
+void Deserialize(TRichYPath& richPath, TYsonPullParserCursor* cursor)
+{
+ Deserialize(richPath, ExtractTo<INodePtr>(cursor));
+}
+
+void ToProto(TString* protoPath, const TRichYPath& path)
+{
+ *protoPath = ConvertToString(path, EYsonFormat::Binary);
+}
+
+void FromProto(TRichYPath* path, const TString& protoPath)
+{
+ *path = TRichYPath::Parse(protoPath);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYPath
+
+size_t THash<NYT::NYPath::TRichYPath>::operator()(const NYT::NYPath::TRichYPath& richYPath) const
+{
+ return ComputeHash(NYT::NYPath::ToString(richYPath));
+}
diff --git a/yt/yt/client/ypath/rich.h b/yt/yt/client/ypath/rich.h
new file mode 100644
index 0000000000..142c87ad26
--- /dev/null
+++ b/yt/yt/client/ypath/rich.h
@@ -0,0 +1,194 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/attributes.h>
+
+#include <yt/yt/core/compression/public.h>
+
+#include <yt/yt/library/erasure/public.h>
+
+#include <yt/yt/client/table_client/column_sort_schema.h>
+#include <yt/yt/client/table_client/schema.h>
+
+#include <yt/yt/client/chunk_client/read_limit.h>
+
+#include <yt/yt/client/transaction_client/public.h>
+
+#include <yt/yt/client/security_client/public.h>
+
+namespace NYT::NYPath {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! YPath string plus attributes.
+class TRichYPath
+{
+public:
+ TRichYPath();
+ TRichYPath(const TRichYPath& other);
+ TRichYPath(TRichYPath&& other);
+ TRichYPath(const char* path);
+ TRichYPath(const TYPath& path);
+ TRichYPath(const TYPath& path, const NYTree::IAttributeDictionary& attributes);
+ TRichYPath& operator = (const TRichYPath& other);
+
+ static TRichYPath Parse(const TString& str);
+ TRichYPath Normalize() const;
+
+ const TYPath& GetPath() const;
+ void SetPath(const TYPath& path);
+
+ const NYTree::IAttributeDictionary& Attributes() const;
+ NYTree::IAttributeDictionary& Attributes();
+
+ void Save(TStreamSaveContext& context) const;
+ void Load(TStreamLoadContext& context);
+
+ // Attribute accessors.
+ // "append"
+ bool GetAppend(bool defaultValue = false) const;
+ void SetAppend(bool value);
+
+ // "teleport"
+ bool GetTeleport() const;
+
+ // "primary"
+ bool GetPrimary() const;
+
+ // "foreign"
+ bool GetForeign() const;
+ void SetForeign(bool value);
+
+ // "columns"
+ std::optional<std::vector<TString>> GetColumns() const;
+ void SetColumns(const std::vector<TString>& columns);
+
+ // "rename_columns"
+ std::optional<NTableClient::TColumnRenameDescriptors> GetColumnRenameDescriptors() const;
+
+ // "ranges"
+ //! COMPAT(ignat): also "lower_limit" and "upper_limit"
+ //! This method returns legacy read ranges completely ignoring key bounds.
+ std::vector<NChunkClient::TLegacyReadRange> GetRanges() const;
+
+ //! Get ranges, possibly transforming legacy keys into new key bounds using provided comparator.
+ //! This method throws if comparator is not present and keys are present.
+ //! In case when at least one column is non-ascending, some requirements are even stronger,
+ //! refer to the implementation for details.
+ std::vector<NChunkClient::TReadRange> GetNewRanges(
+ const NTableClient::TComparator& comparator = NTableClient::TComparator(),
+ const NTableClient::TKeyColumnTypes& conversionTypeHints = {}) const;
+
+ void SetRanges(const std::vector<NChunkClient::TReadRange>& ranges);
+ bool HasNontrivialRanges() const;
+
+ // "file_name"
+ std::optional<TString> GetFileName() const;
+
+ // "executable"
+ std::optional<bool> GetExecutable() const;
+
+ // "format"
+ NYson::TYsonString GetFormat() const;
+
+ // "schema"
+ NTableClient::TTableSchemaPtr GetSchema() const;
+
+ // "sorted_by"
+ NTableClient::TSortColumns GetSortedBy() const;
+ void SetSortedBy(const NTableClient::TSortColumns& value);
+
+ // "row_count_limit"
+ std::optional<i64> GetRowCountLimit() const;
+
+ // "timestamp"
+ std::optional<NTransactionClient::TTimestamp> GetTimestamp() const;
+
+ // "retention_timestamp"
+ std::optional<NTransactionClient::TTimestamp> GetRetentionTimestamp() const;
+
+ // "output_timestamp"
+ std::optional<NTransactionClient::TTimestamp> GetOutputTimestamp() const;
+
+ // "optimize_for"
+ std::optional<NTableClient::EOptimizeFor> GetOptimizeFor() const;
+
+ // "chunk_format"
+ std::optional<NChunkClient::EChunkFormat> GetChunkFormat() const;
+
+ // "compression_codec"
+ std::optional<NCompression::ECodec> GetCompressionCodec() const;
+
+ // "erasure_codec"
+ std::optional<NErasure::ECodec> GetErasureCodec() const;
+
+ // "auto_merge"
+ bool GetAutoMerge() const;
+
+ // "transaction_id"
+ std::optional<NObjectClient::TTransactionId> GetTransactionId() const;
+
+ // "security_tags"
+ std::optional<std::vector<NSecurityClient::TSecurityTag>> GetSecurityTags() const;
+
+ // "bypass_artifact_cache"
+ bool GetBypassArtifactCache() const;
+
+ // "schema_modification"
+ NTableClient::ETableSchemaModification GetSchemaModification() const;
+
+ // "partially_sorted"
+ bool GetPartiallySorted() const;
+
+ // "chunk_unique_keys"
+ std::optional<bool> GetChunkUniqueKeys() const;
+
+ // "copy_file"
+ std::optional<bool> GetCopyFile() const;
+
+ // "chunk_sort_columns"
+ std::optional<NTableClient::TSortColumns> GetChunkSortColumns() const;
+
+ // "cluster"
+ std::optional<TString> GetCluster() const;
+ void SetCluster(const TString& value);
+
+ // "clusters"
+ std::optional<std::vector<TString>> GetClusters() const;
+ void SetClusters(const std::vector<TString>& value);
+
+ // "create"
+ bool GetCreate() const;
+
+private:
+ TYPath Path_;
+ NYTree::IAttributeDictionaryPtr Attributes_;
+};
+
+bool operator== (const TRichYPath& lhs, const TRichYPath& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+TString ToString(const TRichYPath& path);
+
+std::vector<TRichYPath> Normalize(const std::vector<TRichYPath>& paths);
+
+void Serialize(const TRichYPath& richPath, NYson::IYsonConsumer* consumer);
+void Deserialize(TRichYPath& richPath, NYTree::INodePtr node);
+void Deserialize(TRichYPath& richPath, NYson::TYsonPullParserCursor* cursor);
+
+void ToProto(TString* protoPath, const TRichYPath& path);
+void FromProto(TRichYPath* path, const TString& protoPath);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYPath
+
+template <>
+struct THash<NYT::NYPath::TRichYPath>
+{
+ size_t operator()(const NYT::NYPath::TRichYPath& richYPath) const;
+};
diff --git a/yt/yt/client/zookeeper/packet.cpp b/yt/yt/client/zookeeper/packet.cpp
new file mode 100644
index 0000000000..c9ec915778
--- /dev/null
+++ b/yt/yt/client/zookeeper/packet.cpp
@@ -0,0 +1,299 @@
+#include "packet.h"
+
+#include <library/cpp/yt/memory/chunked_memory_allocator.h>
+
+namespace NYT::NZookeeper {
+
+using namespace NBus;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(EPacketPhase,
+ (Header)
+ (Message)
+ (Finished)
+);
+
+struct TPacketDecoderTag
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPacketTranscoderBase
+{
+ union {
+ int MessageSize;
+ char Data[sizeof(int)];
+ } Header_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPacketDecoder
+ : public IPacketDecoder
+ , public TPacketTranscoderBase
+{
+public:
+ TPacketDecoder()
+ : Allocator_(
+ PacketDecoderChunkSize,
+ TChunkedMemoryAllocator::DefaultMaxSmallBlockSizeRatio,
+ GetRefCountedTypeCookie<TPacketDecoderTag>())
+ {
+ Restart();
+ }
+
+ void Restart() override
+ {
+ PacketSize_ = 0;
+ Message_.Reset();
+
+ BeginPhase(
+ EPacketPhase::Header,
+ Header_.Data,
+ sizeof(Header_.Data));
+ }
+
+ bool IsInProgress() const override
+ {
+ return !IsFinished();
+ }
+
+ bool IsFinished() const override
+ {
+ return Phase_ == EPacketPhase::Finished;
+ }
+
+ TMutableRef GetFragment() override
+ {
+ return TMutableRef(FragmentPtr_, FragmentRemaining_);
+ }
+
+ bool Advance(size_t size) override
+ {
+ YT_ASSERT(FragmentRemaining_ != 0);
+ YT_ASSERT(size <= FragmentRemaining_);
+
+ PacketSize_ += size;
+ FragmentRemaining_ -= size;
+ FragmentPtr_ += size;
+ if (FragmentRemaining_ == 0) {
+ return EndPhase();
+ } else {
+ return true;
+ }
+ }
+
+ EPacketType GetPacketType() const override
+ {
+ return EPacketType::Message;
+ }
+
+ EPacketFlags GetPacketFlags() const override
+ {
+ return EPacketFlags::None;
+ }
+
+ TPacketId GetPacketId() const override
+ {
+ return {};
+ }
+
+ size_t GetPacketSize() const override
+ {
+ return PacketSize_;
+ }
+
+ TSharedRefArray GrabMessage() const override
+ {
+ return TSharedRefArray(Message_);
+ }
+
+private:
+ EPacketPhase Phase_ = EPacketPhase::Finished;
+ char* FragmentPtr_ = nullptr;
+ size_t FragmentRemaining_ = 0;
+
+ TChunkedMemoryAllocator Allocator_;
+ constexpr static i64 PacketDecoderChunkSize = 16_KB;
+
+ TSharedRef Message_;
+
+ size_t PacketSize_ = 0;
+
+ void BeginPhase(
+ EPacketPhase phase,
+ char* fragmentPtr,
+ size_t fragmentSize)
+ {
+ Phase_ = phase;
+ FragmentPtr_ = fragmentPtr;
+ FragmentRemaining_ = fragmentSize;
+ }
+
+ bool EndPhase()
+ {
+ switch (Phase_) {
+ case EPacketPhase::Header: {
+ std::reverse(std::begin(Header_.Data), std::end(Header_.Data));
+ auto messageSize = Header_.MessageSize;
+ auto message = Allocator_.AllocateAligned(messageSize);
+ BeginPhase(
+ EPacketPhase::Message,
+ /*fragmentPtr*/ message.begin(),
+ /*fragmentSize*/ message.size());
+ Message_ = std::move(message);
+
+ return true;
+ }
+ case EPacketPhase::Message:
+ BeginPhase(
+ EPacketPhase::Finished,
+ /*fragmentPtr*/ nullptr,
+ /*fragmentSize*/ 0);
+
+ return true;
+ default:
+ YT_ABORT();
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPacketEncoder
+ : public IPacketEncoder
+ , public TPacketTranscoderBase
+{
+public:
+ size_t GetPacketSize(
+ EPacketType type,
+ const TSharedRefArray& /*message*/,
+ size_t messageSize) override
+ {
+ YT_ASSERT(type == EPacketType::Message);
+ YT_ASSERT(messageSize > 0);
+
+ return sizeof(Header_) + messageSize;
+ }
+
+ bool Start(
+ EPacketType type,
+ EPacketFlags flags,
+ bool /*generateChecksums*/,
+ int /*checksummedPartCount*/,
+ TPacketId /*packetId*/,
+ TSharedRefArray messageParts) override
+ {
+ YT_ASSERT(type == EPacketType::Message);
+ YT_ASSERT(flags == EPacketFlags::None);
+ YT_ASSERT(!messageParts.Empty());
+
+ i64 messageSize = 0;
+ for (const auto& messagePart : messageParts) {
+ messageSize += messagePart.size();
+ }
+ Header_.MessageSize = messageSize;
+ std::reverse(std::begin(Header_.Data), std::end(Header_.Data));
+
+ MessageParts_ = std::move(messageParts);
+
+ Phase_ = EPacketPhase::Header;
+ CurrentMessagePart_ = 0;
+
+ return true;
+ }
+
+ TMutableRef GetFragment() override
+ {
+ switch (Phase_) {
+ case EPacketPhase::Header:
+ return TMutableRef(Header_.Data, sizeof(Header_.Data));
+ case EPacketPhase::Message: {
+ const auto& messagePart = MessageParts_[CurrentMessagePart_];
+ return TMutableRef(
+ const_cast<char*>(messagePart.begin()),
+ messagePart.size());
+ }
+ default:
+ YT_ABORT();
+ }
+ }
+
+ bool IsFragmentOwned() const override
+ {
+ switch (Phase_) {
+ case EPacketPhase::Header:
+ return false;
+ case EPacketPhase::Message:
+ return true;
+ default:
+ YT_ABORT();
+ }
+ }
+
+ void NextFragment() override
+ {
+ switch (Phase_) {
+ case EPacketPhase::Header:
+ Phase_ = EPacketPhase::Message;
+ CurrentMessagePart_ = 0;
+ break;
+ case EPacketPhase::Message:
+ if (CurrentMessagePart_ + 1 < MessageParts_.size()) {
+ ++CurrentMessagePart_;
+ } else {
+ Phase_ = EPacketPhase::Finished;
+ }
+ break;
+ default:
+ YT_ABORT();
+ }
+ }
+
+ bool IsFinished() const override
+ {
+ return Phase_ == EPacketPhase::Finished;
+ }
+
+private:
+ EPacketPhase Phase_ = EPacketPhase::Finished;
+
+ TSharedRefArray MessageParts_;
+ size_t CurrentMessagePart_ = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TPacketTranscoderFactory
+ : public IPacketTranscoderFactory
+{
+ std::unique_ptr<IPacketDecoder> CreateDecoder(
+ const NLogging::TLogger& /*logger*/,
+ bool /*verifyChecksum*/) const override
+ {
+ return std::make_unique<TPacketDecoder>();
+ }
+
+ std::unique_ptr<IPacketEncoder> CreateEncoder(
+ const NLogging::TLogger& /*logger*/) const override
+ {
+ return std::make_unique<TPacketEncoder>();
+ }
+
+ bool SupportsHandshakes() const override
+ {
+ return false;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IPacketTranscoderFactory* GetZookeeperPacketTranscoderFactory()
+{
+ return LeakySingleton<TPacketTranscoderFactory>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/client/zookeeper/packet.h b/yt/yt/client/zookeeper/packet.h
new file mode 100644
index 0000000000..dd7eb806e1
--- /dev/null
+++ b/yt/yt/client/zookeeper/packet.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/bus/tcp/packet.h>
+
+namespace NYT::NZookeeper {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NBus::IPacketTranscoderFactory* GetZookeeperPacketTranscoderFactory();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/client/zookeeper/protocol.cpp b/yt/yt/client/zookeeper/protocol.cpp
new file mode 100644
index 0000000000..a450599d32
--- /dev/null
+++ b/yt/yt/client/zookeeper/protocol.cpp
@@ -0,0 +1,204 @@
+#include "protocol.h"
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NZookeeper {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TZookeeperProtocolReader
+ : public IZookeeperProtocolReader
+{
+public:
+ explicit TZookeeperProtocolReader(TSharedRef data)
+ : Data_(std::move(data))
+ { }
+
+ char ReadByte() override
+ {
+ return DoReadInt<char>();
+ }
+
+ int ReadInt() override
+ {
+ return DoReadInt<int>();
+ }
+
+ i64 ReadLong() override
+ {
+ return DoReadInt<i64>();
+ }
+
+ bool ReadBool() override
+ {
+ auto value = ReadByte();
+ return value > 0;
+ }
+
+ TString ReadString() override
+ {
+ TString result;
+ ReadString(&result);
+
+ return result;
+ }
+
+ void ReadString(TString* result) override
+ {
+ auto length = ReadInt();
+ ValidateSizeAvailable(length);
+
+ result->resize(length);
+ auto begin = Data_.begin() + Offset_;
+ std::copy(begin, begin + length, result->begin());
+ Offset_ += length;
+ }
+
+ TSharedRef GetSuffix() const override
+ {
+ return Data_.Slice(Offset_, Data_.Size());
+ }
+
+ bool IsFinished() const override
+ {
+ return Offset_ == std::ssize(Data_);
+ }
+
+ void ValidateFinished() const override
+ {
+ if (!IsFinished()) {
+ THROW_ERROR_EXCEPTION("Expected end of stream")
+ << TErrorAttribute("offset", Offset_)
+ << TErrorAttribute("message_size", Data_.size());
+ }
+ }
+
+private:
+ const TSharedRef Data_;
+ i64 Offset_ = 0;
+
+ template <typename T>
+ T DoReadInt()
+ {
+ ValidateSizeAvailable(sizeof(T));
+
+ union {
+ T value;
+ char bytes[sizeof(T)];
+ } result;
+
+ memcpy(result.bytes, Data_.begin() + Offset_, sizeof(T));
+ std::reverse(result.bytes, result.bytes + sizeof(T));
+ Offset_ += sizeof(T);
+
+ return result.value;
+ }
+
+ void ValidateSizeAvailable(i64 size)
+ {
+ if (std::ssize(Data_) - Offset_ < size) {
+ THROW_ERROR_EXCEPTION("Premature end of stream while reading %v bytes", size);
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IZookeeperProtocolReader> CreateZookeeperProtocolReader(TSharedRef data)
+{
+ return std::make_unique<TZookeeperProtocolReader>(std::move(data));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TZookeeperProtocolWriter
+ : public IZookeeperProtocolWriter
+{
+public:
+ TZookeeperProtocolWriter()
+ : Buffer_(AllocateBuffer(InitialBufferSize))
+ { }
+
+ void WriteByte(char value) override
+ {
+ DoWriteInt(value);
+ }
+
+ void WriteInt(int value) override
+ {
+ DoWriteInt(value);
+ }
+
+ void WriteLong(i64 value) override
+ {
+ DoWriteInt(value);
+ }
+
+ void WriteBool(bool value) override
+ {
+ WriteByte(value ? 1 : 0);
+ }
+
+ void WriteString(const TString& value) override
+ {
+ WriteInt(value.size());
+
+ EnsureFreeSpace(value.size());
+
+ std::copy(value.begin(), value.end(), Buffer_.begin() + Size_);
+ Size_ += value.size();
+ }
+
+ TSharedRef Finish() override
+ {
+ return Buffer_.Slice(0, Size_);
+ }
+
+private:
+ struct TZookeeperProtocolWriterTag
+ { };
+
+ constexpr static i64 InitialBufferSize = 16_KB;
+ constexpr static i64 BufferSizeMultiplier = 2;
+
+ TSharedMutableRef Buffer_;
+ i64 Size_ = 0;
+
+ template <typename T>
+ void DoWriteInt(T value)
+ {
+ EnsureFreeSpace(sizeof(T));
+
+ memcpy(Buffer_.begin() + Size_, &value, sizeof(T));
+ std::reverse(Buffer_.begin() + Size_, Buffer_.begin() + Size_ + sizeof(T));
+ Size_+= sizeof(T);
+ }
+
+ void EnsureFreeSpace(i64 size)
+ {
+ if (Size_ + size <= std::ssize(Buffer_)) {
+ return;
+ }
+
+ auto newSize = std::max<i64>(Size_ + size, Buffer_.size() * BufferSizeMultiplier);
+ auto newBuffer = AllocateBuffer(newSize);
+ std::copy(Buffer_.begin(), Buffer_.begin() + Size_, newBuffer.begin());
+ Buffer_ = std::move(newBuffer);
+ }
+
+ static TSharedMutableRef AllocateBuffer(i64 capacity)
+ {
+ return TSharedMutableRef::Allocate<TZookeeperProtocolWriterTag>(capacity);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IZookeeperProtocolWriter> CreateZookeeperProtocolWriter()
+{
+ return std::make_unique<TZookeeperProtocolWriter>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/client/zookeeper/protocol.h b/yt/yt/client/zookeeper/protocol.h
new file mode 100644
index 0000000000..29fea1da57
--- /dev/null
+++ b/yt/yt/client/zookeeper/protocol.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "public.h"
+
+#include <library/cpp/yt/memory/ref.h>
+
+namespace NYT::NZookeeper {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IZookeeperProtocolReader
+{
+ virtual ~IZookeeperProtocolReader() = default;
+
+ virtual char ReadByte() = 0;
+ virtual int ReadInt() = 0;
+ virtual i64 ReadLong() = 0;
+
+ virtual bool ReadBool() = 0;
+
+ virtual TString ReadString() = 0;
+ virtual void ReadString(TString* result) = 0;
+
+ virtual TSharedRef GetSuffix() const = 0;
+
+ //! Returns true if input is fully consumed and false otherwise.
+ virtual bool IsFinished() const = 0;
+ //! Throws an error if input is not fully consumed. Does nothing otherwise.
+ virtual void ValidateFinished() const = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IZookeeperProtocolReader> CreateZookeeperProtocolReader(TSharedRef data);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IZookeeperProtocolWriter
+{
+ virtual ~IZookeeperProtocolWriter() = default;
+
+ virtual void WriteByte(char value) = 0;
+ virtual void WriteInt(int value) = 0;
+ virtual void WriteLong(i64 value) = 0;
+
+ virtual void WriteBool(bool value) = 0;
+
+ virtual void WriteString(const TString& value) = 0;
+
+ virtual TSharedRef Finish() = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<IZookeeperProtocolWriter> CreateZookeeperProtocolWriter();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/client/zookeeper/public.h b/yt/yt/client/zookeeper/public.h
new file mode 100644
index 0000000000..7f3c7c6823
--- /dev/null
+++ b/yt/yt/client/zookeeper/public.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+namespace NYT::NZookeeperClient {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TZookeeperPath = TString;
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TSessionId = ui64;
+
+constexpr TSessionId NullSessionId = 0;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NZookeeperClient
diff --git a/yt/yt/client/zookeeper/requests.cpp b/yt/yt/client/zookeeper/requests.cpp
new file mode 100644
index 0000000000..26eafd1a03
--- /dev/null
+++ b/yt/yt/client/zookeeper/requests.cpp
@@ -0,0 +1,28 @@
+#include "requests.h"
+
+namespace NYT::NZookeeper {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TReqStartSession::Deserialize(IZookeeperProtocolReader* reader)
+{
+ ProtocolVersion = reader->ReadInt();
+ LastZxidSeen = reader->ReadLong();
+ Timeout = TDuration::MilliSeconds(reader->ReadInt());
+ SessionId = reader->ReadLong();
+ Password = reader->ReadString();
+ ReadOnly = reader->ReadBool();
+}
+
+void TRspStartSession::Serialize(IZookeeperProtocolWriter* writer) const
+{
+ writer->WriteInt(ProtocolVersion);
+ writer->WriteInt(Timeout.MilliSeconds());
+ writer->WriteLong(SessionId);
+ writer->WriteString(Password);
+ writer->WriteBool(ReadOnly);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/client/zookeeper/requests.h b/yt/yt/client/zookeeper/requests.h
new file mode 100644
index 0000000000..14a1e20c17
--- /dev/null
+++ b/yt/yt/client/zookeeper/requests.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "public.h"
+
+#include "protocol.h"
+
+namespace NYT::NZookeeper {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ERequestType,
+ ((None) (-1))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TReqStartSession
+{
+ int ProtocolVersion = -1;
+ i64 LastZxidSeen = -1;
+ TDuration Timeout = TDuration::Zero();
+ i64 SessionId = -1;
+ TString Password;
+ bool ReadOnly = false;
+
+ void Deserialize(IZookeeperProtocolReader* reader);
+};
+
+struct TRspStartSession
+{
+ int ProtocolVersion = -1;
+ TDuration Timeout = TDuration::Zero();
+ i64 SessionId = -1;
+ TString Password;
+ bool ReadOnly = false;
+
+ void Serialize(IZookeeperProtocolWriter* writer) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NZookeeper
diff --git a/yt/yt/core/misc/copyable_atomic.h b/yt/yt/core/misc/copyable_atomic.h
new file mode 100644
index 0000000000..241287ffd0
--- /dev/null
+++ b/yt/yt/core/misc/copyable_atomic.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <atomic>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TCopyableAtomic
+{
+public:
+ T Load() const
+ {
+ return T_.load();
+ }
+
+ void Store(T v)
+ {
+ T_.store(v);
+ }
+
+ TCopyableAtomic() = default;
+
+ TCopyableAtomic(T v)
+ : T_(v)
+ { }
+
+ TCopyableAtomic(const TCopyableAtomic& other)
+ : T_(other.T_.load())
+ { }
+
+ TCopyableAtomic& operator = (const TCopyableAtomic& other)
+ {
+ T_ = other.T_.load();
+ return *this;
+ }
+
+ TCopyableAtomic(TCopyableAtomic&& other)
+ : T_(other.T_.load())
+ { }
+
+ TCopyableAtomic& operator = (TCopyableAtomic&& other)
+ {
+ T_ = other.T_.load();
+ return *this;
+ }
+
+private:
+ std::atomic<T> T_ = {};
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/CMakeLists.darwin-x86_64.txt b/yt/yt/library/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..20f7fe76fa
--- /dev/null
+++ b/yt/yt/library/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,14 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(profiling)
+add_subdirectory(syncmap)
+add_subdirectory(tracing)
+add_subdirectory(tvm)
+add_subdirectory(undumpable)
+add_subdirectory(ytprof)
diff --git a/yt/yt/library/CMakeLists.linux-aarch64.txt b/yt/yt/library/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..d26f3bae28
--- /dev/null
+++ b/yt/yt/library/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,20 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(auth)
+add_subdirectory(decimal)
+add_subdirectory(erasure)
+add_subdirectory(numeric)
+add_subdirectory(profiling)
+add_subdirectory(quantile_digest)
+add_subdirectory(re2)
+add_subdirectory(syncmap)
+add_subdirectory(tracing)
+add_subdirectory(tvm)
+add_subdirectory(undumpable)
+add_subdirectory(ytprof)
diff --git a/yt/yt/library/CMakeLists.linux-x86_64.txt b/yt/yt/library/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..d26f3bae28
--- /dev/null
+++ b/yt/yt/library/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,20 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(auth)
+add_subdirectory(decimal)
+add_subdirectory(erasure)
+add_subdirectory(numeric)
+add_subdirectory(profiling)
+add_subdirectory(quantile_digest)
+add_subdirectory(re2)
+add_subdirectory(syncmap)
+add_subdirectory(tracing)
+add_subdirectory(tvm)
+add_subdirectory(undumpable)
+add_subdirectory(ytprof)
diff --git a/yt/yt/library/CMakeLists.txt b/yt/yt/library/CMakeLists.txt
index 20f7fe76fa..f8b31df0c1 100644
--- a/yt/yt/library/CMakeLists.txt
+++ b/yt/yt/library/CMakeLists.txt
@@ -6,9 +6,12 @@
# original buildsystem will not be accepted.
-add_subdirectory(profiling)
-add_subdirectory(syncmap)
-add_subdirectory(tracing)
-add_subdirectory(tvm)
-add_subdirectory(undumpable)
-add_subdirectory(ytprof)
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/library/CMakeLists.windows-x86_64.txt b/yt/yt/library/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..20f7fe76fa
--- /dev/null
+++ b/yt/yt/library/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,14 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(profiling)
+add_subdirectory(syncmap)
+add_subdirectory(tracing)
+add_subdirectory(tvm)
+add_subdirectory(undumpable)
+add_subdirectory(ytprof)
diff --git a/yt/yt/library/auth/CMakeLists.linux-aarch64.txt b/yt/yt/library/auth/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..7fc0cd3347
--- /dev/null
+++ b/yt/yt/library/auth/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,25 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-auth)
+target_compile_options(yt-library-auth PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-auth PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+ yt-library-tvm
+)
+target_sources(yt-library-auth PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/auth/auth.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/auth/authentication_options.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/auth/credentials_injecting_channel.cpp
+)
diff --git a/yt/yt/library/auth/CMakeLists.linux-x86_64.txt b/yt/yt/library/auth/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..7fc0cd3347
--- /dev/null
+++ b/yt/yt/library/auth/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,25 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-auth)
+target_compile_options(yt-library-auth PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-auth PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+ yt-library-tvm
+)
+target_sources(yt-library-auth PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/auth/auth.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/auth/authentication_options.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/auth/credentials_injecting_channel.cpp
+)
diff --git a/yt/yt/library/auth/CMakeLists.txt b/yt/yt/library/auth/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/library/auth/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/library/auth/auth.cpp b/yt/yt/library/auth/auth.cpp
new file mode 100644
index 0000000000..1a2310250b
--- /dev/null
+++ b/yt/yt/library/auth/auth.cpp
@@ -0,0 +1,64 @@
+#include "auth.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <util/folder/dirut.h>
+#include <util/folder/path.h>
+
+#include <util/stream/file.h>
+
+#include <util/string/strip.h>
+
+#include <util/system/env.h>
+
+namespace NYT::NAuth {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void ValidateToken(TStringBuf token)
+{
+ for (size_t i = 0; i < token.size(); ++i) {
+ auto ch = static_cast<ui8>(token[i]);
+ if (ch < 0x21 || ch > 0x7e) {
+ THROW_ERROR_EXCEPTION("Incorrect token character %qv at position %v", ch, i);
+ }
+ }
+}
+
+std::optional<TString> LoadTokenFromFile(TStringBuf tokenPath)
+{
+ TFsPath path(tokenPath);
+ if (path.IsFile()) {
+ auto token = Strip(TIFStream(path).ReadAll());
+ ValidateToken(token);
+ return token;
+ } else {
+ return std::nullopt;
+ }
+}
+
+std::optional<TString> LoadToken()
+{
+ std::optional<TString> token;
+ if (auto envToken = GetEnv("YT_TOKEN")) {
+ token = envToken;
+ } else if (auto envToken = GetEnv("YT_SECURE_VAULT_YT_TOKEN")) {
+ // If this code runs inside a vanilla operation in YT
+ // it should not use regular environment variable `YT_TOKEN`
+ // because it would be visible in UI.
+ // Token should be passed via `secure_vault` parameter in operation spec.
+ token = envToken;
+ } else if (auto tokenPath = GetEnv("YT_TOKEN_PATH")) {
+ token = LoadTokenFromFile(tokenPath);
+ } else {
+ token = LoadTokenFromFile(JoinFsPaths(GetHomeDir(), ".yt/token"));
+ }
+ if (token) {
+ ValidateToken(*token);
+ }
+ return token;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NAuth
diff --git a/yt/yt/library/auth/auth.h b/yt/yt/library/auth/auth.h
new file mode 100644
index 0000000000..4e37d5a9a8
--- /dev/null
+++ b/yt/yt/library/auth/auth.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <util/generic/string.h>
+
+#include <optional>
+
+namespace NYT {
+namespace NAuth {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Checks that |token| contains only allowed symbols, throws exception otherwise.
+void ValidateToken(TStringBuf token);
+
+// Tries to load the token from the specified file.
+// Returns |std::nullopt| if |tokenPath| is not a valid file name.
+// Else validates the token and returns it.
+std::optional<TString> LoadTokenFromFile(TStringBuf tokenPath);
+
+// Performs standard sequence of attempts to find the token:
+// $YT_TOKEN, $YT_SECURE_VAULT_YT_TOKEN, $YT_TOKEN_PATH, "$HOME/.yt/token".
+std::optional<TString> LoadToken();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NAuth
+} // namespace NYT
diff --git a/yt/yt/library/auth/authentication_options.cpp b/yt/yt/library/auth/authentication_options.cpp
new file mode 100644
index 0000000000..b8b000b560
--- /dev/null
+++ b/yt/yt/library/auth/authentication_options.cpp
@@ -0,0 +1,55 @@
+#include "authentication_options.h"
+
+#include <yt/yt/core/rpc/authentication_identity.h>
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NAuth {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TAuthenticationOptions TAuthenticationOptions::FromUser(const TString& user, const std::optional<TString>& userTag)
+{
+ return {
+ .User = user,
+ .UserTag = userTag.value_or(user),
+ };
+}
+
+TAuthenticationOptions TAuthenticationOptions::FromAuthenticationIdentity(const NRpc::TAuthenticationIdentity& identity)
+{
+ return FromUser(identity.User, identity.UserTag);
+}
+
+TAuthenticationOptions TAuthenticationOptions::FromToken(const TString& token)
+{
+ return {
+ .Token = token
+ };
+}
+
+TAuthenticationOptions TAuthenticationOptions::FromServiceTicketAuth(const IServiceTicketAuthPtr& ticketAuth)
+{
+ return {
+ .ServiceTicketAuth = ticketAuth
+ };
+}
+
+const TString& TAuthenticationOptions::GetAuthenticatedUser() const
+{
+ static const TString UnknownUser("<unknown>");
+ return User ? *User : UnknownUser;
+}
+
+NRpc::TAuthenticationIdentity TAuthenticationOptions::GetAuthenticationIdentity() const
+{
+ if (!User) {
+ THROW_ERROR_EXCEPTION("Authenticated user is not specified in client options");
+ }
+ return NRpc::TAuthenticationIdentity(*User, UserTag.value_or(*User));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NAuth
+
diff --git a/yt/yt/library/auth/authentication_options.h b/yt/yt/library/auth/authentication_options.h
new file mode 100644
index 0000000000..d08e76fc76
--- /dev/null
+++ b/yt/yt/library/auth/authentication_options.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/library/tvm/public.h>
+
+#include <yt/yt/core/rpc/authentication_identity.h>
+
+#include <optional>
+
+namespace NYT::NAuth {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TAuthenticationOptions
+{
+ static TAuthenticationOptions FromUser(const TString& user, const std::optional<TString>& userTag = {});
+ static TAuthenticationOptions FromAuthenticationIdentity(const NRpc::TAuthenticationIdentity& identity);
+ static TAuthenticationOptions FromToken(const TString& token);
+ static TAuthenticationOptions FromServiceTicketAuth(const IServiceTicketAuthPtr& ticketAuth);
+
+ const TString& GetAuthenticatedUser() const;
+ NRpc::TAuthenticationIdentity GetAuthenticationIdentity() const;
+
+ //! This field is not required for authentication.
+ //! When not specified, user is derived from credentials. When
+ //! specified, server additionally checks that #User is
+ //! matching user derived from credentials.
+ std::optional<TString> User;
+
+ //! Provides an additional annotation to differentiate between
+ //! various clients that authenticate via the same effective user.
+ std::optional<TString> UserTag;
+
+ std::optional<TString> Token;
+ std::optional<TString> SessionId;
+ std::optional<TString> SslSessionId;
+ std::optional<IServiceTicketAuthPtr> ServiceTicketAuth;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NAuth
diff --git a/yt/yt/library/auth/credentials_injecting_channel.cpp b/yt/yt/library/auth/credentials_injecting_channel.cpp
new file mode 100644
index 0000000000..e605161331
--- /dev/null
+++ b/yt/yt/library/auth/credentials_injecting_channel.cpp
@@ -0,0 +1,251 @@
+#include "credentials_injecting_channel.h"
+
+#include "authentication_options.h"
+
+#include <yt/yt/core/rpc/client.h>
+#include <yt/yt/core/rpc/channel_detail.h>
+
+#include <yt/yt/library/tvm/tvm_base.h>
+
+#include <yt/yt_proto/yt/core/rpc/proto/rpc.pb.h>
+
+namespace NYT::NAuth {
+
+using namespace NRpc;
+
+////////////////////////////////////////////////////////////////////////////////
+
+NRpc::IChannelPtr CreateCredentialsInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+{
+ if (options.Token) {
+ return CreateTokenInjectingChannel(
+ underlyingChannel,
+ options);
+ } else if (options.SessionId || options.SslSessionId) {
+ return CreateCookieInjectingChannel(
+ underlyingChannel,
+ options);
+ } else if (options.ServiceTicketAuth) {
+ return CreateServiceTicketInjectingChannel(
+ underlyingChannel,
+ options);
+ } else {
+ return CreateUserInjectingChannel(underlyingChannel, options);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUserInjectingChannel
+ : public TChannelWrapper
+{
+public:
+ TUserInjectingChannel(
+ IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+ : TChannelWrapper(std::move(underlyingChannel))
+ , User_(options.User)
+ , UserTag_(options.UserTag)
+ { }
+
+ IClientRequestControlPtr Send(
+ IClientRequestPtr request,
+ IClientResponseHandlerPtr responseHandler,
+ const TSendOptions& options) override
+ {
+ try {
+ DoInject(request);
+ } catch (const std::exception& ex) {
+ responseHandler->HandleError(TError(ex));
+ return nullptr;
+ }
+
+ return TChannelWrapper::Send(
+ std::move(request),
+ std::move(responseHandler),
+ options);
+ }
+
+protected:
+ virtual void DoInject(const IClientRequestPtr& request)
+ {
+ if (User_) {
+ request->SetUser(*User_);
+ }
+ if (UserTag_ && UserTag_ != User_) {
+ request->SetUserTag(*UserTag_);
+ }
+ }
+
+private:
+ const std::optional<TString> User_;
+ const std::optional<TString> UserTag_;
+};
+
+IChannelPtr CreateUserInjectingChannel(
+ IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+{
+ YT_VERIFY(underlyingChannel);
+ return New<TUserInjectingChannel>(std::move(underlyingChannel), options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTokenInjectingChannel
+ : public TUserInjectingChannel
+{
+public:
+ TTokenInjectingChannel(
+ IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+ : TUserInjectingChannel(std::move(underlyingChannel), options)
+ , Token_(*options.Token)
+ { }
+
+protected:
+ void DoInject(const IClientRequestPtr& request) override
+ {
+ TUserInjectingChannel::DoInject(request);
+
+ auto* ext = request->Header().MutableExtension(NRpc::NProto::TCredentialsExt::credentials_ext);
+ ext->set_token(Token_);
+ }
+
+private:
+ const TString Token_;
+};
+
+IChannelPtr CreateTokenInjectingChannel(
+ IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+{
+ YT_VERIFY(underlyingChannel);
+ YT_VERIFY(options.Token);
+ return New<TTokenInjectingChannel>(
+ std::move(underlyingChannel),
+ options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TCookieInjectingChannel
+ : public TUserInjectingChannel
+{
+public:
+ TCookieInjectingChannel(
+ IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+ : TUserInjectingChannel(std::move(underlyingChannel), options)
+ , SessionId_(options.SessionId.value_or(TString()))
+ , SslSessionId_(options.SslSessionId.value_or(TString()))
+ { }
+
+protected:
+ void DoInject(const IClientRequestPtr& request) override
+ {
+ TUserInjectingChannel::DoInject(request);
+
+ auto* ext = request->Header().MutableExtension(NRpc::NProto::TCredentialsExt::credentials_ext);
+ ext->set_session_id(SessionId_);
+ ext->set_ssl_session_id(SslSessionId_);
+ }
+
+private:
+ const TString SessionId_;
+ const TString SslSessionId_;
+};
+
+IChannelPtr CreateCookieInjectingChannel(
+ IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+{
+ YT_VERIFY(underlyingChannel);
+ YT_VERIFY(options.SessionId.has_value() || options.SslSessionId.has_value());
+ return New<TCookieInjectingChannel>(
+ std::move(underlyingChannel),
+ options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TServiceTicketInjectingChannel
+ : public TUserInjectingChannel
+{
+public:
+ TServiceTicketInjectingChannel(
+ IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+ : TUserInjectingChannel(std::move(underlyingChannel), options)
+ , TicketAuth_(*options.ServiceTicketAuth)
+ { }
+
+protected:
+ void DoInject(const IClientRequestPtr& request) override
+ {
+ TUserInjectingChannel::DoInject(request);
+
+ auto* ext = request->Header().MutableExtension(NRpc::NProto::TCredentialsExt::credentials_ext);
+ ext->set_service_ticket(TicketAuth_->IssueServiceTicket());
+ }
+
+private:
+ NAuth::IServiceTicketAuthPtr TicketAuth_;
+};
+
+NRpc::IChannelPtr CreateServiceTicketInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options)
+{
+ YT_VERIFY(underlyingChannel);
+ YT_VERIFY(options.ServiceTicketAuth && *options.ServiceTicketAuth);
+ return New<TServiceTicketInjectingChannel>(
+ std::move(underlyingChannel),
+ options);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TServiceTicketInjectingChannelFactory
+ : public IChannelFactory
+{
+public:
+ TServiceTicketInjectingChannelFactory(
+ IChannelFactoryPtr underlyingFactory,
+ IServiceTicketAuthPtr serviceTicketAuth)
+ : UnderlyingFactory_(std::move(underlyingFactory))
+ , ServiceTicketAuth_(std::move(serviceTicketAuth))
+ { }
+
+ IChannelPtr CreateChannel(const TString& address) override
+ {
+ auto channel = UnderlyingFactory_->CreateChannel(address);
+ if (!ServiceTicketAuth_) {
+ return channel;
+ }
+ return CreateServiceTicketInjectingChannel(
+ std::move(channel),
+ TAuthenticationOptions::FromServiceTicketAuth(ServiceTicketAuth_));
+ }
+
+private:
+ IChannelFactoryPtr UnderlyingFactory_;
+ IServiceTicketAuthPtr ServiceTicketAuth_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IChannelFactoryPtr CreateServiceTicketInjectingChannelFactory(
+ IChannelFactoryPtr underlyingFactory,
+ IServiceTicketAuthPtr serviceTicketAuth)
+{
+ return New<TServiceTicketInjectingChannelFactory>(
+ std::move(underlyingFactory),
+ std::move(serviceTicketAuth));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NAuth
diff --git a/yt/yt/library/auth/credentials_injecting_channel.h b/yt/yt/library/auth/credentials_injecting_channel.h
new file mode 100644
index 0000000000..65a9a458d2
--- /dev/null
+++ b/yt/yt/library/auth/credentials_injecting_channel.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/library/tvm/public.h>
+
+#include <yt/yt/core/rpc/public.h>
+
+namespace NYT::NAuth {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NRpc::IChannelPtr CreateCredentialsInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options);
+
+////////////////////////////////////////////////////////////////////////////////
+
+NRpc::IChannelPtr CreateUserInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options);
+
+NRpc::IChannelPtr CreateTokenInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options);
+
+NRpc::IChannelPtr CreateCookieInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options);
+
+NRpc::IChannelPtr CreateServiceTicketInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options);
+
+NRpc::IChannelPtr CreateServiceTicketInjectingChannel(
+ NRpc::IChannelPtr underlyingChannel,
+ const TAuthenticationOptions& options);
+
+////////////////////////////////////////////////////////////////////////////////
+
+NRpc::IChannelFactoryPtr CreateServiceTicketInjectingChannelFactory(
+ NRpc::IChannelFactoryPtr underlyingFactory,
+ IServiceTicketAuthPtr serviceTicketAuth);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NAuth
diff --git a/yt/yt/library/auth/public.h b/yt/yt/library/auth/public.h
new file mode 100644
index 0000000000..bfe2da01c4
--- /dev/null
+++ b/yt/yt/library/auth/public.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <library/cpp/yt/memory/ref_counted.h>
+
+namespace NYT::NAuth {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TAuthenticationOptions;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NAuth
diff --git a/yt/yt/library/auth/unittests/auth_ut.cpp b/yt/yt/library/auth/unittests/auth_ut.cpp
new file mode 100644
index 0000000000..86588d7ff4
--- /dev/null
+++ b/yt/yt/library/auth/unittests/auth_ut.cpp
@@ -0,0 +1,101 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/library/auth/auth.h>
+
+#include <yt/yt/core/misc/finally.h>
+
+#include <util/folder/tempdir.h>
+
+#include <util/stream/file.h>
+
+#include <util/system/env.h>
+#include <util/system/fs.h>
+#include <util/system/tempfile.h>
+
+namespace NYT::NAuth {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(Auth, ValidateToken)
+{
+ EXPECT_NO_THROW(ValidateToken("ABACABA-3289-ABCDEF"));
+ EXPECT_NO_THROW(ValidateToken("\x21-ohh-\x7e"));
+ EXPECT_NO_THROW(ValidateToken(""));
+
+ EXPECT_THROW_THAT(ValidateToken("\x20"), ::testing::HasSubstr("Incorrect token character"));
+ EXPECT_THROW_THAT(ValidateToken("ABACABA-\x20"), ::testing::HasSubstr("Incorrect token character"));
+ EXPECT_THROW_THAT(ValidateToken("ABACABA-\x7f"), ::testing::HasSubstr("Incorrect token character"));
+}
+
+TEST(Auth, LoadTokenFromFile)
+{
+ TTempFileHandle tempFile;
+ {
+ TOFStream os(tempFile);
+ os << "Some-happy-token";
+ }
+ auto token = LoadTokenFromFile(tempFile.Name());
+ EXPECT_EQ(token, "Some-happy-token");
+
+ EXPECT_EQ(LoadTokenFromFile("/a/b/c/I_HOPE_YOU_DONT_EXIST"), std::nullopt);
+
+ {
+ TOFStream os(tempFile);
+ os << "Unhappy-\x20-token";
+ }
+ EXPECT_THROW_THAT(LoadTokenFromFile(tempFile.Name()), ::testing::HasSubstr("Incorrect token character"));
+}
+
+TEST(Auth, LoadToken)
+{
+ auto oldYtToken = GetEnv("YT_TOKEN");
+ auto oldYtSecureVaultToken = GetEnv("YT_SECURE_VAULT_YT_TOKEN");
+ auto oldYtTokenPath = GetEnv("YT_TOKEN_PATH");
+ auto oldHome = GetEnv("HOME");
+
+ auto guard = Finally([&] {
+ SetEnv("YT_TOKEN", oldYtToken);
+ SetEnv("YT_SECURE_VAULT_YT_TOKEN", oldYtSecureVaultToken);
+ SetEnv("YT_TOKEN_PATH", oldYtTokenPath);
+ SetEnv("HOME", oldHome);
+ });
+
+ SetEnv("YT_TOKEN", "Token-from-YT_TOKEN");
+ SetEnv("YT_SECURE_VAULT_YT_TOKEN", "Token-from-YT_SECURE_VAULT_YT_TOKEN");
+
+ TTempFileHandle tokenPath;
+ {
+ TOFStream os(tokenPath);
+ os << "Token-from-YT_TOKEN_PATH";
+ }
+ SetEnv("YT_TOKEN_PATH", tokenPath.Name());
+
+ TTempDir home;
+ auto dotYt = home.Path().Child(".yt");
+ ASSERT_TRUE(NFs::MakeDirectory(dotYt.GetPath()));
+ TTempFile dotYtToken(dotYt.Child("token"));
+ {
+ TOFStream os(dotYtToken.Name());
+ os << "Token-from-HOME";
+ }
+ SetEnv("HOME", home.Path());
+
+ EXPECT_EQ(LoadToken(), "Token-from-YT_TOKEN");
+ SetEnv("YT_TOKEN", "");
+ EXPECT_EQ(LoadToken(), "Token-from-YT_SECURE_VAULT_YT_TOKEN");
+ SetEnv("YT_SECURE_VAULT_YT_TOKEN", "");
+ EXPECT_EQ(LoadToken(), "Token-from-YT_TOKEN_PATH");
+ SetEnv("YT_TOKEN_PATH", "");
+ EXPECT_EQ(LoadToken(), "Token-from-HOME");
+
+ TTempDir emptyHome;
+ SetEnv("HOME", emptyHome.Path());
+ EXPECT_EQ(LoadToken(), std::nullopt);
+
+ SetEnv("YT_TOKEN", "Invalid-token-\x01");
+ EXPECT_THROW_THAT(LoadToken(), ::testing::HasSubstr("Incorrect token character"));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NAuth
diff --git a/yt/yt/library/auth/unittests/ya.make b/yt/yt/library/auth/unittests/ya.make
new file mode 100644
index 0000000000..36888b9695
--- /dev/null
+++ b/yt/yt/library/auth/unittests/ya.make
@@ -0,0 +1,22 @@
+GTEST(unittester-library-auth)
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+ALLOCATOR(YT)
+
+SRCS(
+ auth_ut.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ yt/yt/build
+ yt/yt/core
+ yt/yt/core/test_framework
+ yt/yt/library/auth
+)
+
+SIZE(MEDIUM)
+
+END()
diff --git a/yt/yt/library/auth/ya.make b/yt/yt/library/auth/ya.make
new file mode 100644
index 0000000000..6bb1c640f6
--- /dev/null
+++ b/yt/yt/library/auth/ya.make
@@ -0,0 +1,20 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ auth.cpp
+ authentication_options.cpp
+ credentials_injecting_channel.cpp
+)
+
+PEERDIR(
+ yt/yt/core
+ yt/yt/library/tvm
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ unittests
+)
diff --git a/yt/yt/library/codegen/caller.h b/yt/yt/library/codegen/caller.h
new file mode 100644
index 0000000000..2aacc09fde
--- /dev/null
+++ b/yt/yt/library/codegen/caller.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <yt/yt/core/actions/callback.h>
+
+namespace NYT::NCodegen {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TSignature>
+class TCGCaller;
+
+template <class TResult, class... TArgs>
+class TCGCaller<TResult(TArgs...)>
+ : public NDetail::TBindStateBase
+{
+private:
+ using TSignature = TResult(*)(TArgs...);
+
+public:
+ explicit TCGCaller(
+#ifdef YT_ENABLE_BIND_LOCATION_TRACKING
+ const TSourceLocation& location,
+#endif
+ TRefCountedPtr module,
+ TSignature function)
+ : NDetail::TBindStateBase(
+#ifdef YT_ENABLE_BIND_LOCATION_TRACKING
+ location
+#endif
+ )
+ , Module_(std::move(module))
+ , FunctionPointer_(function)
+ { }
+
+ TResult Run(TCallArg<TArgs>... args)
+ {
+ return FunctionPointer_(std::forward<TArgs>(args)...);
+ }
+
+ static TResult StaticInvoke(TCallArg<TArgs>... args, NYT::NDetail::TBindStateBase* stateBase)
+ {
+ auto* state = static_cast<TCGCaller*>(stateBase);
+ return state->Run(std::forward<TArgs>(args)...);
+ }
+
+private:
+ TRefCountedPtr Module_;
+ TSignature FunctionPointer_ = nullptr;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+}
+
diff --git a/yt/yt/library/decimal/CMakeLists.linux-aarch64.txt b/yt/yt/library/decimal/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..7a98d8af79
--- /dev/null
+++ b/yt/yt/library/decimal/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,23 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-decimal)
+target_compile_options(yt-library-decimal PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-decimal PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+ library-cpp-int128
+)
+target_sources(yt-library-decimal PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/decimal/decimal.cpp
+)
diff --git a/yt/yt/library/decimal/CMakeLists.linux-x86_64.txt b/yt/yt/library/decimal/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..7a98d8af79
--- /dev/null
+++ b/yt/yt/library/decimal/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,23 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-decimal)
+target_compile_options(yt-library-decimal PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-decimal PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+ library-cpp-int128
+)
+target_sources(yt-library-decimal PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/decimal/decimal.cpp
+)
diff --git a/yt/yt/library/decimal/CMakeLists.txt b/yt/yt/library/decimal/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/library/decimal/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/library/decimal/decimal.cpp b/yt/yt/library/decimal/decimal.cpp
new file mode 100644
index 0000000000..2789f774c6
--- /dev/null
+++ b/yt/yt/library/decimal/decimal.cpp
@@ -0,0 +1,571 @@
+#include "decimal.h"
+
+#include <yt/yt/core/misc/error.h>
+
+#include <util/generic/ylimits.h>
+#include <util/string/hex.h>
+#include <util/system/byteorder.h>
+
+#include <type_traits>
+
+namespace NYT::NDecimal {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+constexpr bool ValidDecimalUnderlyingInteger =
+ std::is_same_v<T, i32> ||
+ std::is_same_v<T, i64> ||
+ std::is_same_v<T, i128>;
+
+template <typename T>
+struct TDecimalTraits
+{
+ static_assert(ValidDecimalUnderlyingInteger<T>);
+
+ static constexpr T Nan = std::numeric_limits<T>::max();
+ static constexpr T PlusInf = std::numeric_limits<T>::max() - 1;
+ static constexpr T MinusInf = -PlusInf;
+
+ static constexpr T MinSpecialValue = PlusInf;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr int GetDecimalBinaryValueSize(int precision)
+{
+ if (precision > 0) {
+ if (precision <= 9) {
+ return 4;
+ } else if (precision <= 18) {
+ return 8;
+ } else if (precision <= 35) {
+ return 16;
+ }
+ }
+ return 0;
+}
+
+static constexpr i128 DecimalIntegerMaxValueTable[] = {
+ i128{0}, // 0
+ i128{9}, // 1
+ i128{99}, // 2
+ i128{999}, // 3
+ i128{9999}, // 4
+ i128{99999}, // 5
+ i128{999999}, // 6
+ i128{9999999}, // 7
+ i128{99999999}, // 8
+ i128{999999999}, // 9
+ i128{9999999999ul}, // 10
+ i128{99999999999ul}, // 11
+ i128{999999999999ul}, // 12
+ i128{9999999999999ul}, // 13
+ i128{99999999999999ul}, // 14
+ i128{999999999999999ul}, // 15
+ i128{9999999999999999ul}, // 16
+ i128{99999999999999999ul}, // 17
+ i128{999999999999999999ul}, // 18
+
+ // 128 bits
+ //
+ // Generated by fair Python script:
+ //
+ // def print_max_decimal(precision):
+ // max_value = int("9" * precision)
+ // hex_value = hex(max_value)[2:] # strip 0x
+ // hex_value = hex_value.strip("L")
+ // print("i128{{0x{}ul}} | (i128{{0x{}l}} << 64), // {}".format(
+ // hex_value[-16:],
+ // hex_value[:-16] or "0",
+ // precision))
+ // for i in range(19, 36):
+ // print_max_decimal(i)
+ //
+ i128{0x8ac7230489e7fffful} | (i128{0x0l} << 64), // 19
+ i128{0x6bc75e2d630ffffful} | (i128{0x5l} << 64), // 20
+ i128{0x35c9adc5de9ffffful} | (i128{0x36l} << 64), // 21
+ i128{0x19e0c9bab23ffffful} | (i128{0x21el} << 64), // 22
+ i128{0x02c7e14af67ffffful} | (i128{0x152dl} << 64), // 23
+ i128{0x1bcecceda0fffffful} | (i128{0xd3c2l} << 64), // 24
+ i128{0x1614014849fffffful} | (i128{0x84595l} << 64), // 25
+ i128{0xdcc80cd2e3fffffful} | (i128{0x52b7d2l} << 64), // 26
+ i128{0x9fd0803ce7fffffful} | (i128{0x33b2e3cl} << 64), // 27
+ i128{0x3e2502610ffffffful} | (i128{0x204fce5el} << 64), // 28
+ i128{0x6d7217ca9ffffffful} | (i128{0x1431e0fael} << 64), // 29
+ i128{0x4674edea3ffffffful} | (i128{0xc9f2c9cd0l} << 64), // 30
+ i128{0xc0914b267ffffffful} | (i128{0x7e37be2022l} << 64), // 31
+ i128{0x85acef80fffffffful} | (i128{0x4ee2d6d415bl} << 64), // 32
+ i128{0x38c15b09fffffffful} | (i128{0x314dc6448d93l} << 64), // 33
+ i128{0x378d8e63fffffffful} | (i128{0x1ed09bead87c0l} << 64), // 34
+ i128{0x2b878fe7fffffffful} | (i128{0x13426172c74d82l} << 64), // 35
+};
+
+template<typename T>
+Y_FORCE_INLINE constexpr T GetDecimalMaxIntegerValue(int precision)
+{
+ static_assert(ValidDecimalUnderlyingInteger<T>);
+
+ if (TDecimal::GetValueBinarySize(precision) <= sizeof(T)) {
+ return DecimalIntegerMaxValueTable[precision];
+ } else {
+ YT_ABORT();
+ }
+}
+
+template <typename T>
+Y_FORCE_INLINE constexpr auto DecimalIntegerToUnsigned(T value)
+{
+ static_assert(ValidDecimalUnderlyingInteger<T>);
+
+ if constexpr (std::is_same_v<T, i128>) {
+ return ui128(value);
+ } else {
+ using TU = std::make_unsigned_t<T>;
+ return static_cast<TU>(value);
+ }
+}
+
+template <typename T>
+static Y_FORCE_INLINE T DecimalHostToInet(T value)
+{
+ if constexpr (std::is_same_v<T, i128> || std::is_same_v<T, ui128>) {
+ return T(::HostToInet(GetLow(value)), ::HostToInet(GetHigh(value)));
+ } else {
+ return ::HostToInet(value);
+ }
+}
+
+template <typename T>
+static Y_FORCE_INLINE T DecimalInetToHost(T value)
+{
+ if constexpr (std::is_same_v<T, i128> || std::is_same_v<T, ui128>) {
+ return T(::InetToHost(GetLow(value)), ::InetToHost(GetHigh(value)));
+ } else {
+ return ::InetToHost(value);
+ }
+}
+
+template <typename T>
+static T DecimalBinaryToIntegerUnchecked(TStringBuf binaryValue)
+{
+ T result;
+ memcpy(&result, binaryValue.Data(), sizeof(result));
+ result = DecimalInetToHost(result);
+
+ constexpr auto one = DecimalIntegerToUnsigned(T{1});
+ result = static_cast<T>(DecimalIntegerToUnsigned(result) ^ (one << (sizeof(T) * 8 - 1)));
+
+ return result;
+}
+
+template<typename T>
+static void DecimalIntegerToBinaryUnchecked(T decodedValue, void* buf)
+{
+ auto unsignedValue = DecimalIntegerToUnsigned(decodedValue);
+ constexpr auto one = DecimalIntegerToUnsigned(T{1});
+ unsignedValue ^= (one << (sizeof(T) * 8 - 1));
+ unsignedValue = DecimalHostToInet(unsignedValue);
+ memcpy(buf, &unsignedValue, sizeof(unsignedValue));
+}
+
+static void CheckDecimalValueSize(TStringBuf value, int precision, int scale)
+{
+ int expectedSize = TDecimal::GetValueBinarySize(precision);
+ if (std::ssize(value) != expectedSize) {
+ THROW_ERROR_EXCEPTION(
+ "Decimal<%v,%v> binary value representation has invalid length: actual %v, expected %v",
+ precision,
+ scale,
+ value.Size(),
+ expectedSize);
+ }
+}
+
+static Y_FORCE_INLINE TStringBuf PlaceOnBuffer(TStringBuf value, char* buffer)
+{
+ memcpy(buffer, value.Data(), value.Size());
+ return TStringBuf(buffer, value.Size());
+}
+
+// TODO(ermolovd): make it FASTER (check NYT::WriteDecIntToBufferBackwards)
+template<typename T>
+static TStringBuf WriteTextDecimalUnchecked(T decodedValue, int scale, char* buffer)
+{
+ i8 digits[std::numeric_limits<T>::digits + 1] = {0,};
+ static constexpr auto ten = DecimalIntegerToUnsigned(T{10});
+
+ const bool negative = decodedValue < 0;
+ auto absValue = DecimalIntegerToUnsigned(negative ? -decodedValue : decodedValue);
+
+ if (decodedValue == TDecimalTraits<T>::MinusInf) {
+ static constexpr TStringBuf minusInf = "-inf";
+ return PlaceOnBuffer(minusInf, buffer);
+ } else if (decodedValue == TDecimalTraits<T>::PlusInf) {
+ static constexpr TStringBuf inf = "inf";
+ return PlaceOnBuffer(inf, buffer);
+ } else if (decodedValue == TDecimalTraits<T>::Nan) {
+ static constexpr TStringBuf nan = "nan";
+ return PlaceOnBuffer(nan, buffer);
+ }
+
+ auto* curDigit = digits;
+ while (absValue > 0) {
+ *curDigit = static_cast<int>(absValue % ten);
+ absValue = absValue / ten;
+ curDigit++;
+ }
+ YT_VERIFY(curDigit <= digits + std::size(digits));
+
+ if (curDigit - digits <= scale) {
+ curDigit = digits + scale + 1;
+ }
+
+ char* bufferPosition = buffer;
+ if (negative) {
+ *bufferPosition = '-';
+ ++bufferPosition;
+ }
+ while (curDigit > digits + scale) {
+ --curDigit;
+ *bufferPosition = '0' + *curDigit;
+ ++bufferPosition;
+ }
+ if (scale > 0) {
+ *bufferPosition = '.';
+ ++bufferPosition;
+ while (curDigit > digits) {
+ --curDigit;
+ *bufferPosition = '0' + *curDigit;
+ ++bufferPosition;
+ }
+ }
+ return TStringBuf(buffer, bufferPosition - buffer);
+}
+
+void ThrowInvalidDecimal(TStringBuf value, int precision, int scale, const char* reason = nullptr)
+{
+ if (reason == nullptr) {
+ THROW_ERROR_EXCEPTION(
+ "String %Qv is not valid Decimal<%v,%v> representation",
+ value,
+ precision,
+ scale);
+ } else {
+ THROW_ERROR_EXCEPTION(
+ "String %Qv is not valid Decimal<%v,%v> representation: %v",
+ value,
+ precision,
+ scale,
+ reason);
+ }
+}
+
+template<typename T>
+T DecimalTextToInteger(TStringBuf textValue, int precision, int scale)
+{
+ if (textValue.empty()) {
+ ThrowInvalidDecimal(textValue, precision, scale);
+ }
+
+ auto cur = textValue.cbegin();
+ auto end = textValue.end();
+
+ bool negative = false;
+ switch (*cur) {
+ case '-':
+ negative = true;
+ [[fallthrough]]; // AUTOGENERATED_FALLTHROUGH_FIXME
+ case '+':
+ ++cur;
+ break;
+ }
+ if (cur == end) {
+ ThrowInvalidDecimal(textValue, precision, scale);
+ }
+
+ switch (*cur) {
+ case 'i':
+ case 'I':
+ if (cur + 3 == end) {
+ ++cur;
+ if (*cur == 'n' || *cur == 'N') {
+ ++cur;
+ if (*cur == 'f' || *cur == 'F') {
+ return negative ? TDecimalTraits<T>::MinusInf : TDecimalTraits<T>::PlusInf;
+ }
+ }
+ }
+ ThrowInvalidDecimal(textValue, precision, scale);
+ [[fallthrough]]; // AUTOGENERATED_FALLTHROUGH_FIXME
+ case 'n':
+ case 'N':
+ if (!negative && cur + 3 == end) {
+ ++cur;
+ if (*cur == 'a' || *cur == 'A') {
+ ++cur;
+ if (*cur == 'n' || *cur == 'N') {
+ return TDecimalTraits<T>::Nan;
+ }
+ }
+ }
+ ThrowInvalidDecimal(textValue, precision, scale);
+ break;
+ }
+
+ T result = 0;
+ int beforePoint = 0;
+ int afterPoint = 0;
+ for (; cur != end; ++cur) {
+ if (*cur == '.') {
+ ++cur;
+ for (; cur != end; ++cur) {
+ int currentDigit = *cur - '0';
+ result *= 10;
+ result += currentDigit;
+ ++afterPoint;
+ if (currentDigit < 0 || currentDigit > 9) {
+ ThrowInvalidDecimal(textValue, precision, scale);
+ }
+ }
+ break;
+ }
+
+ int currentDigit = *cur - '0';
+ result *= 10;
+ result += currentDigit;
+ ++beforePoint;
+ if (currentDigit < 0 || currentDigit > 9) {
+ ThrowInvalidDecimal(textValue, precision, scale);
+ }
+ }
+
+ for (; afterPoint < scale; ++afterPoint) {
+ result *= 10;
+ }
+
+ if (afterPoint > scale) {
+ ThrowInvalidDecimal(textValue, precision, scale, "too many digits after decimal point");
+ }
+
+ if (beforePoint + scale > precision) {
+ ThrowInvalidDecimal(textValue, precision, scale, "too many digits before decimal point");
+ }
+
+ return negative ? -result : result;
+}
+
+template<typename T>
+Y_FORCE_INLINE TStringBuf DecimalBinaryToTextUncheckedImpl(TStringBuf value, int scale, char* buffer)
+{
+ T decoded = DecimalBinaryToIntegerUnchecked<T>(value);
+ return WriteTextDecimalUnchecked(decoded, scale, buffer);
+}
+
+TStringBuf TDecimal::BinaryToText(TStringBuf binaryDecimal, int precision, int scale, char* buffer, size_t bufferSize)
+{
+ ValidatePrecisionAndScale(precision, scale);
+
+ YT_VERIFY(bufferSize >= MaxTextSize);
+ switch (binaryDecimal.Size()) {
+ case 4:
+ return DecimalBinaryToTextUncheckedImpl<i32>(binaryDecimal, scale, buffer);
+ case 8:
+ return DecimalBinaryToTextUncheckedImpl<i64>(binaryDecimal, scale, buffer);
+ case 16:
+ return DecimalBinaryToTextUncheckedImpl<i128>(binaryDecimal, scale, buffer);
+ }
+ CheckDecimalValueSize(binaryDecimal, precision, scale);
+ YT_ABORT();
+}
+
+TString TDecimal::BinaryToText(TStringBuf binaryDecimal, int precision, int scale)
+{
+ TString result;
+ result.ReserveAndResize(MaxTextSize);
+ auto resultSize = BinaryToText(binaryDecimal, precision, scale, result.Detach(), result.size()).size();
+ result.resize(resultSize);
+ return result;
+}
+
+template<typename T>
+TStringBuf TextToBinaryImpl(TStringBuf textDecimal, int precision, int scale, char* buffer)
+{
+ T decoded = DecimalTextToInteger<T>(textDecimal, precision, scale);
+ DecimalIntegerToBinaryUnchecked(decoded, buffer);
+ return TStringBuf(buffer, TDecimal::GetValueBinarySize(precision));
+}
+
+TStringBuf TDecimal::TextToBinary(TStringBuf textValue, int precision, int scale, char* buffer, size_t bufferSize)
+{
+ ValidatePrecisionAndScale(precision, scale);
+
+ YT_VERIFY(bufferSize >= static_cast<size_t>(TDecimal::GetValueBinarySize(precision)));
+
+ int byteSize = TDecimal::GetValueBinarySize(precision);
+ switch (byteSize) {
+ case 4:
+ return TextToBinaryImpl<i32>(textValue, precision, scale, buffer);
+ case 8:
+ return TextToBinaryImpl<i64>(textValue, precision, scale, buffer);
+ case 16:
+ return TextToBinaryImpl<i128>(textValue, precision, scale, buffer);
+ default:
+ static_assert(GetDecimalBinaryValueSize(TDecimal::MaxPrecision) == 16);
+ YT_ABORT();
+ }
+}
+
+TString TDecimal::TextToBinary(TStringBuf textValue, int precision, int scale)
+{
+ TString result;
+ result.ReserveAndResize(MaxBinarySize);
+ auto resultSize = TextToBinary(textValue, precision, scale, result.Detach(), result.size()).size();
+ result.resize(resultSize);
+ return result;
+}
+
+void TDecimal::ValidatePrecisionAndScale(int precision, int scale)
+{
+ if (precision <= 0 || precision > MaxPrecision) {
+ THROW_ERROR_EXCEPTION("Invalid decimal precision %Qlv, precision must be in range [1, %v]",
+ precision,
+ MaxPrecision);
+
+ } else if (scale < 0 || scale > precision) {
+ THROW_ERROR_EXCEPTION("Invalid decimal scale %v (precision: %v); decimal scale must be in range [0, PRECISION]",
+ scale,
+ precision);
+ }
+}
+
+template <typename T>
+static void ValidateDecimalBinaryValueImpl(TStringBuf binaryDecimal, int precision, int scale)
+{
+ T decoded = DecimalBinaryToIntegerUnchecked<T>(binaryDecimal);
+
+ const T maxValue = static_cast<T>(DecimalIntegerMaxValueTable[precision]);
+
+ if (-maxValue <= decoded && decoded <= maxValue) {
+ return;
+ }
+
+ if (decoded == TDecimalTraits<T>::MinusInf ||
+ decoded == TDecimalTraits<T>::PlusInf ||
+ decoded == TDecimalTraits<T>::Nan)
+ {
+ return;
+ }
+
+ char textBuffer[TDecimal::MaxTextSize];
+ auto textDecimal = WriteTextDecimalUnchecked<T>(decoded, scale, textBuffer);
+
+ THROW_ERROR_EXCEPTION(
+ "Decimal<%v,%v> does not have enough precision to represent %Qv",
+ precision,
+ scale,
+ textDecimal)
+ << TErrorAttribute("binary_value", HexEncode(binaryDecimal));
+}
+
+void TDecimal::ValidateBinaryValue(TStringBuf binaryDecimal, int precision, int scale)
+{
+ CheckDecimalValueSize(binaryDecimal, precision, scale);
+ switch (binaryDecimal.Size()) {
+ case 4:
+ return ValidateDecimalBinaryValueImpl<i32>(binaryDecimal, precision, scale);
+ case 8:
+ return ValidateDecimalBinaryValueImpl<i64>(binaryDecimal, precision, scale);
+ case 16:
+ return ValidateDecimalBinaryValueImpl<i128>(binaryDecimal, precision, scale);
+ default:
+ static_assert(GetDecimalBinaryValueSize(TDecimal::MaxPrecision) == 16);
+ YT_ABORT();
+ }
+}
+template <typename T>
+Y_FORCE_INLINE void CheckDecimalIntBits(int precision)
+{
+ const auto expectedSize = TDecimal::GetValueBinarySize(precision);
+ if (expectedSize != sizeof(T)) {
+ const int bitCount = sizeof(T) * 8;
+ THROW_ERROR_EXCEPTION("Decimal<%v, ?> cannot be represented as int%v",
+ precision,
+ bitCount);
+ }
+}
+
+int TDecimal::GetValueBinarySize(int precision)
+{
+ const auto result = GetDecimalBinaryValueSize(precision);
+ if (result <= 0) {
+ ValidatePrecisionAndScale(precision, 0);
+ YT_ABORT();
+ }
+ return result;
+}
+
+TStringBuf TDecimal::WriteBinary32(int precision, i32 value, char* buffer, size_t bufferLength)
+{
+ const size_t resultLength = GetValueBinarySize(precision);
+ CheckDecimalIntBits<i32>(precision);
+ YT_VERIFY(bufferLength >= resultLength);
+
+ DecimalIntegerToBinaryUnchecked(value, buffer);
+ return TStringBuf{buffer, sizeof(value)};
+}
+
+TStringBuf TDecimal::WriteBinary64(int precision, i64 value, char* buffer, size_t bufferLength)
+{
+ const size_t resultLength = GetValueBinarySize(precision);
+ CheckDecimalIntBits<i64>(precision);
+ YT_VERIFY(bufferLength >= resultLength);
+
+ DecimalIntegerToBinaryUnchecked(value, buffer);
+ return TStringBuf{buffer, sizeof(value)};
+}
+
+TStringBuf TDecimal::WriteBinary128(int precision, TValue128 value, char* buffer, size_t bufferLength)
+{
+ const size_t resultLength = GetValueBinarySize(precision);
+ CheckDecimalIntBits<TValue128>(precision);
+ YT_VERIFY(bufferLength >= resultLength);
+
+ DecimalIntegerToBinaryUnchecked(i128(value.High, value.Low), buffer);
+ return TStringBuf{buffer, sizeof(TValue128)};
+}
+
+template <typename T>
+Y_FORCE_INLINE void CheckBufferLength(int precision, size_t bufferLength)
+{
+ CheckDecimalIntBits<T>(precision);
+ if (sizeof(T) != bufferLength) {
+ THROW_ERROR_EXCEPTION("Decimal<%v, ?> has unexpected length: expected %v, actual %v",
+ precision,
+ sizeof(T),
+ bufferLength);
+ }
+}
+
+i32 TDecimal::ParseBinary32(int precision, TStringBuf buffer)
+{
+ CheckBufferLength<i32>(precision, buffer.Size());
+ return DecimalBinaryToIntegerUnchecked<i32>(buffer);
+}
+
+i64 TDecimal::ParseBinary64(int precision, TStringBuf buffer)
+{
+ CheckBufferLength<i64>(precision, buffer.Size());
+ return DecimalBinaryToIntegerUnchecked<i64>(buffer);
+}
+
+TDecimal::TValue128 TDecimal::ParseBinary128(int precision, TStringBuf buffer)
+{
+ CheckBufferLength<i128>(precision, buffer.Size());
+ auto result = DecimalBinaryToIntegerUnchecked<i128>(buffer);
+ return {GetLow(result), static_cast<i64>(GetHigh(result))};
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDecimal
diff --git a/yt/yt/library/decimal/decimal.h b/yt/yt/library/decimal/decimal.h
new file mode 100644
index 0000000000..7cb9bdd7c0
--- /dev/null
+++ b/yt/yt/library/decimal/decimal.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <library/cpp/int128/int128.h>
+
+#include <util/system/defaults.h>
+#include <util/generic/string.h>
+
+namespace NYT::NDecimal {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDecimal
+{
+public:
+ struct TValue128
+ {
+ ui64 Low;
+ i64 High;
+ };
+ static_assert(sizeof(TValue128) == 2 * sizeof(ui64));
+
+public:
+ // Maximum precision supported by YT
+ static constexpr int MaxPrecision = 35;
+ static constexpr int MaxBinarySize = 16;
+
+ // NB. Sometimes we print values that exceed MaxPrecision (e.g. in error messages)
+ // MaxTextSize is chosen so we can print ANY i128 number as decimal.
+ static constexpr int MaxTextSize =
+ std::numeric_limits<ui128>::digits + 1 // max number of digits in ui128 number
+ + 1 // possible decimal point
+ + 1; // possible minus sign
+
+ static void ValidatePrecisionAndScale(int precision, int scale);
+
+ static void ValidateBinaryValue(TStringBuf binaryValue, int precision, int scale);
+
+ static TString BinaryToText(TStringBuf binaryDecimal, int precision, int scale);
+ static TString TextToBinary(TStringBuf textDecimal, int precision, int scale);
+
+ // More efficient versions of conversion functions without allocations.
+ // `buffer` must be at least of size MaxTextSize / MaxBinarySize.
+ // Returned value is substring of buffer.
+ static TStringBuf BinaryToText(TStringBuf binaryDecimal, int precision, int scale, char* buffer, size_t bufferLength);
+ static TStringBuf TextToBinary(TStringBuf textDecimal, int precision, int scale, char* buffer, size_t bufferLength);
+
+ static int GetValueBinarySize(int precision);
+
+ static TStringBuf WriteBinary32(int precision, i32 value, char* buffer, size_t bufferLength);
+ static TStringBuf WriteBinary64(int precision, i64 value, char* buffer, size_t bufferLength);
+ static TStringBuf WriteBinary128(int precision, TValue128 value, char* buffer, size_t bufferLength);
+
+ static i32 ParseBinary32(int precision, TStringBuf buffer);
+ static i64 ParseBinary64(int precision, TStringBuf buffer);
+ static TValue128 ParseBinary128(int precision, TStringBuf buffer);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDecimal
diff --git a/yt/yt/library/decimal/unittests/decimal_ut.cpp b/yt/yt/library/decimal/unittests/decimal_ut.cpp
new file mode 100644
index 0000000000..f8827a3dbb
--- /dev/null
+++ b/yt/yt/library/decimal/unittests/decimal_ut.cpp
@@ -0,0 +1,299 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/library/decimal/decimal.h>
+
+#include <util/string/hex.h>
+
+using namespace NYT;
+using namespace NYT::NDecimal;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static TString RoundConvertText(TStringBuf textValue, int precision, int scale)
+{
+ auto binary = TDecimal::TextToBinary(textValue, precision, scale);
+ return TDecimal::BinaryToText(binary, precision, scale);
+}
+
+#define CHECK_ROUND_CONVERSION(text, precision, scale) \
+ do { \
+ EXPECT_EQ(text, RoundConvertText(text, precision, scale)); \
+ } while (0)
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TDecimal, TestTextBinaryConversion)
+{
+#define TEST_TEXT_BINARY_CONVERSION(precision, scale, text, binary) \
+ do { \
+ EXPECT_EQ(binary, HexEncode(TDecimal::TextToBinary(text, precision, scale))); \
+ EXPECT_EQ(text, TDecimal::BinaryToText(HexDecode(binary), precision, scale)); \
+ } while (0)
+
+ TEST_TEXT_BINARY_CONVERSION(3, 2, "3.14", "8000013A");
+ TEST_TEXT_BINARY_CONVERSION(10, 2, "3.14", "80000000" "0000013A");
+ TEST_TEXT_BINARY_CONVERSION(35, 2, "3.14", "80000000" "00000000" "00000000" "0000013A");
+
+ TEST_TEXT_BINARY_CONVERSION(10, 9, "-2.718281828", "7FFFFFFF" "5DFA4F9C");
+ TEST_TEXT_BINARY_CONVERSION(35, 9, "-2.718281828", "7FFFFFFF" "FFFFFFFF" "FFFFFFFF" "5DFA4F9C");
+
+ TEST_TEXT_BINARY_CONVERSION(3, 2, "nan", "FFFFFFFF");
+ TEST_TEXT_BINARY_CONVERSION(3, 2, "inf", "FFFFFFFE");
+ TEST_TEXT_BINARY_CONVERSION(3, 2, "-inf", "00000002");
+ EXPECT_EQ("FFFFFFFE", HexEncode(TDecimal::TextToBinary("+inf", 3, 2)));
+
+ TEST_TEXT_BINARY_CONVERSION(10, 2,"nan", "FFFFFFFF" "FFFFFFFF");
+ TEST_TEXT_BINARY_CONVERSION(10, 2,"inf", "FFFFFFFF" "FFFFFFFE");
+ TEST_TEXT_BINARY_CONVERSION(10, 2,"-inf", "00000000" "00000002");
+ EXPECT_EQ("FFFFFFFF" "FFFFFFFE", HexEncode(TDecimal::TextToBinary("+inf", 10, 2)));
+
+ TEST_TEXT_BINARY_CONVERSION(35, 2,"nan", "FFFFFFFF" "FFFFFFFF" "FFFFFFFF" "FFFFFFFF");
+ TEST_TEXT_BINARY_CONVERSION(35, 2,"inf", "FFFFFFFF" "FFFFFFFF" "FFFFFFFF" "FFFFFFFE");
+ TEST_TEXT_BINARY_CONVERSION(35, 2,"-inf", "00000000" "00000000" "00000000" "00000002");
+ EXPECT_EQ("FFFFFFFF" "FFFFFFFF" "FFFFFFFF" "FFFFFFFE", HexEncode(TDecimal::TextToBinary("+inf", 35, 2)));
+
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("-nan", 3, 2), "is not valid Decimal");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("infinity", 3, 2), "is not valid Decimal");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("-infinity", 3, 2), "is not valid Decimal");
+
+ // Few test cases with big numbers generated by python snippet:
+ // import random
+ // def print_test_case(plus):
+ // textval = "".join(random.choice("0123456789") for _ in range(35))
+ // if not plus:
+ // textval = "-" + textval
+ // binval = hex(2 ** 127 + int(textval))
+ // binval = binval[2:].strip('L') # strip 0x and final 'L'
+ // binval = binval.upper()
+ // print(
+ // "TEST_TEXT_BINARY_CONVERSION(\n"
+ // " 35, 0,\n"
+ // " \"{text}\",\n"
+ // " \"{binary}\");\n"
+ // .format(text=textval, binary=binval)
+ // )
+ // random.seed(42)
+ // print_test_case(False)
+ // print_test_case(False)
+ // print_test_case(True)
+ // print_test_case(True)
+
+ TEST_TEXT_BINARY_CONVERSION(
+ 35, 0,
+ "-60227680402501652580863193008687593",
+ "7FF4668BCCE002BD685B3A3811CE3617");
+
+ TEST_TEXT_BINARY_CONVERSION(
+ 35, 0,
+ "-58685702202126332296617139656872032",
+ "7FF4B2924D28572FF19525515205AFA0");
+
+ TEST_TEXT_BINARY_CONVERSION(
+ 35, 0,
+ "29836394225258329500167403959807652",
+ "8005BF0C3D439F6FFD649D99A704EEA4");
+
+ TEST_TEXT_BINARY_CONVERSION(
+ 35, 0,
+ "61449825198266175750309883089040771",
+ "800BD5B5D5F0C73E0C9CD4943298B583");
+
+#undef TEST_TEXT_BINARY_CONVERSION
+
+}
+
+TEST(TDecimal, TestPrecisionScaleLimits)
+{
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("0", -1, 0), "Invalid decimal precision");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("0", 0, 0), "Invalid decimal precision");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("0", TDecimal::MaxPrecision + 1, 0), "Invalid decimal precision");
+
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::BinaryToText("0", -1, 0), "Invalid decimal precision");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::BinaryToText("0", 0, 0), "Invalid decimal precision");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::BinaryToText("0", TDecimal::MaxPrecision + 1, 0), "Invalid decimal precision");
+
+ CHECK_ROUND_CONVERSION("0", 1, 0);
+ CHECK_ROUND_CONVERSION("0", TDecimal::MaxPrecision, 0);
+
+
+ CHECK_ROUND_CONVERSION("-3.14", 3, 2);
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::BinaryToText("0000", 3, 4), "Invalid decimal scale");
+
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("3.1415", 10, 3), "too many digits after decimal point");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("-3.1415", 10, 3), "too many digits after decimal point");
+ EXPECT_EQ("3.140", RoundConvertText("3.14", 10, 3));
+ EXPECT_EQ("-3.140", RoundConvertText("-3.14", 10, 3));
+
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("314.15", 5, 3), "too many digits before decimal point");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary("-314.15", 5, 3), "too many digits before decimal point");
+
+ // Sometimes we want to print values that are not representable with given precision
+ // (e.g. in error messages we sometimes want to print text value of invalid decimal to explain that it has
+ // more digits than allowed by precision).
+ //
+ // Here we test that extreme values are printed ok.
+ auto maxBinaryDecimal = HexDecode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD");
+ auto minBinaryDecimal1 = HexDecode("00000000000000000000000000000000");
+ auto minBinaryDecimal2 = HexDecode("00000000000000000000000000000003");
+ EXPECT_EQ(TDecimal::MaxBinarySize, std::ssize(maxBinaryDecimal)); // If max TDecimal::MaxBinarySize ever increases
+ EXPECT_EQ(TDecimal::MaxBinarySize, std::ssize(minBinaryDecimal1)); // please update this test
+ EXPECT_EQ(TDecimal::MaxBinarySize, std::ssize(minBinaryDecimal2)); // with better values.
+ EXPECT_EQ("1701411834604692317316873037158841057.25", TDecimal::BinaryToText(maxBinaryDecimal, TDecimal::MaxPrecision, 2));
+ EXPECT_EQ("-1701411834604692317316873037158841057.28", TDecimal::BinaryToText(minBinaryDecimal1, TDecimal::MaxPrecision, 2));
+ EXPECT_EQ("-1701411834604692317316873037158841057.25", TDecimal::BinaryToText(minBinaryDecimal2, TDecimal::MaxPrecision, 2));
+}
+
+TEST(TDecimal, TestValidation)
+{
+ EXPECT_NO_THROW(TDecimal::ValidateBinaryValue(HexDecode("8000013A"), 3, 2));
+ EXPECT_NO_THROW(TDecimal::ValidateBinaryValue(HexDecode("80000000" "0000013A"), 10, 2));
+ EXPECT_NO_THROW(TDecimal::ValidateBinaryValue(HexDecode("80000000" "00000000" "00000000" "0000013A"), 35, 2));
+}
+
+class TDecimalWithPrecisionTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<int>
+{
+public:
+ static std::vector<int> GetAllPossiblePrecisions() {
+ std::vector<int> result;
+ for (int i = 1; i <= TDecimal::MaxPrecision; ++i) {
+ result.push_back(i);
+ }
+
+ YT_VERIFY(result.back() == TDecimal::MaxPrecision);
+ return result;
+ }
+
+ static TString GetTextNines(int precision)
+ {
+ return TString(precision, '9');
+ }
+
+ static TString GetTextMinusNines(int precision)
+ {
+ return "-" + GetTextNines(precision);
+ }
+
+ static TString GetTextZillion(int precision)
+ {
+ return "1" + TString(precision, '0');
+ }
+
+ static TString GetTextMinusZillion(int precision)
+ {
+ return "-" + GetTextZillion(precision);
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ Precisions,
+ TDecimalWithPrecisionTest,
+ ::testing::ValuesIn(TDecimalWithPrecisionTest::GetAllPossiblePrecisions())
+);
+
+TEST_P(TDecimalWithPrecisionTest, TestTextLimits)
+{
+ auto precision = GetParam();
+
+ CHECK_ROUND_CONVERSION("0", precision, 0);
+ CHECK_ROUND_CONVERSION("1", precision, 0);
+ CHECK_ROUND_CONVERSION("-1", precision, 0);
+ CHECK_ROUND_CONVERSION("2", precision, 0);
+ CHECK_ROUND_CONVERSION("-2", precision, 0);
+ CHECK_ROUND_CONVERSION("3", precision, 0);
+ CHECK_ROUND_CONVERSION("-3", precision, 0);
+ CHECK_ROUND_CONVERSION("4", precision, 0);
+ CHECK_ROUND_CONVERSION("-4", precision, 0);
+ CHECK_ROUND_CONVERSION("5", precision, 0);
+ CHECK_ROUND_CONVERSION("-5", precision, 0);
+ CHECK_ROUND_CONVERSION("6", precision, 0);
+ CHECK_ROUND_CONVERSION("-6", precision, 0);
+ CHECK_ROUND_CONVERSION("7", precision, 0);
+ CHECK_ROUND_CONVERSION("-7", precision, 0);
+ CHECK_ROUND_CONVERSION("8", precision, 0);
+ CHECK_ROUND_CONVERSION("-8", precision, 0);
+ CHECK_ROUND_CONVERSION("9", precision, 0);
+ CHECK_ROUND_CONVERSION("-9", precision, 0);
+ CHECK_ROUND_CONVERSION("inf", precision, 0);
+ CHECK_ROUND_CONVERSION("-inf", precision, 0);
+ CHECK_ROUND_CONVERSION("nan", precision, 0);
+
+ CHECK_ROUND_CONVERSION(GetTextNines(precision), precision, 0);
+ CHECK_ROUND_CONVERSION(GetTextMinusNines(precision), precision, 0);
+
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary(GetTextZillion(precision), precision, 0),
+ "too many digits before decimal point");
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::TextToBinary(GetTextMinusZillion(precision), precision, 0),
+ "too many digits before decimal point");
+}
+
+TEST_P(TDecimalWithPrecisionTest, TestBinaryValidation)
+{
+ auto precision = GetParam();
+
+#define TO_BINARY_THEN_VALIDATE(text, precision, scale) \
+ do { \
+ auto binaryValue = TDecimal::TextToBinary(text, precision, scale); \
+ EXPECT_NO_THROW(TDecimal::ValidateBinaryValue(binaryValue, precision, scale)); \
+ } while (0)
+
+ TO_BINARY_THEN_VALIDATE("0", precision, 0);
+ TO_BINARY_THEN_VALIDATE("1", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-1", precision, 0);
+ TO_BINARY_THEN_VALIDATE("2", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-2", precision, 0);
+ TO_BINARY_THEN_VALIDATE("3", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-3", precision, 0);
+ TO_BINARY_THEN_VALIDATE("4", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-4", precision, 0);
+ TO_BINARY_THEN_VALIDATE("5", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-5", precision, 0);
+ TO_BINARY_THEN_VALIDATE("6", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-6", precision, 0);
+ TO_BINARY_THEN_VALIDATE("7", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-7", precision, 0);
+ TO_BINARY_THEN_VALIDATE("8", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-8", precision, 0);
+ TO_BINARY_THEN_VALIDATE("9", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-9", precision, 0);
+ TO_BINARY_THEN_VALIDATE("inf", precision, 0);
+ TO_BINARY_THEN_VALIDATE("-inf", precision, 0);
+ TO_BINARY_THEN_VALIDATE("nan", precision, 0);
+
+#undef TO_BINARY_THEN_VALIDATE
+
+ auto binaryValueIncrement = [] (void* buffer, int size) {
+ auto cur = static_cast<ui8*>(buffer) + size -1;
+ for (; cur >= buffer; --cur) {
+ if (++(*cur) != 0) {
+ break;
+ }
+ }
+ };
+
+ auto binaryValueDecrement = [] (void* buffer, int size) {
+ auto cur = static_cast<ui8*>(buffer) + size -1;
+ for (; cur >= buffer; --cur) {
+ if ((*cur)-- != 0) {
+ break;
+ }
+ }
+ };
+
+ TStringBuf binValue;
+ char binBuffer[TDecimal::MaxBinarySize];
+ // Check 99..9
+ binValue = TDecimal::TextToBinary(GetTextNines(precision), precision, 0, binBuffer, sizeof(binBuffer));
+ EXPECT_NO_THROW(TDecimal::ValidateBinaryValue(binValue, precision, 0));
+ // Check 100..0
+ binaryValueIncrement(binBuffer, binValue.size());
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::ValidateBinaryValue(binValue, precision, 0), "does not have enough precision to represent");
+
+ // Check -99..9
+ binValue = TDecimal::TextToBinary(GetTextMinusNines(precision), precision, 0, binBuffer, sizeof(binBuffer));
+ EXPECT_NO_THROW(TDecimal::ValidateBinaryValue(binValue, precision, 0));
+ // Check -100..0
+ binaryValueDecrement(binBuffer, binValue.size());
+ EXPECT_THROW_WITH_SUBSTRING(TDecimal::ValidateBinaryValue(binValue, precision, 0), "does not have enough precision to represent");
+} \ No newline at end of file
diff --git a/yt/yt/library/decimal/unittests/ya.make b/yt/yt/library/decimal/unittests/ya.make
new file mode 100644
index 0000000000..0686252855
--- /dev/null
+++ b/yt/yt/library/decimal/unittests/ya.make
@@ -0,0 +1,18 @@
+GTEST(unittester-library-decimal)
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+ALLOCATOR(YT)
+
+SRCS(
+ decimal_ut.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ yt/yt/core/test_framework
+ yt/yt/library/decimal
+)
+
+END()
diff --git a/yt/yt/library/decimal/ya.make b/yt/yt/library/decimal/ya.make
new file mode 100644
index 0000000000..262d7ae86c
--- /dev/null
+++ b/yt/yt/library/decimal/ya.make
@@ -0,0 +1,18 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ decimal.cpp
+)
+
+PEERDIR(
+ yt/yt/core
+ library/cpp/int128
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ unittests
+)
diff --git a/yt/yt/library/erasure/CMakeLists.linux-aarch64.txt b/yt/yt/library/erasure/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..46876dae39
--- /dev/null
+++ b/yt/yt/library/erasure/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-erasure)
+target_compile_options(yt-library-erasure PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-erasure PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+)
+target_sources(yt-library-erasure PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/erasure/public.cpp
+)
diff --git a/yt/yt/library/erasure/CMakeLists.linux-x86_64.txt b/yt/yt/library/erasure/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..46876dae39
--- /dev/null
+++ b/yt/yt/library/erasure/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-erasure)
+target_compile_options(yt-library-erasure PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-erasure PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+)
+target_sources(yt-library-erasure PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/erasure/public.cpp
+)
diff --git a/yt/yt/library/erasure/CMakeLists.txt b/yt/yt/library/erasure/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/library/erasure/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/library/erasure/impl/codec.cpp b/yt/yt/library/erasure/impl/codec.cpp
new file mode 100644
index 0000000000..ee35da1d44
--- /dev/null
+++ b/yt/yt/library/erasure/impl/codec.cpp
@@ -0,0 +1,81 @@
+#include "codec.h"
+
+#include "codec_detail.h"
+
+#include <yt/yt/core/misc/error.h>
+
+namespace NYT::NErasure {
+
+////////////////////////////////////////////////////////////////////////////////
+
+int ICodec::GetTotalPartCount() const
+{
+ return GetDataPartCount() + GetParityPartCount();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ICodec* GetCodec(ECodec id)
+{
+ if (auto* codec = FindCodec(id)) {
+ return codec;
+ }
+
+ THROW_ERROR_EXCEPTION("Unsupported erasure codec %Qlv", id);
+}
+
+ECodec GetEffectiveCodecId(ECodec id)
+{
+ ECodec effectiveCodecId;
+ switch (id) {
+ case ECodec::JerasureReedSolomon_6_3:
+ case ECodec::IsaReedSolomon_6_3:
+ effectiveCodecId = ECodec::IsaReedSolomon_6_3;
+ break;
+
+ case ECodec::IsaReedSolomon_3_3:
+ effectiveCodecId = ECodec::IsaReedSolomon_3_3;
+ break;
+
+ case ECodec::JerasureLrc_12_2_2:
+ case ECodec::IsaLrc_12_2_2:
+ effectiveCodecId = ECodec::IsaLrc_12_2_2;
+ break;
+
+ case ECodec::None:
+ effectiveCodecId = ECodec::None;
+ break;
+
+ default:
+ YT_ABORT();
+ }
+
+ if (FindCodec(effectiveCodecId)) {
+ return effectiveCodecId;
+ }
+
+ return id;
+}
+
+const std::vector<ECodec>& GetSupportedCodecIds()
+{
+ static const std::vector<ECodec> supportedCodecIds = [] {
+ std::vector<ECodec> codecIds;
+ for (auto codecId : TEnumTraits<ECodec>::GetDomainValues()) {
+ if (FindCodec(codecId)) {
+ codecIds.push_back(codecId);
+ }
+ }
+ codecIds.push_back(ECodec::None);
+
+ SortUnique(codecIds);
+
+ return codecIds;
+ }();
+ return supportedCodecIds;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
+
diff --git a/yt/yt/library/erasure/impl/codec.h b/yt/yt/library/erasure/impl/codec.h
new file mode 100644
index 0000000000..489e55548f
--- /dev/null
+++ b/yt/yt/library/erasure/impl/codec.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/blob.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+#include <library/cpp/erasure/codec.h>
+
+#include <bitset>
+
+namespace NYT::NErasure {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ICodec
+{
+ virtual ~ICodec() = default;
+
+ //! Returns the codec id.
+ virtual ECodec GetId() const = 0;
+
+ //! Encodes data blocks, returns parity blocks.
+ virtual std::vector<TSharedRef> Encode(const std::vector<TSharedRef>& blocks) const = 0;
+
+ //! Decodes (repairs) missing blocks.
+ /*!
+ * #erasedIndices must contain the set of erased blocks indices.
+ * #blocks must contain known blocks (in the order specified by #GetRepairIndices).
+ * \returns The repaired blocks.
+ */
+ virtual std::vector<TSharedRef> Decode(
+ const std::vector<TSharedRef>& blocks,
+ const TPartIndexList& erasedIndices) const = 0;
+
+ //! Given a set of missing block indices, returns |true| if missing blocks can be repaired.
+ //! Due to performance reasons the elements of #erasedIndices must unique and sorted.
+ virtual bool CanRepair(const TPartIndexList& erasedIndices) const = 0;
+
+ //! Rapid version that works with set instead of list.
+ virtual bool CanRepair(const TPartIndexSet& erasedIndices) const = 0;
+
+ //! Given a set of missing block indices, checks if missing blocks can be repaired.
+ /*!
+ * \returns
+ * If repair is not possible, returns |std::nullopt|.
+ * Otherwise returns the indices of blocks (both data and parity) to be passed to #Decode
+ * (in this very order). Not all known blocks may be needed for repair.
+ */
+ virtual std::optional<TPartIndexList> GetRepairIndices(const TPartIndexList& erasedIndices) const = 0;
+
+ //! Returns the number of data blocks this codec can handle.
+ virtual int GetDataPartCount() const = 0;
+
+ //! Returns the number of parity blocks this codec can handle.
+ virtual int GetParityPartCount() const = 0;
+
+ //! Returns the maximum number of blocks that can always be repaired when missing.
+ virtual int GetGuaranteedRepairablePartCount() const = 0;
+
+ //! Every block passed to this codec must have size divisible by the result of #GetWordSize.
+ virtual int GetWordSize() const = 0;
+
+ //! Returns |true| if the codec is "bytewise", i.e. the i-th byte of any parity part depends only on
+ //! the i-th bytes of data parts.
+ virtual bool IsBytewise() const = 0;
+
+ // Extension methods.
+
+ //! Returns the sum of #GetDataPartCount and #GetParityPartCount.
+ int GetTotalPartCount() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Finds an erasure codec by id. Returns |nullptr| if codec is not supported.
+ICodec* FindCodec(ECodec id);
+
+//! Finds an erasure codec by id. Throws an error if codec is not supported.
+ICodec* GetCodec(ECodec id);
+
+//! For a given codec id returns the id of most optimal erasure codec
+//! that has the same number of data and parity parts.
+ECodec GetEffectiveCodecId(ECodec id);
+
+//! Returns the list of supported erasure codecs.
+const std::vector<ECodec>& GetSupportedCodecIds();
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NErasure
diff --git a/yt/yt/library/erasure/impl/codec_detail.cpp b/yt/yt/library/erasure/impl/codec_detail.cpp
new file mode 100644
index 0000000000..47f30cf4e9
--- /dev/null
+++ b/yt/yt/library/erasure/impl/codec_detail.cpp
@@ -0,0 +1,33 @@
+#include "codec_detail.h"
+
+namespace NYT::NErasure::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TJerasureBlobTag
+{ };
+
+struct TLrcBufferTag
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+TCodecTraits::TMutableBlobType TCodecTraits::AllocateBlob(size_t size)
+{
+ return TMutableBlobType::Allocate<TJerasureBlobTag>(size, {.InitializeStorage = false});
+}
+
+TCodecTraits::TBufferType TCodecTraits::AllocateBuffer(size_t size)
+{
+ // Only LRC now uses buffer allocation.
+ return TBufferType(GetRefCountedTypeCookie<TLrcBufferTag>(), size);
+}
+
+TCodecTraits::TBlobType TCodecTraits::FromBufferToBlob(TBufferType&& blob)
+{
+ return TBlobType::FromBlob(std::move(blob));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NErasure::NDetail
diff --git a/yt/yt/library/erasure/impl/codec_detail.h b/yt/yt/library/erasure/impl/codec_detail.h
new file mode 100644
index 0000000000..e43963fd71
--- /dev/null
+++ b/yt/yt/library/erasure/impl/codec_detail.h
@@ -0,0 +1,100 @@
+#pragma once
+
+#include "public.h"
+
+#include "codec.h"
+
+namespace NYT::NErasure::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TCodecTraits
+{
+ using TBlobType = TSharedRef;
+ using TMutableBlobType = TSharedMutableRef;
+ using TBufferType = TBlob;
+
+ static TMutableBlobType AllocateBlob(size_t size);
+ static TBufferType AllocateBuffer(size_t size);
+ static TBlobType FromBufferToBlob(TBufferType&& blob);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TUnderlying>
+class TCodec
+ : public ICodec
+{
+public:
+ TCodec(ECodec id, bool bytewise)
+ : Id_(id)
+ , Bytewise_(bytewise)
+ { }
+
+ ECodec GetId() const override
+ {
+ return Id_;
+ }
+
+ std::vector<TSharedRef> Encode(const std::vector<TSharedRef>& blocks) const override
+ {
+ return Underlying_.Encode(blocks);
+ }
+
+ std::vector<TSharedRef> Decode(
+ const std::vector<TSharedRef>& blocks,
+ const TPartIndexList& erasedIndices) const override
+ {
+ return Underlying_.Decode(blocks, erasedIndices);
+ }
+
+ bool CanRepair(const TPartIndexList& erasedIndices) const override
+ {
+ return Underlying_.CanRepair(erasedIndices);
+ }
+
+ bool CanRepair(const TPartIndexSet& erasedIndices) const override
+ {
+ return Underlying_.CanRepair(erasedIndices);
+ }
+
+ std::optional<TPartIndexList> GetRepairIndices(const TPartIndexList& erasedIndices) const override
+ {
+ return Underlying_.GetRepairIndices(erasedIndices);
+ }
+
+ int GetDataPartCount() const override
+ {
+ return Underlying_.GetDataPartCount();
+ }
+
+ int GetParityPartCount() const override
+ {
+ return Underlying_.GetParityPartCount();
+ }
+
+ int GetGuaranteedRepairablePartCount() const override
+ {
+ return Underlying_.GetGuaranteedRepairablePartCount();
+ }
+
+ int GetWordSize() const override
+ {
+ return Underlying_.GetWordSize();
+ }
+
+ bool IsBytewise() const override
+ {
+ return Bytewise_;
+ }
+
+private:
+ const ECodec Id_;
+ const bool Bytewise_;
+
+ TUnderlying Underlying_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NErasure::NDetail
diff --git a/yt/yt/library/erasure/impl/codec_opensource.cpp b/yt/yt/library/erasure/impl/codec_opensource.cpp
new file mode 100644
index 0000000000..8c0f2cf66e
--- /dev/null
+++ b/yt/yt/library/erasure/impl/codec_opensource.cpp
@@ -0,0 +1,39 @@
+#include "codec.h"
+
+#include "codec_detail.h"
+
+#include <library/cpp/erasure/lrc_isa.h>
+#include <library/cpp/erasure/reed_solomon_isa.h>
+
+namespace NYT::NErasure {
+
+using namespace ::NErasure;
+
+////////////////////////////////////////////////////////////////////////////////
+
+ICodec* FindCodec(ECodec codecId)
+{
+ // NB: Changing the set of supported codecs or their properties requires master reign promotion.
+ switch (codecId) {
+ // These codecs use ISA-l as a backend.
+ case ECodec::ReedSolomon_3_3: {
+ static NDetail::TCodec<TReedSolomonIsa<3, 3, 8, NDetail::TCodecTraits>> result(ECodec::ReedSolomon_3_3, /*bytewise*/ true);
+ return &result;
+ }
+ case ECodec::IsaReedSolomon_6_3: {
+ static NDetail::TCodec<TReedSolomonIsa<6, 3, 8, NDetail::TCodecTraits>> result(ECodec::IsaReedSolomon_6_3, /*bytewise*/ true);
+ return &result;
+ }
+ case ECodec::IsaLrc_12_2_2: {
+ static NDetail::TCodec<TLrcIsa<12, 4, 8, NDetail::TCodecTraits>> result(ECodec::IsaLrc_12_2_2, /*bytewise*/ true);
+ return &result;
+ }
+
+ default:
+ return nullptr;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NErasure
diff --git a/yt/yt/library/erasure/impl/public.h b/yt/yt/library/erasure/impl/public.h
new file mode 100644
index 0000000000..8aa8070222
--- /dev/null
+++ b/yt/yt/library/erasure/impl/public.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <yt/yt/library/erasure/public.h>
+
+#include <library/cpp/erasure/codec.h>
+
+namespace NYT::NErasure {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ICodec;
+
+using ::NErasure::TPartIndexList;
+using ::NErasure::TPartIndexSet;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NErasure
diff --git a/yt/yt/library/erasure/impl/unittests/erasure_stability_ut.cpp b/yt/yt/library/erasure/impl/unittests/erasure_stability_ut.cpp
new file mode 100644
index 0000000000..f32029a413
--- /dev/null
+++ b/yt/yt/library/erasure/impl/unittests/erasure_stability_ut.cpp
@@ -0,0 +1,77 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/library/erasure/impl/codec.h>
+
+#include <util/random/random.h>
+
+namespace NYT::NErasure {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TErasureStabilityTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<
+ std::tuple<ECodec, std::vector<unsigned char>>>
+{
+public:
+ TBlob GenerateDataBuffer(int wordSize)
+ {
+ std::vector<unsigned char> data(wordSize);
+ for (int i = 0; i < wordSize; ++i) {
+ data[i] = RandomNumber<unsigned char>();
+ }
+
+ return TBlob(GetRefCountedTypeCookie<TDefaultBlobTag>(), TRef(data.data(), data.size()));
+ }
+};
+
+TEST_P(TErasureStabilityTest, TErasureStabilityTest)
+{
+ SetRandomSeed(42);
+ const auto& params = GetParam();
+
+ auto* codec = FindCodec(std::get<0>(params));
+ if (!codec) {
+ return;
+ }
+
+ std::vector<TSharedRef> dataParts;
+ for (int i = 0; i < codec->GetDataPartCount(); ++i) {
+ dataParts.push_back(TSharedRef::FromBlob(GenerateDataBuffer(codec->GetWordSize())));
+ }
+
+ auto parities = codec->Encode(dataParts);
+ auto expected = std::get<1>(params);
+
+ EXPECT_EQ(expected.size(), parities.size());
+ for (int i = 0; i < std::ssize(expected); ++i) {
+ // Check only the first element.
+ EXPECT_EQ(static_cast<char>(expected[i]), *parities[i].Begin());
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TErasureStabilityTest,
+ TErasureStabilityTest,
+ ::testing::Values(
+ std::make_tuple(
+ ECodec::IsaReedSolomon_3_3,
+ std::vector<unsigned char>{59, 252, 207}),
+ std::make_tuple(
+ ECodec::ReedSolomon_6_3,
+ std::vector<unsigned char>{194, 8, 51}),
+ std::make_tuple(
+ ECodec::JerasureLrc_12_2_2,
+ std::vector<unsigned char>{194, 201, 87, 67}),
+ std::make_tuple(
+ ECodec::IsaLrc_12_2_2,
+ std::vector<unsigned char>{194, 201, 104, 219}),
+ std::make_tuple(
+ ECodec::IsaReedSolomon_6_3,
+ std::vector<unsigned char>{194, 60, 234})));
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NErasure
diff --git a/yt/yt/library/erasure/impl/unittests/ya.make b/yt/yt/library/erasure/impl/unittests/ya.make
new file mode 100644
index 0000000000..2b2eaf6dd0
--- /dev/null
+++ b/yt/yt/library/erasure/impl/unittests/ya.make
@@ -0,0 +1,19 @@
+GTEST(unittester-library-erasure)
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+ALLOCATOR(YT)
+
+SRCS(
+ erasure_stability_ut.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ yt/yt/build
+ yt/yt/core/test_framework
+ yt/yt/library/erasure/impl
+)
+
+END()
diff --git a/yt/yt/library/erasure/impl/ya.make b/yt/yt/library/erasure/impl/ya.make
new file mode 100644
index 0000000000..cbf31129c9
--- /dev/null
+++ b/yt/yt/library/erasure/impl/ya.make
@@ -0,0 +1,26 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ yt/yt/library/erasure
+
+ library/cpp/erasure
+)
+
+SRCS(
+ codec.cpp
+ codec_detail.cpp
+)
+
+IF (OPENSOURCE)
+ SRCS(codec_opensource.cpp)
+ELSE()
+ SRCS(codec_yandex.cpp)
+ENDIF()
+
+END()
+
+RECURSE_FOR_TESTS(
+ unittests
+)
diff --git a/yt/yt/library/erasure/public.cpp b/yt/yt/library/erasure/public.cpp
new file mode 100644
index 0000000000..e33eb82be1
--- /dev/null
+++ b/yt/yt/library/erasure/public.cpp
@@ -0,0 +1,10 @@
+#include "public.h"
+
+namespace NYT::NErasure {
+
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NChunkClient
+
diff --git a/yt/yt/library/erasure/public.h b/yt/yt/library/erasure/public.h
new file mode 100644
index 0000000000..820d311e29
--- /dev/null
+++ b/yt/yt/library/erasure/public.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <library/cpp/yt/misc/enum.h>
+
+namespace NYT::NErasure {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_AMBIGUOUS_ENUM_WITH_UNDERLYING_TYPE(ECodec, i8,
+ ((None) (0))
+
+ ((ReedSolomon_6_3) (1))
+ ((JerasureReedSolomon_6_3) (1))
+ ((IsaReedSolomon_6_3) (5))
+
+ ((ReedSolomon_3_3) (4))
+ ((IsaReedSolomon_3_3) (4))
+
+ ((Lrc_12_2_2) (2))
+ ((JerasureLrc_12_2_2) (2))
+ ((IsaLrc_12_2_2) (3))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NErasure
diff --git a/yt/yt/library/erasure/ya.make b/yt/yt/library/erasure/ya.make
new file mode 100644
index 0000000000..b6485d90fd
--- /dev/null
+++ b/yt/yt/library/erasure/ya.make
@@ -0,0 +1,17 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ yt/yt/core
+)
+
+SRCS(
+ public.cpp
+)
+
+END()
+
+RECURSE(
+ impl
+)
diff --git a/yt/yt/library/named_value/README.md b/yt/yt/library/named_value/README.md
new file mode 100644
index 0000000000..ed56c42823
--- /dev/null
+++ b/yt/yt/library/named_value/README.md
@@ -0,0 +1,3 @@
+# named\_value
+
+Библиотека предоставляет набор функций для удобной (но не самой эффективной) работы с `NTableClient::TUnversionedValue`.
diff --git a/yt/yt/library/named_value/named_value.cpp b/yt/yt/library/named_value/named_value.cpp
new file mode 100644
index 0000000000..912d9069e0
--- /dev/null
+++ b/yt/yt/library/named_value/named_value.cpp
@@ -0,0 +1,126 @@
+#include "named_value.h"
+
+namespace NYT::NNamedValue {
+
+using namespace NTableClient;
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTableClient::TUnversionedOwningRow MakeRow(
+ const TNameTablePtr& nameTable,
+ const std::initializer_list<TNamedValue>& values)
+{
+ TUnversionedOwningRowBuilder builder;
+ for (const auto& v : values) {
+ builder.AddValue(v.ToUnversionedValue(nameTable));
+ }
+ return builder.FinishRow();
+}
+
+NTableClient::TUnversionedOwningRow MakeRow(
+ const TNameTablePtr& nameTable,
+ const std::vector<TNamedValue>& values)
+{
+ TUnversionedOwningRowBuilder builder;
+ for (const auto& v : values) {
+ builder.AddValue(v.ToUnversionedValue(nameTable));
+ }
+ return builder.FinishRow();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NTableClient::TUnversionedValue TNamedValue::ToUnversionedValue(const NTableClient::TNameTablePtr& nameTable) const
+{
+ const int valueId = nameTable->GetIdOrRegisterName(Name_);
+ return std::visit([valueId] (const auto& value) -> TUnversionedValue {
+ using T = std::decay_t<decltype(value)>;
+ if constexpr (std::is_same_v<T, i64>) {
+ return MakeUnversionedInt64Value(value, valueId);
+ } else if constexpr (std::is_same_v<T, ui64>) {
+ return MakeUnversionedUint64Value(value, valueId);
+ } else if constexpr (std::is_same_v<T, double>) {
+ return MakeUnversionedDoubleValue(value, valueId);
+ } else if constexpr (std::is_same_v<T, bool>) {
+ return MakeUnversionedBooleanValue(value, valueId);
+ } else if constexpr (std::is_same_v<T, TString>) {
+ return MakeUnversionedStringValue(value, valueId);
+ } else if constexpr (std::is_same_v<T, TAny>) {
+ return MakeUnversionedAnyValue(value.Value, valueId);
+ } else if constexpr (std::is_same_v<T, TComposite>) {
+ return MakeUnversionedCompositeValue(value.Value, valueId);
+ } else {
+ static_assert(std::is_same_v<T, std::nullptr_t>);
+ return MakeUnversionedSentinelValue(EValueType::Null, valueId);
+ }
+ }, Value_);
+}
+
+TNamedValue::TValue TNamedValue::ExtractValue(const NTableClient::TUnversionedValue& value)
+{
+ auto getString = [] (const TUnversionedValue& value) {
+ return value.AsString();
+ };
+ switch (value.Type) {
+ case EValueType::Null:
+ return nullptr;
+ case EValueType::Int64:
+ return value.Data.Int64;
+ case EValueType::Uint64:
+ return value.Data.Uint64;
+ case EValueType::Boolean:
+ return value.Data.Boolean;
+ case EValueType::Double:
+ return value.Data.Double;
+ case EValueType::String:
+ return getString(value);
+ case EValueType::Any:
+ return TAny{getString(value)};
+ case EValueType::Composite:
+ return TComposite{getString(value)};
+ case EValueType::Min:
+ case EValueType::Max:
+ case EValueType::TheBottom:
+ break;
+ }
+ YT_ABORT();
+}
+
+TNamedValue::TValue TNamedValue::ToValue(NTableClient::EValueType valueType, TStringBuf value) {
+ using namespace NTableClient;
+ if (valueType == EValueType::String) {
+ return TString(value);
+ } else if (valueType == EValueType::Any) {
+ return TAny{TString(value)};
+ } else if (valueType == EValueType::Composite) {
+ return TComposite{TString(value)};
+ } else {
+ YT_ABORT();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator ==(const TNamedValue::TAny& lhs, const TNamedValue::TAny& rhs)
+{
+ return lhs.Value == rhs.Value;
+}
+
+bool operator !=(const TNamedValue::TAny& lhs, const TNamedValue::TAny& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool operator ==(const TNamedValue::TComposite& lhs, const TNamedValue::TComposite& rhs)
+{
+ return lhs.Value == rhs.Value;
+}
+
+bool operator !=(const TNamedValue::TComposite& lhs, const TNamedValue::TComposite& rhs)
+{
+ return !(lhs.Value == rhs.Value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/named_value/named_value.h b/yt/yt/library/named_value/named_value.h
new file mode 100644
index 0000000000..9ac205c702
--- /dev/null
+++ b/yt/yt/library/named_value/named_value.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <yt/yt/client/table_client/unversioned_row.h>
+#include <yt/yt/client/table_client/name_table.h>
+
+#include <initializer_list>
+
+namespace NYT::NNamedValue {
+
+class TNamedValue;
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Not efficient but convenient way to make a row, useful in tests.
+/*!
+ * Meant to be used like this:
+ *
+ * auto owningRow = MakeRow(namedTable, {
+ * {"string-column-name", "value"},
+ * {"int-column-name", -42},
+ * {"uint-column-name", 42u},
+ * {"null-column-name", nullptr},
+ * {"any-column-name", EValueType::Any, "{foo=bar}"},
+ * {"composite-column-name", EValueType::Composite, "[1;2;3]"},
+ * });
+ */
+NTableClient::TUnversionedOwningRow MakeRow(
+ const NTableClient::TNameTablePtr& nameTable,
+ const std::initializer_list<TNamedValue>& values);
+
+NTableClient::TUnversionedOwningRow MakeRow(
+ const NTableClient::TNameTablePtr& nameTable,
+ const std::vector<TNamedValue>& values);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Slow but convenient analogue of TUnversionedValue
+class TNamedValue
+{
+public:
+ struct TAny {
+ TString Value;
+ };
+
+ struct TComposite {
+ TString Value;
+ };
+
+ using TValue = std::variant<std::nullptr_t, i64, ui64, double, bool, TString, TAny, TComposite>;
+
+public:
+ template <typename T>
+ TNamedValue(TString name, T value)
+ : Name_(std::move(name))
+ , Value_(std::move(value))
+ { }
+
+ template <>
+ TNamedValue(TString name, std::nullptr_t)
+ : Name_(std::move(name))
+ , Value_()
+ { }
+
+ template <>
+ TNamedValue(TString name, unsigned value)
+ : Name_(std::move(name))
+ , Value_(static_cast<ui64>(value))
+ { }
+
+ template <>
+ TNamedValue(TString name, TStringBuf value)
+ : Name_(std::move(name))
+ , Value_(TString(value))
+ { }
+
+ TNamedValue(TString name, NTableClient::EValueType valueType, TStringBuf value)
+ : Name_(std::move(name))
+ , Value_(ToValue(valueType, TString(value)))
+ { }
+
+ NTableClient::TUnversionedValue ToUnversionedValue(const NTableClient::TNameTablePtr& nameTable) const;
+
+ static TValue ExtractValue(const NTableClient::TUnversionedValue& value);
+
+private:
+ TValue ToValue(NTableClient::EValueType valueType, TStringBuf value);
+
+private:
+ TString Name_;
+ TValue Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator ==(const TNamedValue::TAny& lhs, const TNamedValue::TAny& rhs);
+bool operator !=(const TNamedValue::TAny& lhs, const TNamedValue::TAny& rhs);
+
+bool operator ==(const TNamedValue::TComposite& lhs, const TNamedValue::TComposite& rhs);
+bool operator !=(const TNamedValue::TComposite& lhs, const TNamedValue::TComposite& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NNamedValue
diff --git a/yt/yt/library/named_value/ya.make b/yt/yt/library/named_value/ya.make
new file mode 100644
index 0000000000..3cd43d39db
--- /dev/null
+++ b/yt/yt/library/named_value/ya.make
@@ -0,0 +1,13 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ yt/yt/client
+)
+
+SRCS(
+ named_value.cpp
+)
+
+END()
diff --git a/yt/yt/library/numeric/CMakeLists.linux-aarch64.txt b/yt/yt/library/numeric/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..19789bf730
--- /dev/null
+++ b/yt/yt/library/numeric/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-numeric)
+target_compile_options(yt-library-numeric PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-numeric PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yt-small_containers
+)
+target_sources(yt-library-numeric PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/numeric/piecewise_linear_function.cpp
+)
diff --git a/yt/yt/library/numeric/CMakeLists.linux-x86_64.txt b/yt/yt/library/numeric/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..19789bf730
--- /dev/null
+++ b/yt/yt/library/numeric/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,22 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-numeric)
+target_compile_options(yt-library-numeric PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-numeric PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yt-small_containers
+)
+target_sources(yt-library-numeric PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/numeric/piecewise_linear_function.cpp
+)
diff --git a/yt/yt/library/numeric/CMakeLists.txt b/yt/yt/library/numeric/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/library/numeric/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/library/numeric/binary_search-inl.h b/yt/yt/library/numeric/binary_search-inl.h
new file mode 100644
index 0000000000..1b428261c2
--- /dev/null
+++ b/yt/yt/library/numeric/binary_search-inl.h
@@ -0,0 +1,155 @@
+#ifndef BINARY_SEARCH_INL_H_
+#error "Direct inclusion of this file is not allowed, include binary_search.h"
+// For the sake of sane code completion.
+#include "binary_search.h"
+#endif
+
+namespace NYT {
+
+using std::uint32_t;
+using std::uint64_t;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+static constexpr uint64_t Uint64FirstBit = 1ull << 63ull;
+
+// In IEEE 754 the bit representation of non-negative finite doubles is monotonous,
+// i.e. |BitCast<uint64_t>(x1) < BitCast<uin64_t>(x2)| iff |x1 < x2|.
+// Negative numbers are represented by setting the first bit (called "sign bit") to 1.
+//
+// In order to create monotonous one-to-one mapping between |uint64_t| and |double|,
+// we flip all bits in the binary representation of negative values (map them to the numbers less than 2^63),
+// and flip the sign bit in the binary representation of positive values
+// (map them to the numbers larger than or equal to 2^63).
+//
+// Examples:
+// 0.0 is represented by 0x8000000000000000 (2^63).
+// -0.0 is represented by 0x7FFFFFFFFFFFFFFF (2^63 - 1).
+// +inf is represented by 0xFFF0000000000000. Any (lexicographically) larger bit pattern represents NaN.
+// -inf is represented by 0x000FFFFFFFFFFFFF. Any (lexicographically) smaller bit pattern represents NaN.
+inline uint64_t DoubleToBitset(double value) noexcept
+{
+ auto bitset = BitCast<uint64_t>(value);
+ // If the sign bit is 1, mask=0xFFFFFFFFFFFFFFFF. Otherwise, mask=0x8000000000000000.
+ // We use arithmetic right shift to achieve this.
+ uint64_t mask = (static_cast<int64_t>(bitset) >> 63) | Uint64FirstBit; // NOLINT(hicpp-signed-bitwise)
+ // For negative values (first bit is 1), we flip all bits.
+ // For positive values (first bit is 0), we flip only the first bit.
+ return bitset ^ mask;
+}
+
+inline double BitsetToDouble(uint64_t bitset) noexcept
+{
+ // If the first bit is 0, mask=0xFFFFFFFFFFFFFFFF. Otherwise, mask=0x8000000000000000.
+ // We use arithmetic right shift to achieve this.
+ uint64_t mask = (static_cast<int64_t>(~bitset) >> 63) | Uint64FirstBit; // NOLINT(hicpp-signed-bitwise)
+ // To restore a negative value (first bit is 0), we flip all bits.
+ // To restore a positive value (first bit is 1), we flip only the first bit.
+ return BitCast<double>(bitset ^ mask);
+}
+
+} // namespace NDetail
+
+template <class TInt, class TPredicate>
+constexpr TInt IntegerLowerBound(TInt lo, TInt hi, TPredicate&& predicate)
+{
+ static_assert(std::is_integral_v<TInt>);
+
+ using TUInt = std::make_unsigned_t<TInt>;
+
+ Y_VERIFY(lo <= hi);
+
+ Y_VERIFY(predicate(hi));
+ if (predicate(lo)) {
+ return lo;
+ }
+
+ // Notice that lo < hi and hence does not overflow.
+ while (lo + 1 < hi) {
+ // NB(antonkikh): std::midpoint is slow because it also handles the case when lo > hi.
+ TInt mid = lo + static_cast<TInt>(static_cast<TUInt>(hi - lo) >> 1);
+
+ if (predicate(mid)) {
+ hi = mid;
+ } else {
+ lo = mid;
+ }
+ }
+
+ return hi;
+}
+
+template <class TInt, class TPredicate>
+constexpr TInt IntegerInverseLowerBound(TInt lo, TInt hi, TPredicate&& predicate)
+{
+ static_assert(std::is_integral_v<TInt>);
+
+ using TUInt = std::make_unsigned_t<TInt>;
+
+ Y_VERIFY(lo <= hi);
+
+ Y_VERIFY(predicate(lo));
+ if (predicate(hi)) {
+ return hi;
+ }
+
+ // Notice that lo < hi and hence does not overflow.
+ while (lo + 1 < hi) {
+ // NB(antonkikh): std::midpoint is slow because it also handles the case when lo > hi.
+ TInt mid = lo + static_cast<TInt>(static_cast<TUInt>(hi - lo) >> 1);
+
+ if (predicate(mid)) {
+ lo = mid;
+ } else {
+ hi = mid;
+ }
+ }
+
+ return lo;
+}
+
+template <class TPredicate>
+double FloatingPointLowerBound(double lo, double hi, TPredicate&& predicate)
+{
+ Y_VERIFY(!std::isnan(lo));
+ Y_VERIFY(!std::isnan(hi));
+ Y_VERIFY(lo <= hi);
+
+ // NB(antonkikh): Note that this handles the case when |hi == -0.0| and |lo == 0.0|.
+ if (lo == hi) {
+ Y_VERIFY(predicate(hi));
+ return hi;
+ }
+
+ uint64_t resultBitset = IntegerLowerBound(
+ NDetail::DoubleToBitset(lo),
+ NDetail::DoubleToBitset(hi),
+ [&predicate] (uint64_t bitset) { return predicate(NDetail::BitsetToDouble(bitset)); });
+ return NDetail::BitsetToDouble(resultBitset);
+}
+
+template <class TPredicate>
+double FloatingPointInverseLowerBound(double lo, double hi, TPredicate&& predicate)
+{
+ Y_VERIFY(!std::isnan(lo));
+ Y_VERIFY(!std::isnan(hi));
+ Y_VERIFY(lo <= hi);
+
+ // NB(antonkikh): Note that this handles the case when |hi == -0.0| and |lo == 0.0|.
+ if (lo == hi) {
+ Y_VERIFY(predicate(lo));
+ return lo;
+ }
+
+ uint64_t resultBitset = IntegerInverseLowerBound(
+ NDetail::DoubleToBitset(lo),
+ NDetail::DoubleToBitset(hi),
+ [&predicate] (uint64_t bitset) { return predicate(NDetail::BitsetToDouble(bitset)); });
+ return NDetail::BitsetToDouble(resultBitset);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/binary_search.h b/yt/yt/library/numeric/binary_search.h
new file mode 100644
index 0000000000..d8a695a08b
--- /dev/null
+++ b/yt/yt/library/numeric/binary_search.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "util.h"
+
+#include <util/system/yassert.h>
+
+#include <cmath>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static_assert(std::numeric_limits<double>::is_iec559, "We assume IEEE 754 floating point implementation");
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Returns the lowest integer value in [|lo|, |hi|] satisfying the predicate.
+// |lo| must not be greater than |hi|.
+// |predicate| must be monotonous and deterministic, and |predicate(hi)| must be satisfied.
+// |predicate(FloatingPointLowerBound(lo, hi, predicate))| is guaranteed to be satisfied.
+// Note that if |predicate(lo)| is satisfied, |lo| is returned.
+// The behaviour is undefined if |predicate| is not monotonous or not deterministic.
+template <class TInt, class TPredicate>
+constexpr TInt IntegerLowerBound(TInt lo, TInt hi, TPredicate&& predicate);
+
+// Return the highest integer value in [|lo|, |hi|] satisfying the predicate.
+// |lo| must not be greater than |hi|.
+// |predicate| must be monotonous and determenistic, and |predicate(lo)| must be satisfied.
+// |predicate(IntegerInverseLowerBound(lo, hi, predicate))| is guaranteed to be satisfied.
+// Note that if |predicate(hi)| is satisfied, |hi| is returned.
+// The behaviour is undefined if |predicate| is not monotonous or not deterministic.
+template <class TInt, class TPredicate>
+constexpr TInt IntegerInverseLowerBound(TInt lo, TInt hi, TPredicate&& predicate);
+
+// Returns the lowest representable value in [|lo|, |hi|] satisfying the predicate.
+// |lo| an |hi| must not be NaN (as for |std::isnan|), and |lo| must not be greater than |hi|.
+// Notice that |lo| and / or |hi| can be infinite.
+// |predicate| must be monotonous and deterministic, and |predicate(hi)| must be satisfied.
+// |predicate(FloatingPointLowerBound(lo, hi, predicate))| is guaranteed to be satisfied.
+// The behaviour is undefined if |predicate| is not monotonous or not deterministic.
+// The function makes no more than 70 calls to |predicate|
+// (64 for the binary search, and 6 are reserved for corner case handling).
+template <class TPredicate>
+double FloatingPointLowerBound(double lo, double hi, TPredicate&& predicate);
+
+// Returns the highest representable value in [|lo|, |hi|] satisfying the predicate.
+// |lo| an |hi| must not be NaN (as for |std::isnan|), and |lo| must not be greater than |hi|.
+// Notice that |lo| and / or |hi| can be infinite.
+// |predicate| must be monotonous and deterministic, and |predicate(lo)| must be satisfied.
+// |predicate(FloatingPointInverseLowerBound(lo, hi, predicate))| is guaranteed to be satisfied.
+// The behaviour is undefined if |predicate| is not monotonous or not deterministic.
+// The function makes no more than 70 calls to |predicate|
+// (64 for the binary search, and 6 are reserved for corner case handling).
+template <class TPredicate>
+double FloatingPointInverseLowerBound(double lo, double hi, TPredicate&& predicate);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define BINARY_SEARCH_INL_H_
+#include "binary_search-inl.h"
+#undef BINARY_SEARCH_INL_H_
diff --git a/yt/yt/library/numeric/double_array.h b/yt/yt/library/numeric/double_array.h
new file mode 100644
index 0000000000..58555d93dd
--- /dev/null
+++ b/yt/yt/library/numeric/double_array.h
@@ -0,0 +1,420 @@
+#pragma once
+
+#include <vector>
+#include <algorithm>
+#include <array>
+
+#include <util/system/yassert.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// "Double array" is a real valued vector in mathematical sense (not related to |std::vector|).
+// This class is not intended for direct instantiation.
+// If you just need a general purpose double valued vector, use |TDoubleArray| instead.
+//
+// This class can be used to create custom extensions of the double array abstraction.
+// E.g. one might add custom factory methods and custom |operator()| overloads.
+// In order to inherit all functionality of |TDoubleArray| (including all helper functions such as Min, Max, ForEach,
+// Apply, and so on), one must derive from |TDoubleArrayBase<DimCnt, TDerived>|, where |DimCnt| is the desired number
+// of dimensions and |TDerived| is the derived class
+// (see https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern for more details on this technique).
+// See |TDoubleArray| as an example.
+template <size_t DimCnt, class TDerived>
+class TDoubleArrayBase
+{
+public:
+ using value_type = double;
+ using iterator = double*;
+ using const_iterator = const double*;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ static constexpr size_t Size = DimCnt;
+
+public:
+ constexpr TDoubleArrayBase()
+ : Values_({})
+ { }
+
+ constexpr TDoubleArrayBase(std::initializer_list<double> values)
+ {
+ Y_VERIFY_DEBUG(values.size() == Size);
+ std::copy(std::begin(values), std::end(values), std::begin(Values_));
+ }
+
+ Y_FORCE_INLINE int size() const
+ {
+ return Values_.size();
+ }
+
+ Y_FORCE_INLINE double& operator[](int i)
+ {
+ return Values_[i];
+ }
+
+ Y_FORCE_INLINE const double& operator[](int i) const
+ {
+ return Values_[i];
+ }
+
+ Y_FORCE_INLINE iterator begin()
+ {
+ return std::begin(Values_);
+ }
+
+ Y_FORCE_INLINE iterator end()
+ {
+ return std::end(Values_);
+ }
+
+ Y_FORCE_INLINE const_iterator begin() const
+ {
+ return std::begin(Values_);
+ }
+
+ Y_FORCE_INLINE const_iterator end() const
+ {
+ return std::end(Values_);
+ }
+
+ Y_FORCE_INLINE const_iterator cbegin() const
+ {
+ return std::cbegin(Values_);
+ }
+
+ Y_FORCE_INLINE const_iterator cend() const
+ {
+ return std::cend(Values_);
+ }
+
+ Y_FORCE_INLINE reverse_iterator rbegin()
+ {
+ return std::rbegin(Values_);
+ }
+
+ Y_FORCE_INLINE reverse_iterator rend()
+ {
+ return std::rend(Values_);
+ }
+
+ Y_FORCE_INLINE const_reverse_iterator rbegin() const
+ {
+ return std::rbegin(Values_);
+ }
+
+ Y_FORCE_INLINE const_reverse_iterator rend() const
+ {
+ return std::rend(Values_);
+ }
+
+ Y_FORCE_INLINE const_reverse_iterator crbegin() const
+ {
+ return std::crbegin(Values_);
+ }
+
+ Y_FORCE_INLINE const_reverse_iterator crend() const
+ {
+ return std::crend(Values_);
+ }
+
+ static constexpr TDerived FromDouble(double value)
+ {
+ TDerived result;
+ std::fill(std::begin(result), std::end(result), value);
+ return result;
+ }
+
+ static constexpr TDerived Zero()
+ {
+ return FromDouble(0.0);
+ }
+
+ static constexpr TDerived Ones()
+ {
+ return FromDouble(1.0);
+ }
+
+private:
+ std::array<double, Size> Values_;
+
+public:
+ template <class TPredicate>
+ constexpr static bool All(const TDerived& vec, TPredicate&& predicate)
+ {
+ return std::all_of(std::begin(vec), std::end(vec), std::forward<TPredicate>(predicate));
+ }
+
+ template <class TPredicate>
+ constexpr static bool All(const TDerived& vec1, const TDerived& vec2, TPredicate&& predicate)
+ {
+ for (size_t i = 0; i < Size; i++) {
+ if (!predicate(vec1[i], vec2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ template <class TPredicate>
+ constexpr static bool All(const TDerived& vec1, const TDerived& vec2, const TDerived& vec3, TPredicate&& predicate)
+ {
+ for (size_t i = 0; i < Size; i++) {
+ if (!predicate(vec1[i], vec2[i], vec3[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ template <class TPredicate>
+ constexpr static bool Any(const TDerived& vec, TPredicate&& predicate)
+ {
+ return std::any_of(std::begin(vec), std::end(vec), std::forward<TPredicate>(predicate));
+ }
+
+ template <class TPredicate>
+ constexpr static bool Any(const TDerived& vec1, const TDerived& vec2, TPredicate&& predicate)
+ {
+ for (size_t i = 0; i < Size; i++) {
+ if (predicate(vec1[i], vec2[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <class TPredicate>
+ constexpr static bool Any(const TDerived& vec1, const TDerived& vec2, const TDerived& vec3, TPredicate&& predicate)
+ {
+ for (size_t i = 0; i < Size; i++) {
+ if (predicate(vec1[i], vec2[i], vec3[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <class TOperation>
+ constexpr static TDerived Apply(const TDerived& vec, TOperation&& op)
+ {
+ TDerived res = {};
+ std::transform(std::begin(vec), std::end(vec), std::begin(res), std::forward<TOperation>(op));
+ return res;
+ }
+
+ template <class TOperation>
+ constexpr static TDerived Apply(const TDerived& vec1, const TDerived& vec2, TOperation&& op)
+ {
+ TDerived res = {};
+ for (size_t i = 0; i < Size; i++) {
+ res[i] = op(vec1[i], vec2[i]);
+ }
+ return res;
+ }
+
+ template <class TOperation>
+ constexpr static TDerived Apply(const TDerived& vec1, const TDerived& vec2, const TDerived& vec3, TOperation&& op)
+ {
+ TDerived res = {};
+ for (size_t i = 0; i < Size; i++) {
+ res[i] = op(vec1[i], vec2[i], vec3[i]);
+ }
+ return res;
+ }
+
+ template <class TFunction>
+ constexpr static void ForEach(const TDerived& vec, TFunction&& fn)
+ {
+ std::for_each(std::begin(vec), std::end(vec), std::forward<TFunction>(fn));
+ }
+
+ template <class TFunction>
+ constexpr static void ForEach(const TDerived& vec1, const TDerived& vec2, TFunction&& fn)
+ {
+ for (size_t i = 0; i < Size; i++) {
+ fn(vec1[i], vec2[i]);
+ }
+ }
+
+ template <class TFunction>
+ constexpr static void ForEach(const TDerived& vec1, const TDerived& vec2, const TDerived& vec3, TFunction&& fn)
+ {
+ for (size_t i = 0; i < Size; i++) {
+ fn(vec1[i], vec2[i], vec3[i]);
+ }
+ }
+
+ constexpr static bool Near(const TDerived& lhs, const TDerived& rhs, double precision)
+ {
+ return All(lhs, rhs, [&](auto x, auto y) { return std::abs(x - y) <= precision; });
+ }
+
+ constexpr static TDerived Max(const TDerived& lhs, const TDerived& rhs)
+ {
+ return TDerived::Apply(lhs, rhs, [](auto x, auto y) { return std::max(x, y); });
+ }
+
+ constexpr static TDerived Min(const TDerived& lhs, const TDerived& rhs)
+ {
+ return TDerived::Apply(lhs, rhs, [](auto x, auto y) { return std::min(x, y); });
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <size_t DimCnt, class TDerived>
+std::true_type IsDoubleArrayImpl(const TDoubleArrayBase<DimCnt, TDerived>*);
+
+std::false_type IsDoubleArrayImpl(const void*);
+
+} // namespace NDetail
+
+template <class T>
+constexpr bool IsDoubleArray = decltype(NDetail::IsDoubleArrayImpl(std::declval<T*>()))::value;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr bool Dominates(const TDerived& lhs, const TDerived& rhs)
+{
+ return TDerived::All(lhs, rhs, [](auto x, auto y) { return x >= y; });
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr double MaxComponent(const TDerived& vec)
+{
+ double result = std::numeric_limits<double>::lowest();
+ for (size_t i = 0; i < TDerived::Size; i++) {
+ result = std::max(result, vec[i]);
+ }
+ return result;
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr double MinComponent(const TDerived& vec)
+{
+ double result = std::numeric_limits<double>::max();
+ for (size_t i = 0; i < TDerived::Size; i++) {
+ result = std::min(result, vec[i]);
+ }
+ return result;
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr bool operator==(const TDerived& lhs, const TDerived& rhs)
+{
+ return TDerived::All(lhs, rhs, [](auto x, auto y) { return x == y; });
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr bool operator!=(const TDerived& lhs, const TDerived& rhs)
+{
+ return !(lhs == rhs);
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr TDerived operator+(const TDerived& lhs, const TDerived& rhs)
+{
+ return TDerived::Apply(lhs, rhs, [](auto x, auto y) { return x + y; });
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr TDerived operator-(const TDerived& lhs, const TDerived& rhs)
+{
+ return TDerived::Apply(lhs, rhs, [](auto x, auto y) { return x - y; });
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr TDerived operator-(const TDerived& lhs)
+{
+ return TDerived::Apply(lhs, [](auto x) { return -x; });
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr void operator+=(TDerived& lhs, const TDerived& rhs)
+{
+ for (size_t i = 0; i < TDerived::Size; i++) {
+ lhs[i] += rhs[i];
+ }
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr void operator-=(TDerived& lhs, const TDerived& rhs)
+{
+ for (size_t i = 0; i < TDerived::Size; i++) {
+ lhs[i] -= rhs[i];
+ }
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr TDerived operator*(const TDerived& vec, double scalar)
+{
+ return TDerived::Apply(vec, [=](auto x) { return x * scalar; });
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr TDerived operator/(const TDerived& vec, double scalar)
+{
+ return TDerived::Apply(vec, [=](auto x) { return x / scalar; });
+}
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+constexpr TDerived Div(
+ const TDerived& lhs,
+ const TDerived& rhs,
+ double zeroDivByZero,
+ double oneDivByZero)
+{
+ return TDerived::Apply(lhs, rhs, [=](auto x, auto y) {
+ if (y == 0) {
+ if (x == 0) {
+ return zeroDivByZero;
+ } else {
+ return oneDivByZero;
+ }
+ } else {
+ return x / y;
+ }
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// "Double array" is a real valued vector in mathematical sense (not related to |std::vector|).
+// This class can be used as a general purpose real valued vector. Please note that this class is final.
+// If you want to make any extensions of the abstraction, use |TDoubleArrayBase| instead.
+//
+// Example of usage:
+// TDoubleArray<4> vec1 = {1, 2, 3, 4};
+// Y_VERIFY(vec1[3] == 4);
+// Y_VERIFY(TDoubleArray<4>::All(vec1, [] (double x) { return x > 0; }));
+// Y_VERIFY(MinComponent(vec1) == 1);
+//
+// TDoubleArray<4> vec2 = {4, 3, 2, 1};
+// Y_VERIFY(vec1 + vec2 == TDoubleArray<4>::FromDouble(5));
+//
+// // |vec1 * vec1| wouldn't work because multiplication is not defined for mathematical vectors.
+// auto vec1Square = TDoubleArray<4>::Apply(vec1, [] (double x) { return x * x; });
+// Y_VERIFY(TDoubleArray<4>::All(vec1, vec1Square, [] (double x, double y) { return y == x * x; }));
+template <size_t DimCnt>
+class TDoubleArray final : public TDoubleArrayBase<DimCnt, TDoubleArray<DimCnt>>
+{
+private:
+ using TBase = TDoubleArrayBase<DimCnt, TDoubleArray>;
+
+public:
+ // For some reason, cannot use |TBase::TDoubleArrayBase| for constructor inheritance.
+ using TDoubleArrayBase<DimCnt, TDoubleArray>::TDoubleArrayBase;
+
+ using TBase::operator[];
+};
+
+static_assert(IsDoubleArray<TDoubleArray<3>>);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/double_array_format.h b/yt/yt/library/numeric/double_array_format.h
new file mode 100644
index 0000000000..2fddfbc264
--- /dev/null
+++ b/yt/yt/library/numeric/double_array_format.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "vector_format.h"
+#include "double_array.h"
+
+#include <util/string/builder.h>
+
+namespace NYT::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This overload is important for readable output in tests.
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+std::ostream& operator<<(std::ostream& os, const TDerived& vec)
+{
+ os << ToString(vec);
+ return os;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+}; // namespace NYT::NDetail
diff --git a/yt/yt/library/numeric/piecewise_linear_function-inl.h b/yt/yt/library/numeric/piecewise_linear_function-inl.h
new file mode 100644
index 0000000000..1a1caac023
--- /dev/null
+++ b/yt/yt/library/numeric/piecewise_linear_function-inl.h
@@ -0,0 +1,1133 @@
+#ifndef PIECEWISE_LINEAR_FUNCTION_INL_H_
+#error "Direct inclusion of this file is not allowed, include piecewise_linear_function.h"
+// For the sake of sane code completion.
+#include "piecewise_linear_function.h"
+#endif
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+TPiecewiseSegment<TValue>::TPiecewiseSegment(std::pair<double, TValue> leftPoint, std::pair<double, TValue> rightPoint)
+ : LeftBound_(leftPoint.first)
+ , LeftValue_(leftPoint.second)
+ , RightBound_(rightPoint.first)
+ , RightValue_(rightPoint.second)
+{
+ Y_VERIFY(LeftBound_ <= RightBound_);
+}
+
+template <class TValue>
+double TPiecewiseSegment<TValue>::LeftBound() const
+{
+ return LeftBound_;
+}
+
+template <class TValue>
+double TPiecewiseSegment<TValue>::RightBound() const
+{
+ return RightBound_;
+}
+
+template <class TValue>
+const TValue& TPiecewiseSegment<TValue>::LeftValue() const
+{
+ return LeftValue_;
+}
+
+template <class TValue>
+const TValue& TPiecewiseSegment<TValue>::RightValue() const
+{
+ return RightValue_;
+}
+
+template <class TValue>
+bool TPiecewiseSegment<TValue>::IsDefinedAt(double x) const
+{
+ return x >= LeftBound_ && x <= RightBound_;
+}
+
+template <class TValue>
+bool TPiecewiseSegment<TValue>::IsDefinedOn(double left, double right) const
+{
+ return IsDefinedAt(left) && IsDefinedAt(right);
+}
+
+template <class TValue>
+TValue TPiecewiseSegment<TValue>::LeftLimitAt(double x) const
+{
+ Y_VERIFY(IsDefinedAt(x));
+ return LeftRightLimitAt(x).first;
+}
+
+template <class TValue>
+TValue TPiecewiseSegment<TValue>::RightLimitAt(double x) const
+{
+ Y_VERIFY(IsDefinedAt(x));
+ return LeftRightLimitAt(x).second;
+}
+
+template <class TValue>
+std::pair<TValue, TValue> TPiecewiseSegment<TValue>::LeftRightLimitAt(double x) const
+{
+ Y_VERIFY(IsDefinedAt(x));
+ if (RightBound() == LeftBound()) {
+ return {LeftValue_, RightValue_};
+ } else {
+ TValue res = InterpolateAt(x);
+ return {res, res};
+ }
+}
+
+template <class TValue>
+TValue TPiecewiseSegment<TValue>::ValueAt(double x) const
+{
+ Y_VERIFY(IsDefinedAt(x));
+ // NB: We currently assume all functions to be left-continuous.
+ return LeftLimitAt(x);
+}
+
+template <class TValue>
+bool TPiecewiseSegment<TValue>::IsVertical() const
+{
+ return LeftBound_ == RightBound_ && LeftValue_ != RightValue_;
+}
+
+template <class TValue>
+bool TPiecewiseSegment<TValue>::IsHorizontal() const
+{
+ return LeftBound_ != RightBound_ && LeftValue_ == RightValue_;
+}
+
+template <class TValue>
+bool TPiecewiseSegment<TValue>::IsPoint() const
+{
+ return LeftBound_ == RightBound_ && LeftValue_ == RightValue_;
+}
+
+template <class TValue>
+bool TPiecewiseSegment<TValue>::IsTilted() const
+{
+ return LeftBound_ != RightBound_ && LeftValue_ != RightValue_;
+}
+
+template <class TValue>
+TPiecewiseSegment<TValue> TPiecewiseSegment<TValue>::Transpose() const
+{
+ static_assert(std::is_same_v<TValue, double>);
+ return TPiecewiseSegment({LeftValue_, LeftBound_}, {RightValue_, RightBound_});
+}
+
+template <class TValue>
+TPiecewiseSegment<TValue> TPiecewiseSegment<TValue>::ScaleArgument(double scale) const
+{
+ return TPiecewiseSegment({LeftBound_ / scale, LeftValue_}, {RightBound_ / scale, RightValue_});
+}
+
+template <class TValue>
+TPiecewiseSegment<TValue> TPiecewiseSegment<TValue>::Shift(double deltaBound, const TValue& deltaValue) const
+{
+ return TPiecewiseSegment(
+ {LeftBound_ + deltaBound, LeftValue_ + deltaValue},
+ {RightBound_ + deltaBound, RightValue_ + deltaValue});
+}
+
+// The result of interpolation must satisfy the following properties:
+// (1) Match the endpoints.
+// I.e. |InterpolateAt(LeftBound()) == LeftValue()| and |InterpolateAt(RightBound()) == RightValue()|.
+// (2) Monotonicity.
+// I.e. if |LeftValue() <= RightValue()| and |x1 <= x2|, then |InterpolateAt(x1) <= InterpolateAt(x2)|.
+// (3) Bounded.
+// I.e. |InterpolateAt(x) >= LeftValue()| and |InterpolateAt(x) <= RightValue()|.
+// This follows from properties (1) and (2).
+// (4) Deterministic.
+// I.e. |InterpolateAt(x) == InterpolateAt(x)|.
+template <class TValue>
+TValue TPiecewiseSegment<TValue>::InterpolateAt(double x) const
+{
+ Y_VERIFY_DEBUG(IsDefinedAt(x));
+ Y_VERIFY_DEBUG(LeftBound_ != RightBound_);
+
+ // The value of t is monotonic and is exact at bounds.
+ double t = (x - LeftBound()) / (RightBound() - LeftBound());
+
+ Y_VERIFY_DEBUG(x != LeftBound() || t == 0);
+ Y_VERIFY_DEBUG(x != RightBound() || t == 1);
+
+ return InterpolateNormalized(t);
+}
+
+template <class TValue>
+TValue TPiecewiseSegment<TValue>::InterpolateNormalized(double t) const
+{
+ // The used method is from https://math.stackexchange.com/a/1798323
+ // It obviously matches the endpoints and is obviously monotonic in both halves.
+ // It turns out that it is also monotonic in the middle.
+ if (t < 0.5) {
+ return LeftValue() + (RightValue() - LeftValue()) * t;
+ } else {
+ return RightValue() - (RightValue() - LeftValue()) * (1 - t);
+ }
+}
+
+template <class TValue>
+bool operator ==(const TPiecewiseSegment<TValue>& lhs, const TPiecewiseSegment<TValue>& rhs) {
+ return lhs.LeftBound() == rhs.LeftBound() && lhs.RightBound() == rhs.RightBound() &&
+ lhs.LeftValue() == rhs.LeftValue() && lhs.RightValue() == rhs.RightValue();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+void ExtractCriticalPointsFromFunction(const TPiecewiseLinearFunction<TValue>& func, std::vector<double>* out)
+{
+ out->push_back(func.LeftFunctionBound());
+ double lastAdded = func.LeftFunctionBound();
+
+ for (const auto& segment : func.Segments()) {
+ if (segment.RightBound() > lastAdded) {
+ out->push_back(segment.RightBound());
+ lastAdded = segment.RightBound();
+ }
+ }
+}
+
+template <class TValue>
+void ExtractCriticalPointsFromFunctionWithValues(
+ const TPiecewiseLinearFunction<TValue>& func,
+ std::vector<std::pair<double, TValue>>* out)
+{
+ const auto& firstSegment = func.Segments().front();
+ out->emplace_back(firstSegment.LeftBound(), firstSegment.LeftValue());
+
+ for (const auto& segment : func.Segments()) {
+ out->emplace_back(segment.RightBound(), segment.RightValue());
+ }
+}
+
+template <class TValue>
+void ExtractDiscontinuityPointsFromFunction(const TPiecewiseLinearFunction<TValue>& func, std::vector<double>* out)
+{
+ for (const auto& segment : func.Segments()) {
+ if (segment.IsVertical()) {
+ out->push_back(segment.LeftBound());
+ }
+ }
+}
+
+template <class TValue>
+void PushSegmentImpl(std::vector<TPiecewiseSegment<TValue>>* vec, TPiecewiseSegment<TValue> segment)
+{
+ if (!vec->empty()) {
+ // NB: Strict equality is required in both cases.
+ Y_VERIFY(vec->back().RightBound() == segment.LeftBound());
+ Y_VERIFY(vec->back().RightValue() == segment.LeftValue());
+
+ // Try to merge two segments.
+ const auto& leftSegment = vec->back();
+ const auto& rightSegment = segment;
+ auto mergedSegment = TPiecewiseSegment<TValue>(
+ {leftSegment.LeftBound(), leftSegment.LeftValue()},
+ {rightSegment.RightBound(), rightSegment.RightValue()});
+
+ if (rightSegment.IsPoint()) {
+ return;
+ }
+ if (leftSegment.IsPoint()) {
+ vec->back() = rightSegment;
+ return;
+ }
+ if (leftSegment.IsVertical() && rightSegment.IsVertical()) {
+ Y_VERIFY(mergedSegment.IsVertical());
+ vec->back() = mergedSegment;
+ return;
+ }
+ if (leftSegment.IsHorizontal() && rightSegment.IsHorizontal()) {
+ Y_VERIFY(mergedSegment.IsHorizontal());
+ vec->back() = mergedSegment;
+ return;
+ }
+ }
+
+ vec->push_back(segment);
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> PointwiseMin(
+ const TPiecewiseLinearFunction<TValue>& lhs,
+ const TPiecewiseLinearFunction<TValue>& rhs)
+{
+ double resultLeftBound = std::max(lhs.LeftFunctionBound(), rhs.LeftFunctionBound());
+ double resultRightBound = std::min(lhs.RightFunctionBound(), rhs.RightFunctionBound());
+ Y_VERIFY(resultLeftBound <= resultRightBound);
+
+ auto sampleResult = [
+ lhsTraverser = lhs.GetLeftToRightTraverser(),
+ rhsTraverser = rhs.GetLeftToRightTraverser()
+ ] (double x) mutable -> std::pair<double, double> {
+
+ auto [lhsLeftLimit, lhsRightLimit] = lhsTraverser.LeftRightLimitAt(x);
+ auto [rhsLeftLimit, rhsRightLimit] = rhsTraverser.LeftRightLimitAt(x);
+
+ return {
+ std::min(lhsLeftLimit, rhsLeftLimit),
+ std::min(lhsRightLimit, rhsRightLimit)
+ };
+ };
+
+ // Add critical points from the original functions.
+ std::vector<double> criticalPoints;
+ ExtractCriticalPointsFromFunction(lhs, &criticalPoints);
+ ExtractCriticalPointsFromFunction(rhs, &criticalPoints);
+
+ ClearAndSortCriticalPoints(&criticalPoints, resultLeftBound, resultRightBound);
+
+ // Add critical points on function intersections.
+ int criticalPointsInitialSize = criticalPoints.size();
+
+ auto lhsTraverser = lhs.GetLeftToRightTraverser();
+ auto rhsTraverser = rhs.GetLeftToRightTraverser();
+
+ // NB(antonkikh): We ignore intersections of vertical segments here since those points are already in |criticalPoints|.
+ for (int i = 1; i < criticalPointsInitialSize; i++) {
+ double leftBound = criticalPoints[i - 1];
+ double rightBound = criticalPoints[i];
+ Y_VERIFY(leftBound < rightBound);
+
+ // NB: Cannot use structure bindings here because it is not compatible with lambda capturing until C++20.
+ auto pair = MinMaxBy(
+ lhsTraverser.RightSegmentAt(leftBound),
+ rhsTraverser.RightSegmentAt(leftBound),
+ /* getKey */ [&] (const auto& segment) { return segment.ValueAt(leftBound); });
+ auto segmentLo = pair.first;
+ auto segmentHi = pair.second;
+
+ Y_VERIFY(segmentLo.IsDefinedOn(leftBound, rightBound));
+ Y_VERIFY(segmentHi.IsDefinedOn(leftBound, rightBound));
+
+ if (segmentLo.ValueAt(leftBound) < segmentHi.ValueAt(leftBound)
+ && segmentLo.ValueAt(rightBound) > segmentHi.ValueAt(rightBound))
+ {
+ criticalPoints.push_back(FloatingPointInverseLowerBound(
+ /* lo */ leftBound,
+ /* hi */ rightBound,
+ [&] (double mid) { return segmentLo.ValueAt(mid) <= segmentHi.ValueAt(mid); }));
+ }
+ }
+
+ // Build the result.
+ auto result = TPiecewiseLinearFunction<TValue>::Create(
+ sampleResult,
+ resultLeftBound,
+ resultRightBound,
+ std::move(criticalPoints));
+ return result;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> PointwiseMin(const std::vector<TPiecewiseLinearFunction<TValue>>& funcs)
+{
+ Y_VERIFY(!funcs.empty());
+ return std::accumulate(
+ begin(funcs) + 1,
+ end(funcs),
+ funcs[0],
+ [] (const auto& f1, const auto& f2) { return PointwiseMin(f1, f2); });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+void TPiecewiseLinearFunctionBuilder<TValue>::AddPoint(TPoint point)
+{
+ if (RightPoint_) {
+ PushSegmentImpl(&Segments_, TSegment(*RightPoint_, point));
+ }
+
+ RightPoint_ = point;
+}
+
+template <class TValue>
+void TPiecewiseLinearFunctionBuilder<TValue>::PushSegment(const TSegment& segment)
+{
+ if (RightPoint_) {
+ Y_VERIFY(RightPoint_->first == segment.LeftBound());
+ Y_VERIFY(RightPoint_->second == segment.LeftValue());
+ }
+ PushSegmentImpl(&Segments_, segment);
+ RightPoint_ = TPoint(segment.RightBound(), segment.RightValue());
+}
+
+template <class TValue>
+void TPiecewiseLinearFunctionBuilder<TValue>::PushSegment(
+ std::pair<double, TValue> leftPoint,
+ std::pair<double, TValue> rightPoint)
+{
+ PushSegment(TSegment(leftPoint, rightPoint));
+}
+
+template <class TValue>
+auto TPiecewiseLinearFunctionBuilder<TValue>::Finish() -> TFunction
+{
+ return TFunction(std::move(Segments_));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+template <class TFunction>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Create(
+ TFunction&& sampleFunction,
+ double leftBound,
+ double rightBound,
+ std::vector<double> criticalPoints)
+{
+ // sampleFunction(x) should return a pair of values: left-hand and right-hand limit for this argument.
+ static_assert(std::is_same_v<decltype(sampleFunction(1.0L)), std::pair<TValue, TValue>>);
+
+ ClearAndSortCriticalPoints(&criticalPoints, leftBound, rightBound);
+ Y_VERIFY(!criticalPoints.empty());
+ if (criticalPoints.front() != leftBound) {
+ throw yexception()
+ << "Left bound of the function must be its first critical point (CriticalPoints: ["
+ << NDetail::ToString(criticalPoints)
+ << "], LeftBound: " << leftBound << ")";
+ }
+ Y_VERIFY(criticalPoints.front() == leftBound);
+ Y_VERIFY(criticalPoints.back() == rightBound);
+
+ TBuilder builder;
+
+ for (double criticalPoint : criticalPoints) {
+ auto [leftLimit, rightLimit] = sampleFunction(criticalPoint);
+
+ builder.AddPoint({criticalPoint, leftLimit});
+ builder.AddPoint({criticalPoint, rightLimit});
+ }
+
+ TSelf result = builder.Finish();
+
+ Y_VERIFY_DEBUG(result.LeftFunctionBound() == leftBound);
+ Y_VERIFY_DEBUG(result.RightFunctionBound() == rightBound);
+
+ return result;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>::TPiecewiseLinearFunction(std::vector<TPiecewiseSegment<TValue>> segments)
+ : Segments_(std::move(segments))
+{
+ Y_VERIFY(!Segments_.empty());
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Linear(
+ double leftBound,
+ TValue leftValue,
+ double rightBound,
+ TValue rightValue)
+{
+ TBuilder builder;
+ builder.PushSegment({leftBound, leftValue}, {rightBound, rightValue});
+ return builder.Finish();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Constant(
+ double leftBound,
+ double rightBound,
+ TValue value)
+{
+ return Linear(leftBound, value, rightBound, value);
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::LeftLimitAt(double x) const
+{
+ return LeftSegmentAt(x).LeftLimitAt(x);
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::RightLimitAt(double x) const
+{
+ return RightSegmentAt(x).RightLimitAt(x);
+}
+
+template <class TValue>
+std::pair<TValue, TValue> TPiecewiseLinearFunction<TValue>::LeftRightLimitAt(double x) const
+{
+ return SegmentAt(x).LeftRightLimitAt(x);
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::ValueAt(double x) const
+{
+ // NB: We currently assume all functions to be left-continuous.
+ return LeftLimitAt(x);
+}
+
+template <class TValue>
+const std::vector<TPiecewiseSegment<TValue>>& TPiecewiseLinearFunction<TValue>::Segments() const
+{
+ return Segments_;
+}
+
+template <class TValue>
+double TPiecewiseLinearFunction<TValue>::LeftFunctionBound() const
+{
+ return Segments_.front().LeftBound();
+}
+
+template <class TValue>
+double TPiecewiseLinearFunction<TValue>::RightFunctionBound() const
+{
+ return Segments_.back().RightBound();
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::LeftFunctionValue() const
+{
+ return Segments_.front().LeftValue();
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::RightFunctionValue() const
+{
+ Y_VERIFY(!Segments_.back().IsVertical());
+ return Segments_.back().RightValue();
+}
+
+template <class TValue>
+bool TPiecewiseLinearFunction<TValue>::IsDefinedAt(double x) const
+{
+ return x >= LeftFunctionBound() && x <= RightFunctionBound();
+}
+
+template <class TValue>
+bool TPiecewiseLinearFunction<TValue>::IsContinuous() const
+{
+ return std::all_of(begin(Segments_), end(Segments_), [] (const auto& segment) {
+ return !segment.IsVertical();
+ });
+}
+
+template <class TValue>
+bool TPiecewiseLinearFunction<TValue>::IsNondecreasing() const
+{
+ return std::all_of(begin(Segments_), end(Segments_), [] (const auto& segment) {
+ return segment.LeftValue() <= segment.RightValue();
+ });
+}
+
+template <class TValue>
+bool TPiecewiseLinearFunction<TValue>::IsTrimmed() const
+{
+ return IsTrimmedRight();
+}
+
+template <class TValue>
+bool TPiecewiseLinearFunction<TValue>::IsTrimmedLeft() const
+{
+ return !Segments_.front().IsVertical();
+}
+
+template <class TValue>
+bool TPiecewiseLinearFunction<TValue>::IsTrimmedRight() const
+{
+ // Since we assume all functions to be left-continuous, only the last segment needs to be checked.
+ return !Segments_.back().IsVertical();
+}
+
+template <class TValue>
+const TPiecewiseSegment<TValue>& TPiecewiseLinearFunction<TValue>::LeftSegmentAt(double x, int* segmentIndex) const
+{
+ Y_VERIFY_DEBUG(IsDefinedAt(x));
+
+ // Finds first element with |RightBound() >= x|.
+ auto it = LowerBoundBy(
+ begin(Segments_),
+ end(Segments_),
+ /* value */ x,
+ [] (const auto& segment) { return segment.RightBound(); });
+ Y_VERIFY_DEBUG(it != end(Segments_));
+
+ if (segmentIndex != nullptr) {
+ *segmentIndex = it - begin(Segments_);
+ }
+ return *it;
+}
+
+template <class TValue>
+const TPiecewiseSegment<TValue>& TPiecewiseLinearFunction<TValue>::RightSegmentAt(double x, int* segmentIndex) const
+{
+ Y_VERIFY_DEBUG(IsDefinedAt(x));
+
+ // Returns first element with LeftBound() > x.
+ auto it = UpperBoundBy(
+ begin(Segments_),
+ end(Segments_),
+ /* value */ x,
+ [] (const auto& segment) { return segment.LeftBound(); });
+ Y_VERIFY(it != begin(Segments_));
+ // We need the last segment with LeftBound() <= x.
+ --it;
+
+ if (segmentIndex != nullptr) {
+ *segmentIndex = it - begin(Segments_);
+ }
+ return *it;
+}
+
+template <class TValue>
+const TPiecewiseSegment<TValue>& TPiecewiseLinearFunction<TValue>::SegmentAt(double x, int* segmentIndex) const
+{
+ Y_VERIFY_DEBUG(IsDefinedAt(x));
+
+ // Finds first element with |RightBound() >= x|.
+ auto it = LowerBoundBy(
+ begin(Segments_),
+ end(Segments_),
+ /* value */ x,
+ [] (const auto& segment) { return segment.RightBound(); });
+ Y_VERIFY_DEBUG(it != end(Segments_));
+
+ auto next = it + 1;
+ if (it->RightBound() == x && next != end(Segments_) && next->IsVertical()) {
+ Y_VERIFY_DEBUG(next->LeftBound() == x);
+ it = next;
+ }
+
+ if (segmentIndex != nullptr) {
+ *segmentIndex = it - begin(Segments_);
+ }
+ return *it;
+}
+
+template <class TValue>
+void TPiecewiseLinearFunction<TValue>::PushSegment(const TPiecewiseSegment<TValue>& segment)
+{
+ PushSegmentImpl(&Segments_, segment);
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>& TPiecewiseLinearFunction<TValue>::TransposeInplace()
+{
+ for (auto& segment : Segments_) {
+ segment = segment.Transpose();
+ }
+ return *this;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Transpose() const&
+{
+ auto res = *this;
+ res.TransposeInplace();
+ return res;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Transpose()&&
+{
+ auto res = std::move(*this);
+ return res.TransposeInplace();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>& TPiecewiseLinearFunction<TValue>::TrimLeftInplace()
+{
+ if (Segments_.front().IsVertical()) {
+ if (Segments_.size() > 1) {
+ // TODO(antonkikh): Consider implementing it efficiently by replacing Segments_ with an |std::dequeue|.
+ Segments_.erase(Segments_.begin());
+ } else {
+ // Replace the segment with a single point.
+ double argument = Segments_.front().LeftBound();
+ TValue value = Segments_.front().RightValue();
+ Segments_.front() = TSegment({argument, value}, {argument, value});
+ }
+
+ Y_VERIFY(!Segments_.front().IsVertical());
+ }
+
+ return *this;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::TrimLeft() const&
+{
+ auto res = *this;
+ return res.TrimLeftInplace();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::TrimLeft()&&
+{
+ auto res = std::move(*this);
+ return res.TrimLeftInplace();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>& TPiecewiseLinearFunction<TValue>::TrimRightInplace()
+{
+ // Since we assume all functions to be left-continuous, only the last segment needs to be checked.
+ if (Segments_.back().IsVertical()) {
+ if (Segments_.size() > 1) {
+ Segments_.pop_back();
+ } else {
+ // Replace the segment with a single point.
+ double argument = Segments_.back().LeftBound();
+ TValue value = Segments_.back().ValueAt(argument);
+ Segments_.back() = TSegment({argument, value}, {argument, value});
+ }
+
+ Y_VERIFY(!Segments_.back().IsVertical());
+ }
+
+ return *this;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::TrimRight() const&
+{
+ auto res = *this;
+ res.TrimRightInplace();
+ return res;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::TrimRight()&&
+{
+ auto res = std::move(*this);
+ return res.TrimRightInplace();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>& TPiecewiseLinearFunction<TValue>::TrimInplace()
+{
+ // Since we assume all functions to be left-continuous, only the discontinuity point at the right bound is non-sensible.
+ return TrimRightInplace();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Trim() const&
+{
+ return TrimRight();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Trim()&&
+{
+ return std::move(*this).TrimRight();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::ScaleArgument(double scale) const
+{
+ TBuilder builder;
+
+ for (const auto& segment : Segments_) {
+ builder.PushSegment(segment.ScaleArgument(scale));
+ }
+
+ return builder.Finish();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Shift(
+ double deltaArgument,
+ const TValue& deltaValue) const
+{
+ TBuilder builder;
+
+ for (const auto& segment : Segments_) {
+ builder.PushSegment(segment.Shift(deltaArgument, deltaValue));
+ }
+
+ return builder.Finish();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>& TPiecewiseLinearFunction<TValue>::NarrowInplace(
+ double newLeftBound,
+ double newRightBound)
+{
+ // TODO(antonkikh): Consider implementing it efficiently by replacing Segments_ with an |std::deque|.
+ *this = Narrow(newLeftBound, newRightBound);
+ return *this;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Narrow(
+ double newLeftBound,
+ double newRightBound) const
+{
+ Y_VERIFY(IsDefinedAt(newLeftBound));
+ Y_VERIFY(IsDefinedAt(newRightBound));
+ Y_VERIFY(newLeftBound <= newRightBound);
+
+ return *this + Constant(newLeftBound, newRightBound, TValue{});
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>& TPiecewiseLinearFunction<TValue>::ExtendInplace(
+ double newLeftBound,
+ const TValue& newLeftValue,
+ double newRightBound,
+ const TValue& newRightValue)
+{
+ // TODO(antonkikh): Consider implementing it efficiently by replacing Segments_ with an |std::deque|.
+ *this = Extend(newLeftBound, newLeftValue, newRightBound, newRightValue);
+ return *this;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Extend(
+ double newLeftBound,
+ const TValue& newLeftValue,
+ double newRightBound,
+ const TValue& newRightValue) const
+{
+ Y_VERIFY(newLeftBound <= LeftFunctionBound());
+
+ TBuilder builder;
+
+ if (newLeftBound < LeftFunctionBound()) {
+ builder.PushSegment({newLeftBound, newLeftValue}, {LeftFunctionBound(), newLeftValue});
+
+ TValue leftFunctionValue = Segments_.front().LeftValue();
+ if (newLeftValue != leftFunctionValue) {
+ builder.PushSegment({LeftFunctionBound(), newLeftValue}, {LeftFunctionBound(), leftFunctionValue});
+ }
+ }
+
+ for (const auto& segment : Segments_) {
+ builder.PushSegment(segment);
+ }
+
+ if (RightFunctionBound() < newRightBound) {
+ // NB(antonkikh): cannot use RightFunctionValue(), since the function might not be trimmed.
+ TValue rightFunctionValue = Segments_.back().RightValue();
+ if (newRightValue != rightFunctionValue) {
+ builder.PushSegment({RightFunctionBound(), rightFunctionValue}, {RightFunctionBound(), newRightValue});
+ }
+
+ builder.PushSegment({RightFunctionBound(), newRightValue}, {newRightBound, newRightValue});
+ }
+
+ return builder.Finish();
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Extend(
+ double newLeftBound,
+ double newRightBound) const
+{
+ // NB(antonkikh): cannot use |RightFunctionValue()|, since the function might not be trimmed.
+ return Extend(
+ newLeftBound,
+ Segments_.front().LeftValue(),
+ newRightBound,
+ Segments_.back().RightValue());
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::ExtendRight(
+ double newRightBound,
+ const TValue& newRightValue) const
+{
+ return Extend(
+ LeftFunctionBound(),
+ LeftFunctionValue(),
+ newRightBound,
+ newRightValue);
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::ExtendRight(double newRightBound) const
+{
+ // NB(antonkikh): cannot use |RightFunctionValue()|, since the function might not be trimmed.
+ return ExtendRight(newRightBound, Segments_.back().RightValue());
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::operator+(
+ const TPiecewiseLinearFunction<TValue>& other) const
+{
+ return ApplyBinaryOperation(*this, other, std::plus<>());
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>& TPiecewiseLinearFunction<TValue>::operator+=(
+ const TPiecewiseLinearFunction<TValue>& other)
+{
+ *this = *this + other;
+ return *this;
+}
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Sum(
+ const std::vector<TPiecewiseLinearFunction<TValue>>& funcs)
+{
+ Y_VERIFY(!funcs.empty());
+
+ double resultLeftBound = std::numeric_limits<double>::lowest();
+ double resultRightBound = std::numeric_limits<double>::max();
+ for (const auto& func : funcs) {
+ resultLeftBound = std::max(resultLeftBound, func.LeftFunctionBound());
+ resultRightBound = std::min(resultRightBound, func.RightFunctionBound());
+ }
+ Y_VERIFY(resultLeftBound <= resultRightBound);
+
+ std::vector<double> criticalPoints;
+ for (const auto& func : funcs) {
+ // TODO(antonkikh): |SortOrMerge| optimization won't work well here if |funcs.size()| is larger than a constant.
+ // Consider optimizing this place manually or modifying |SortOrMerge|.
+ ExtractCriticalPointsFromFunction(func, &criticalPoints);
+ }
+
+ std::vector<TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser> traversers;
+ for (const auto& func : funcs) {
+ traversers.emplace_back(func.GetLeftToRightTraverser());
+ }
+
+ auto sampleResult = [&] (double x) mutable -> std::pair<TValue, TValue> {
+ TValue leftLimit = {};
+ TValue rightLimit = {};
+
+ for (int i = 0; i < std::ssize(funcs); i++) {
+ auto [childLeftLimit, childRightLimit] = traversers[i].LeftRightLimitAt(x);
+ leftLimit += childLeftLimit;
+ rightLimit += childRightLimit;
+ }
+
+ return {leftLimit, rightLimit};
+ };
+
+ return Create(
+ sampleResult,
+ /* leftBound */ resultLeftBound,
+ /* rightBound */ resultRightBound,
+ std::move(criticalPoints));
+}
+
+template <class TValue>
+template <class TOther>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::Compose(const TOther& other) const
+{
+ // Verify input parameters.
+ static_assert(IsPiecewiseLinearFunction<TOther>);
+ static_assert(std::is_same_v<double, typename TOther::TValueType>);
+
+ if (!other.IsNondecreasing()) {
+ throw yexception() << "Composition is only supported for non-decreasing functions";
+ }
+ Y_VERIFY(IsDefinedAt(other.LeftLimitAt(other.LeftFunctionBound())));
+ Y_VERIFY(IsDefinedAt(other.RightLimitAt(other.RightFunctionBound())));
+
+ // Prepare critical points with the expected values of the rhs function at these points.
+ std::vector<std::pair<double, double>> criticalPoints;
+ ExtractCriticalPointsFromFunctionWithValues(other, &criticalPoints);
+
+ {
+ auto inverseOther = other.Transpose();
+
+ std::vector<double> lhsCriticalPoints;
+ ExtractCriticalPointsFromFunction(*this, &lhsCriticalPoints);
+
+ for (double rhsValue : lhsCriticalPoints) {
+ if (inverseOther.IsDefinedAt(rhsValue)) {
+ auto rhsInverseSegment = inverseOther.SegmentAt(rhsValue);
+ auto rhsSegment = rhsInverseSegment.Transpose();
+
+ // If this condition is not met, this critical point is sufficiently covered by the critical points of |other|.
+ if (rhsSegment.IsTilted() && rhsValue != rhsSegment.LeftValue() && rhsValue != rhsSegment.RightValue()) {
+ criticalPoints.emplace_back(rhsInverseSegment.ValueAt(rhsValue), rhsValue);
+ }
+ }
+ }
+ }
+
+ Sort(begin(criticalPoints), end(criticalPoints));
+ Y_VERIFY_DEBUG(IsSortedBy(begin(criticalPoints), end(criticalPoints), [] (const auto& pair) { return pair.second; }));
+ Y_VERIFY_DEBUG(Unique(begin(criticalPoints), end(criticalPoints)) == end(criticalPoints));
+
+ // Finally, build the resulting function.
+ TBuilder builder;
+
+ for (int i = 0; i < std::ssize(criticalPoints); i++) {
+ const auto &[criticalPoint, rhsValue] = criticalPoints[i];
+
+ // If this condition is not satisfied, the left-hand limit at this point was already calculated.
+ if (i == 0 || criticalPoints[i - 1].first != criticalPoint) {
+ TValue resultLeftLimit;
+ if (i > 0 && criticalPoints[i - 1].second < rhsValue) {
+ resultLeftLimit = LeftLimitAt(rhsValue);
+ } else {
+ resultLeftLimit = ValueAt(rhsValue);
+ }
+
+ builder.AddPoint({criticalPoint, resultLeftLimit});
+ }
+
+ // If this condition is not satisfied, the right-hand limit at this point will be calculated later.
+ if (i + 1 == std::ssize(criticalPoints) || criticalPoints[i + 1].first != criticalPoint) {
+ TValue resultRightLimit;
+ if (i + 1 < std::ssize(criticalPoints) && criticalPoints[i + 1].second > rhsValue) {
+ resultRightLimit = RightLimitAt(rhsValue);
+ } else {
+ resultRightLimit = ValueAt(rhsValue);
+ }
+
+ builder.AddPoint({criticalPoint, resultRightLimit});
+ }
+ }
+
+ TSelf result = builder.Finish().Trim();
+
+ Y_VERIFY(result.LeftFunctionBound() == other.LeftFunctionBound());
+ Y_VERIFY(result.RightFunctionBound() == other.RightFunctionBound());
+
+ return result;
+}
+
+template <class TValue>
+template <class TOperation>
+TPiecewiseLinearFunction<TValue> TPiecewiseLinearFunction<TValue>::ApplyBinaryOperation(
+ const TPiecewiseLinearFunction<TValue>& lhs,
+ const TPiecewiseLinearFunction<TValue>& rhs,
+ const TOperation& operation)
+{
+ double resultLeftBound = std::max(lhs.LeftFunctionBound(), rhs.LeftFunctionBound());
+ double resultRightBound = std::min(lhs.RightFunctionBound(), rhs.RightFunctionBound());
+
+ auto sampleResult = [
+ &operation,
+ lhsTraverser = lhs.GetLeftToRightTraverser(),
+ rhsTraverser = rhs.GetLeftToRightTraverser()
+ ] (double x) mutable -> std::pair<TValue, TValue> {
+
+ auto [lhsLeftLimit, lhsRightLimit] = lhsTraverser.LeftRightLimitAt(x);
+ auto [rhsLeftLimit, rhsRightLimit] = rhsTraverser.LeftRightLimitAt(x);
+
+ return {
+ operation(lhsLeftLimit, rhsLeftLimit),
+ operation(lhsRightLimit, rhsRightLimit)
+ };
+ };
+
+ std::vector<double> criticalPoints;
+ ExtractCriticalPointsFromFunction(lhs, &criticalPoints);
+ ExtractCriticalPointsFromFunction(rhs, &criticalPoints);
+
+ return Create(sampleResult, resultLeftBound, resultRightBound, std::move(criticalPoints));
+}
+
+template <class TValue>
+auto TPiecewiseLinearFunction<TValue>::GetLeftToRightTraverser(int segmentIndex) const -> TLeftToRightTraverser
+{
+ return TLeftToRightTraverser(*this, segmentIndex);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::TLeftToRightTraverser(
+ const TPiecewiseLinearFunction& function,
+ int segmentIndex)
+ : Function_(&function)
+ , Cur_(Function_->Segments().begin() + segmentIndex)
+ , End_(Function_->Segments().end())
+{
+ Y_VERIFY_DEBUG(segmentIndex < std::ssize(Function_->Segments()));
+}
+
+template <class TValue>
+const TPiecewiseSegment<TValue>& TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::LeftSegmentAt(double x)
+{
+ Y_VERIFY_DEBUG(Function_->IsDefinedAt(x));
+ Y_VERIFY_DEBUG(Cur_ == Function_->Segments().begin() || (Cur_ - 1)->RightBound() < x);
+
+ // Note that since |Function_->IsDefinedAt(x)| holds, we do not need to check that |Cur_ != End_|.
+ while (Cur_->RightBound() < x) {
+ ++Cur_;
+ }
+
+ // Return the first segment with |RightBound() >= x|.
+ return *Cur_;
+}
+
+template <class TValue>
+const TPiecewiseSegment<TValue>& TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::RightSegmentAt(double x)
+{
+ Y_VERIFY_DEBUG(Function_->IsDefinedAt(x));
+ Y_VERIFY_DEBUG(Cur_->LeftBound() <= x);
+
+ while (true) {
+ auto next = Cur_ + 1;
+
+ if (next != End_ && next->LeftBound() <= x) {
+ Cur_ = next;
+ } else {
+ break;
+ }
+ }
+
+ // Return the last segment with |LeftBound() <= x|.
+ return *Cur_;
+}
+
+template <class TValue>
+const TPiecewiseSegment<TValue>& TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::SegmentAt(double x)
+{
+ Y_VERIFY_DEBUG(Function_->IsDefinedAt(x));
+ Y_VERIFY_DEBUG(Cur_->LeftBound() <= x);
+ // Note that since |Function_->IsDefinedAt(x)| holds, we do not need to check that |Cur_ != End_|.
+ while (Cur_->RightBound() < x) {
+ ++Cur_;
+ }
+ // |Cur_->RightBound() >= x|.
+
+ auto next = Cur_ + 1;
+ if (Cur_->RightBound() == x && next != End_ && next->IsVertical()) {
+ Y_VERIFY_DEBUG(next->LeftBound() == x);
+ Cur_ = next;
+ }
+
+ return *Cur_;
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::ValueAt(double x)
+{
+ return LeftLimitAt(x);
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::LeftLimitAt(double x)
+{
+ return LeftSegmentAt(x).LeftLimitAt(x);
+}
+
+template <class TValue>
+TValue TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::RightLimitAt(double x)
+{
+ return LeftSegmentAt(x).RightLimitAt(x);
+}
+
+template <class TValue>
+std::pair<TValue, TValue> TPiecewiseLinearFunction<TValue>::TLeftToRightTraverser::LeftRightLimitAt(double x)
+{
+ return SegmentAt(x).LeftRightLimitAt(x);
+}
+
+template <class TValue>
+bool operator==(const TPiecewiseLinearFunction<TValue>& lhs, const TPiecewiseLinearFunction<TValue>& rhs)
+{
+ return lhs.Segments() == rhs.Segments();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/piecewise_linear_function-test.h b/yt/yt/library/numeric/piecewise_linear_function-test.h
new file mode 100644
index 0000000000..6132a8f7b8
--- /dev/null
+++ b/yt/yt/library/numeric/piecewise_linear_function-test.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "algorithm_helpers.h"
+#include "binary_search.h"
+#include "piecewise_linear_function.h"
+
+#include <library/cpp/yt/small_containers/compact_vector.h>
+
+#include <vector>
+#include <algorithm>
+#include <random>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+static constexpr int MergeArity = 8;
+using TPivotsVector = TCompactVector<int, MergeArity + 1>;
+
+void SortOrMergeImpl(
+ std::vector<double>* vec,
+ std::vector<double>* buffer,
+ TPivotsVector* mergePivots,
+ TPivotsVector* newPivots);
+
+bool FindMergePivots(const std::vector<double>* vec, TPivotsVector* pivots) noexcept;
+
+void SortOrMerge(std::vector<double>* vec);
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/piecewise_linear_function.cpp b/yt/yt/library/numeric/piecewise_linear_function.cpp
new file mode 100644
index 0000000000..3b175bc235
--- /dev/null
+++ b/yt/yt/library/numeric/piecewise_linear_function.cpp
@@ -0,0 +1,117 @@
+#include "piecewise_linear_function.h"
+#include "piecewise_linear_function-test.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+void SortOrMergeImpl(
+ std::vector<double>* vec,
+ std::vector<double>* buffer,
+ TPivotsVector* mergePivots,
+ TPivotsVector* newPivots)
+{
+ std::vector<double>* buffer1 = vec;
+ std::vector<double>* buffer2 = buffer;
+
+ while (mergePivots->size() > 2) {
+ newPivots->clear();
+
+ for (int startPivot = 0; startPivot < std::ssize(*mergePivots) - 1; startPivot += 2) {
+ newPivots->push_back((*mergePivots)[startPivot]);
+ if (startPivot + 2 < std::ssize(*mergePivots)) {
+ std::merge(
+ /* first1 */ begin(*buffer1) + (*mergePivots)[startPivot],
+ /* last1 */ begin(*buffer1) + (*mergePivots)[startPivot + 1],
+ /* first2 */ begin(*buffer1) + (*mergePivots)[startPivot + 1],
+ /* last2 */ begin(*buffer1) + (*mergePivots)[startPivot + 2],
+ /* result */ begin(*buffer2) + (*mergePivots)[startPivot]);
+ } else {
+ std::copy(
+ /* first */ begin(*buffer1) + (*mergePivots)[startPivot],
+ /* last */ begin(*buffer1) + (*mergePivots)[startPivot + 1],
+ /* result */ begin(*buffer2) + (*mergePivots)[startPivot]);
+ }
+ }
+ newPivots->push_back(vec->size());
+
+ // Swap the pointers.
+ std::swap(buffer1, buffer2);
+ std::swap(mergePivots, newPivots);
+ }
+
+ if (buffer1 != vec) {
+ // Swap the contents (takes only constant number of operations).
+ std::swap(*vec, *buffer);
+ }
+}
+
+bool FindMergePivots(const std::vector<double>* vec, TPivotsVector* pivots) noexcept
+{
+ pivots->clear();
+ pivots->push_back(0);
+
+ for (int i = 1; i < std::ssize(*vec); i++) {
+ if ((*vec)[i] < (*vec)[i - 1]) {
+ if (pivots->size() < MergeArity) {
+ pivots->push_back(i);
+ } else {
+ return false;
+ }
+ }
+ }
+
+ pivots->push_back(vec->size());
+
+ return true;
+}
+
+void SortOrMerge(std::vector<double>* vec)
+{
+ // If |*vec| is small, |std::sort| works fine.
+ if (vec->size() <= 1000) {
+ std::sort(begin(*vec), end(*vec));
+ return;
+ }
+
+ TPivotsVector mergePivots;
+
+ if (!FindMergePivots(vec, &mergePivots)) {
+ // Fall back to |std::sort|.
+ std::sort(begin(*vec), end(*vec));
+ return;
+ }
+
+ if (mergePivots.size() == 2) {
+ // *vec is already sorted.
+ return;
+ }
+
+ std::vector<double> mergeBuffer(vec->size());
+ TPivotsVector pivotsBuffer;
+
+ SortOrMergeImpl(vec, &mergeBuffer, &mergePivots, &pivotsBuffer);
+ Y_VERIFY_DEBUG(std::is_sorted(begin(*vec), end(*vec)));
+}
+
+} // namespace NDetail
+
+// Removes duplicates and the point outside the range [|leftBound|, |rightBound|].
+void ClearAndSortCriticalPoints(std::vector<double>* vec, double leftBound, double rightBound)
+{
+ auto removePredicate = [&] (double point) {
+ return point < leftBound || point > rightBound;
+ };
+
+ vec->erase(std::remove_if(begin(*vec), end(*vec), removePredicate), end(*vec));
+ // NB(antonkikh): This method is often used where simple merge can be used.
+ // To avoid complicating the callers code, we make dynamic optimization here.
+ NDetail::SortOrMerge(vec);
+ vec->erase(std::unique(begin(*vec), end(*vec)), end(*vec));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/piecewise_linear_function.h b/yt/yt/library/numeric/piecewise_linear_function.h
new file mode 100644
index 0000000000..254f4de0d7
--- /dev/null
+++ b/yt/yt/library/numeric/piecewise_linear_function.h
@@ -0,0 +1,447 @@
+#pragma once
+
+#include "algorithm_helpers.h"
+#include "binary_search.h"
+#include "vector_format.h"
+
+#include <library/cpp/testing/gtest/friend.h>
+
+#include <util/system/yassert.h>
+#include <util/generic/algorithm.h>
+#include <util/generic/yexception.h>
+
+#include <algorithm>
+#include <optional>
+#include <random>
+#include <vector>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static_assert(std::numeric_limits<double>::is_iec559, "We assume IEEE 754 floating point implementation");
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+class TPiecewiseSegment
+{
+public:
+ using TSegment = TPiecewiseSegment<TValue>;
+
+public:
+ TPiecewiseSegment(std::pair<double, TValue> leftPoint, std::pair<double, TValue> rightPoint);
+
+ double LeftBound() const;
+
+ double RightBound() const;
+
+ const TValue& LeftValue() const;
+
+ const TValue& RightValue() const;
+
+ bool IsDefinedAt(double x) const;
+
+ bool IsDefinedOn(double left, double right) const;
+
+ TValue LeftLimitAt(double x) const;
+
+ TValue RightLimitAt(double x) const;
+
+ std::pair<TValue, TValue> LeftRightLimitAt(double x) const;
+
+ TValue ValueAt(double x) const;
+
+ // Returns true when |LeftBound() == RightBound() && LeftValue() != RightValue()|.
+ bool IsVertical() const;
+
+ // Returns true when |LeftBound() != RightBound() && LeftValue() == RightValue()|.
+ bool IsHorizontal() const;
+
+ // Returns true when |LeftBound() == RightBound() && LeftValue() == RightValue()|.
+ bool IsPoint() const;
+
+ // Returns true when |LeftBound() != RightBound() && LeftValue() != RightValue()|.
+ bool IsTilted() const;
+
+ TPiecewiseSegment Transpose() const;
+
+ // See: |TPiecewiseLinearFunction<TValue>::ScaleArgument|.
+ TPiecewiseSegment ScaleArgument(double scale) const;
+
+ TPiecewiseSegment Shift(double deltaBound, const TValue& deltaValue) const;
+
+public:
+ double LeftBound_;
+ TValue LeftValue_;
+ double RightBound_;
+ TValue RightValue_;
+
+private:
+ TValue InterpolateAt(double x) const;
+ TValue InterpolateNormalized(double t) const;
+
+ FRIEND_TEST(TPiecewiseLinearFunctionTest, TestInterpolationProperties);
+};
+
+template <class TValue>
+bool operator ==(const TPiecewiseSegment<TValue>& lhs, const TPiecewiseSegment<TValue>& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+class TPiecewiseLinearFunction;
+
+template <class TValue>
+void ExtractCriticalPointsFromFunction(const TPiecewiseLinearFunction<TValue>& func, std::vector<double>* out);
+
+template <class TValue>
+void ExtractCriticalPointsFromFunctionWithValues(
+ const TPiecewiseLinearFunction<TValue>& func,
+ std::vector<std::pair<double, TValue>>* out);
+
+template <class TValue>
+void ExtractDiscontinuityPointsFromFunction(
+ const TPiecewiseLinearFunction<TValue>& func,
+ std::vector<double>* out);
+
+template <class TValue>
+void PushSegmentImpl(std::vector<TPiecewiseSegment<TValue>>* vec, TPiecewiseSegment<TValue> segment);
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> PointwiseMin(
+ const TPiecewiseLinearFunction<TValue>& lhs,
+ const TPiecewiseLinearFunction<TValue>& rhs);
+
+template <class TValue>
+TPiecewiseLinearFunction<TValue> PointwiseMin(const std::vector<TPiecewiseLinearFunction<TValue>>& funcs);
+
+void ClearAndSortCriticalPoints(std::vector<double>* vec, double leftBound, double rightBound);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class>
+struct TIsPiecewiseLinearFunction : std::false_type {};
+
+template <class TValue>
+struct TIsPiecewiseLinearFunction<TPiecewiseLinearFunction<TValue>> : std::true_type {};
+
+template <class TValue>
+constexpr inline bool IsPiecewiseLinearFunction = TIsPiecewiseLinearFunction<TValue>::value;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+class TPiecewiseLinearFunctionBuilder
+{
+public:
+ using TPoint = std::pair<double, TValue>;
+ using TSegment = TPiecewiseSegment<TValue>;
+ using TFunction = TPiecewiseLinearFunction<TValue>;
+
+public:
+ TPiecewiseLinearFunctionBuilder() = default;
+
+ void AddPoint(TPoint point);
+
+ void PushSegment(const TSegment& segment);
+
+ void PushSegment(std::pair<double, TValue> leftPoint, std::pair<double, TValue> rightPoint);
+
+ TFunction Finish();
+
+private:
+ std::optional<TPoint> RightPoint_;
+ std::vector<TSegment> Segments_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Represents a (potentially discontinuous) piecewise linear function defined on a closed interval of real numbers.
+// The function can be sampled using methods: |ValueAt(x)|, |LeftLimitAt(x)|, |RightLimitAt(x)|, |LeftRightLimitAt(x)|.
+// New functions can be created using |TPiecewiseLinearFunctionBuilder| class.
+// Currently only left-continuous functions are supported, i.e. for all functions |ValueAt(x) == LeftLimitAt(x)|.
+//
+// The results of all sampling functions preserve monotonicity.
+// I.e. if the function is non-decreasing, the following properties are satisfied for all valid |x|, |x1| and |x2|:
+// (1) Left-hand limit at a point is not greater than the right-hand limit.
+// I.e. |LeftLimitAt(x) <= RightLimitAt(x)|.
+// (2) |Get|, |LeftLimitAt| and |RightLimitAt| are non-decreasing.
+// I.e. if |x1 <= x2|, then |ValueAt(x1) <= ValueAt(x2)|, |LeftLimitAt(x1) <= LeftLimitAt(x2)| and |RightLimitAt(x1) <= RightLimitAt(x2)|.
+// (3) if |x1 < x2|, then |RightLimitAt(x1) <= LeftLimitAt(x2)|.
+//
+// A function is represented by its plot.
+// Discontinuity points are represented by vertical segments in the plot.
+template <class TValue>
+class TPiecewiseLinearFunction
+{
+public:
+ using TValueType = TValue;
+ using TSelf = TPiecewiseLinearFunction<TValue>;
+ using TBuilder = TPiecewiseLinearFunctionBuilder<TValue>;
+ using TSegment = TPiecewiseSegment<TValue>;
+
+public:
+ explicit TPiecewiseLinearFunction(std::vector<TSegment> segments);
+
+ TPiecewiseLinearFunction(const TPiecewiseLinearFunction& other) = default;
+ TPiecewiseLinearFunction(TPiecewiseLinearFunction&& other) noexcept = default;
+
+ TPiecewiseLinearFunction& operator=(const TPiecewiseLinearFunction& other) = default;
+ TPiecewiseLinearFunction& operator=(TPiecewiseLinearFunction&& other) noexcept = default;
+
+ // Creates a new piecewise linear function from given sample function and a set of critical points.
+ // It is guaranteed that |sample| will be called in ascending order of arguments.
+ // The total complexity is linear if |criticalPoints| is sorted and the complexity of |sample| is constant.
+ template <class TSampleFunction>
+ static TSelf Create(
+ TSampleFunction&& sample,
+ double leftBound,
+ double rightBound,
+ std::vector<double> criticalPoints);
+
+ static TSelf Linear(double leftBound, TValue leftValue, double rightBound, TValue rightValue);
+
+ static TSelf Constant(double leftBound, double rightBound, TValue value);
+
+ // Returns left-hand limit of the function at point |x|.
+ TValue LeftLimitAt(double x) const;
+
+ // Returns right-hand limit of the function at point |x|.
+ TValue RightLimitAt(double x) const;
+
+ // Returns both left-hand and right-hand limit of the function at point |x|.
+ // They might differ if |x| is a discontinuity point.
+ std::pair<TValue, TValue> LeftRightLimitAt(double x) const;
+
+ // Returns the value of the function at point |x|.
+ // It must be equal to either left-hand limit or right-hand limit.
+ TValue ValueAt(double x) const;
+
+ // Returns a const reference to the vector of all segments of the function plot.
+ const std::vector<TSegment>& Segments() const;
+
+ // Returns minimal valid argument for the function.
+ double LeftFunctionBound() const;
+
+ // Returns maximal valid argument for the function.
+ double RightFunctionBound() const;
+
+ // Returns minimal valid argument for the function.
+ TValue LeftFunctionValue() const;
+
+ // Returns the function value at |RightFunctionBound()|.
+ // This function is not defined if there is a discontinuity point.
+ TValue RightFunctionValue() const;
+
+ // Return true iff the function is defined at point |x|.
+ bool IsDefinedAt(double x) const;
+
+ // Returns true iff the function has no discontinuity points.
+ bool IsContinuous() const;
+
+ // Returns true iff the function is nondecreasing.
+ // I.e. for all |x1|, |x2|: if |x1 <= x2|, then |ValueAt(x1) <= ValueAt(x2)|.
+ // NB: This function is only defined when |operator<=| is defined for |TValue|.
+ bool IsNondecreasing() const;
+
+ // See: |Trim|.
+ bool IsTrimmed() const;
+
+ // See: |TrimLeft|.
+ bool IsTrimmedLeft() const;
+
+ // See: |TrimRight|.
+ bool IsTrimmedRight() const;
+
+ // Returns the leftmost segment that contains |x|.
+ // |LeftSegmentAt(x).LeftLimitAt(x)| is guaranteed to be equal to |LeftLimitAt(x)|.
+ const TSegment& LeftSegmentAt(double x, int* segmentIndex = nullptr) const;
+
+ // Returns the rightmost segment that contains |x|.
+ // |RightSegmentAt(x).RightLimitAt(x)| is guaranteed to be equal to |RightLimitAt(x)|.
+ const TSegment& RightSegmentAt(double x, int* segmentIndex = nullptr) const;
+
+ // Returns a segment that has both left limit and right limit for |x|.
+ // I.e. |SegmentAt(x).LeftRightLimitAt(x)| is guaranteed to be equal to |LeftRightLimitAt(x)|.
+ // It might differ from both |LeftSegmentAt| and |RightSegmentAt| when |x| is a discontinuity point.
+ // The returned segment is vertical when |x| is a discontinuity point.
+ const TSegment& SegmentAt(double x, int* segmentIndex = nullptr) const;
+
+ // Adds segment to the right of the function plot.
+ // The left point of |segment| must be equal to the right point of
+ // the last already added segment (i.e. |Segments().back()|.
+ void PushSegment(const TSegment& segment);
+
+ // Transposition can be used to obtain the inverse function.
+ // Returns a reference to |*this| for chaining.
+ TSelf& TransposeInplace();
+
+ // See: |TransposeInplace|.
+ [[nodiscard]] TSelf Transpose() const &;
+
+ // See: |TransposeInplace|.
+ // Rvalue version is used for efficient chaining.
+ [[nodiscard]] TSelf Transpose() &&;
+
+ // Removes a discontinuity point at the left bound if there is one.
+ // Returns a reference to |*this| for chaining.
+ TSelf& TrimLeftInplace();
+
+ // See: |TrimLeftInplace|.
+ [[nodiscard]] TSelf TrimLeft() const &;
+
+ // See: |TrimLeftInplace|.
+ // Rvalue version is used for efficient chaining.
+ [[nodiscard]] TSelf TrimLeft() &&;
+
+ // Removes a discontinuity point at the right bound if there is one.
+ // Returns a reference to |*this| for chaining.
+ TSelf& TrimRightInplace();
+
+ // See: |TrimRightInplace|.
+ [[nodiscard]] TSelf TrimRight() const &;
+
+ // See: |TrimRightInplace|.
+ // Rvalue version is used for efficient chaining.
+ [[nodiscard]] TSelf TrimRight() &&;
+
+ // Removes non-sensible discontinuity points at the bounds of the functions.
+ // Returns a reference to |*this| for chaining.
+ TSelf& TrimInplace();
+
+ // See: |TrimInplace|.
+ [[nodiscard]] TSelf Trim() const &;
+
+ // See: |TrimInplace|.
+ // Rvalue version is used for efficient chaining.
+ [[nodiscard]] TSelf Trim() &&;
+
+ // Returns new function |g| s.t. |g.ValueAt(x) == this->ValueAt(x * scale)|.
+ // NOTE: This means that the inner representation of the function (as well as its domain) will be scaled by (1.0 / |scale|).
+ [[nodiscard]] TSelf ScaleArgument(double scale) const;
+
+ [[nodiscard]] TSelf Shift(double deltaArgument, const TValue& deltaValue = TValue{}) const;
+
+
+ // Narrows the domain of the function to the segment [|newLeftBound|, |newRightBound|].
+ // Returns a reference to |*this| for chaining.
+ TSelf& NarrowInplace(double newLeftBound, double newRightBound);
+
+ // See: |NarrowInplace|.
+ [[nodiscard]] TSelf Narrow(double newLeftBound, double newRightBound) const;
+
+ // Extends the domain of the function to the segment [|newLeftBound|, |newRightBound|].
+ // The function value is |newLeftValue| on the segment [|newLeftBound|, |LeftFunctionBound()|],
+ // and |newRightValue| on the segment [|RightFunctionBound()|, |newRightBound|].
+ // Returns a reference to |*this| for chaining.
+ TSelf& ExtendInplace(
+ double newLeftBound,
+ const TValue& newLeftValue,
+ double newRightBound,
+ const TValue& newRightValue);
+
+ // See: |ExtendInplace|.
+ [[nodiscard]] TSelf Extend(
+ double newLeftBound,
+ const TValue& newLeftValue,
+ double newRightBound,
+ const TValue& newRightValue) const;
+
+ // Equivalent to |Extend(newLeftBound, Segments().front().LeftValue(), newRightBound, Segments_.back().RightValue())|.
+ [[nodiscard]] TSelf Extend(double newLeftBound, double newRightBound) const;
+
+ // Equivalent to |Extend(LeftFunctionBound(), LeftFunctionValue(), newRightBound, newRightValue)|
+ [[nodiscard]] TSelf ExtendRight(double newRightBound, const TValue& newRightValue) const;
+
+ // Equivalent to |ExtendRight(newRightBound, Segments().back().RightValue())|
+ [[nodiscard]] TSelf ExtendRight(double newRightBound) const;
+
+ // Returns function |g| such that |g.ValueAt(x) == this->ValueAt(x) + other.ValueAt(x)| for all |x| in the intersection of
+ // the domains of |*this| and |other|.
+ [[nodiscard]] TSelf operator+(const TSelf& other) const;
+
+ TSelf& operator+=(const TSelf& other);
+
+ // Returns function |h| such that |h.ValueAt(x) == sum(funcs[i].ValueAt(x))| for all |x| in the intersections of
+ // the domains of the functions in |funcs|.
+ [[nodiscard]] static TSelf Sum(const std::vector<TPiecewiseLinearFunction<TValue>>& funcs);
+
+ // Returns left composition of function |f=*this| and |g=other|,
+ // i.e. a function |h| such that |h.ValueAt(x) = f.ValueAt(g.ValueAt(x))| for all |x| in the domain of |g|.
+ // |other| must be non-decreasing.
+ // If |*this| is non-decreasing, the result is guaranteed to be non-decreasing.
+ template <class TOther>
+ [[nodiscard]] TSelf Compose(const TOther& other) const;
+
+public:
+ // Helper class that can be used for efficient amortized access to |TPiecewiseLinearFunction|.
+ // The behaviour is undefined if the function changes during traversal or if the requests are not monotonous.
+ class TLeftToRightTraverser
+ {
+ public:
+ TLeftToRightTraverser(const TPiecewiseLinearFunction& function, int segmentIndex = 0);
+
+ TLeftToRightTraverser(const TLeftToRightTraverser& other) = default;
+
+ // See: |TPiecewiseLinearFunction::LeftSegmentAt|.
+ // If |y > x|, |LeftSegmentAt(x)| cannot be called after |LeftSegmentAt(y)|.
+ // If |y >= x|, |LeftSegmentAt(x)| cannot be called after |SegmentAt(y)| or |RightSegmentAt(y)|.
+ const TSegment& LeftSegmentAt(double x);
+
+ // See: |TPiecewiseLinearFunction::RightSegmentAt|.
+ // If |y > x|, |RightSegmentAt(x)| cannot be called after |LeftSegmentAt(y)|, |SegmentAt(y)|, or |RightSegmentAt(y)|.
+ const TSegment& RightSegmentAt(double x);
+
+ // See: |TPiecewiseLinearFunction::SegmentAt|.
+ // If |y > x|, |SegmentAt(x)| cannot be called after |LeftSegmentAt(y)| or |SegmentAt(x)|.
+ // If |y >= x|, |LeftSegmentAt(x)| cannot be called after |RightSegmentAt(x)|.
+ const TSegment& SegmentAt(double x);
+
+ // See: |TPiecewiseLinearFunction::ValueAt|.
+ // Since the function is assumed to be left-continuous, equivalent to |LeftLimitAt(x)|.
+ TValue ValueAt(double x);
+
+ // See: |TPiecewiseLinearFunction::LeftLimitAt|.
+ // Equivalent to |LeftSegmentAt(x).LeftLimitAt(x)|.
+ TValue LeftLimitAt(double x);
+
+ // See: |TPiecewiseLinearFunction::RightLimitAt|.
+ // Equivalent to |RightSegmentAt(x).RightLimitAt(x)|.
+ TValue RightLimitAt(double x);
+
+ // See: |TPiecewiseLinearFunction::LeftRightLimitAt|.
+ // Equivalent to |SegmentAt(x).LeftRightLimitAt(x)|.
+ std::pair<TValue, TValue> LeftRightLimitAt(double x);
+
+ private:
+ const TPiecewiseLinearFunction* Function_;
+
+ typename std::vector<TPiecewiseSegment<TValue>>::const_iterator Cur_;
+
+ typename std::vector<TPiecewiseSegment<TValue>>::const_iterator End_;
+ };
+
+ TLeftToRightTraverser GetLeftToRightTraverser(int segmentIndex = 0) const;
+
+private:
+ std::vector<TSegment> Segments_;
+
+ template <class TOperation>
+ static TSelf ApplyBinaryOperation(
+ const TSelf& lhs,
+ const TSelf& rhs,
+ const TOperation& operation);
+};
+
+template <class TValue>
+bool operator==(const TPiecewiseLinearFunction<TValue>& lhs, const TPiecewiseLinearFunction<TValue>& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define PIECEWISE_LINEAR_FUNCTION_INL_H_
+#include "piecewise_linear_function-inl.h"
+#undef PIECEWISE_LINEAR_FUNCTION_INL_H_
+
diff --git a/yt/yt/library/numeric/serialize/double_array.h b/yt/yt/library/numeric/serialize/double_array.h
new file mode 100644
index 0000000000..a700b4d0ab
--- /dev/null
+++ b/yt/yt/library/numeric/serialize/double_array.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <yt/yt/library/numeric/double_array.h>
+
+#include <library/cpp/yt/string/format.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TDerived>
+struct TValueFormatter<TDerived, std::enable_if_t<IsDoubleArray<TDerived>>>
+{
+ static void Do(TStringBuilderBase* builder, const TDerived& vec, TStringBuf format)
+ {
+ builder->AppendChar('[');
+ FormatValue(builder, vec[0], format);
+ for (size_t i = 1; i < TDerived::Size; i++) {
+ builder->AppendChar(' ');
+ FormatValue(builder, vec[i], format);
+ }
+ builder->AppendChar(']');
+ }
+};
+
+// TODO(ignat)
+// template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+// IOutputStream& operator<<(IOutputStream& os, const TDerived& vec)
+// {
+// return os << Format("%v", vec);
+// }
+
+template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>>
+std::ostream& operator<<(std::ostream& os, const TDerived& vec)
+{
+ return os << Format("%v", vec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/serialize/unittests/double_array_ut.cpp b/yt/yt/library/numeric/serialize/unittests/double_array_ut.cpp
new file mode 100644
index 0000000000..52440b80c9
--- /dev/null
+++ b/yt/yt/library/numeric/serialize/unittests/double_array_ut.cpp
@@ -0,0 +1,43 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <yt/yt/library/numeric/serialize/double_array.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDoubleArrayTest
+ : public testing::Test
+{
+protected:
+ TDoubleArrayTest() = default;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TDoubleArrayTest, TestToString)
+{
+ TDoubleArray<5> arr = {0, 1, 2, 3, 4};
+ TString result = "[0.000000 1.000000 2.000000 3.000000 4.000000]";
+ TString resultSmallPrecision = "[0.00 1.00 2.00 3.00 4.00]";
+ TString resultHighPrecision = "[0.0000000000 1.0000000000 2.0000000000 3.0000000000 4.0000000000]";
+
+ // TODO(ignat)
+ // EXPECT_EQ(result, ToString(arr));
+ EXPECT_EQ(result, Format("%v", arr));
+ EXPECT_EQ(resultSmallPrecision, Format("%.2v", arr));
+ EXPECT_EQ(resultHighPrecision, Format("%.10v", arr));
+
+ std::stringstream ss1;
+ ss1 << arr;
+ EXPECT_EQ(result, TString(ss1.str()));
+
+ // TODO(ignat)
+ // TStringStream ss2;
+ // ss2 << arr;
+ // EXPECT_EQ(result, ss2.Str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/serialize/unittests/ya.make b/yt/yt/library/numeric/serialize/unittests/ya.make
new file mode 100644
index 0000000000..efcc03c3d2
--- /dev/null
+++ b/yt/yt/library/numeric/serialize/unittests/ya.make
@@ -0,0 +1,20 @@
+GTEST(unittester-yt-library-numeric)
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ double_array_ut.cpp
+)
+
+ADDINCL(
+ yt/yt/library/numeric/serialize
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ yt/yt/library/numeric/serialize
+ library/cpp/testing/gtest
+)
+
+END()
diff --git a/yt/yt/library/numeric/serialize/ya.make b/yt/yt/library/numeric/serialize/ya.make
new file mode 100644
index 0000000000..f0eace3178
--- /dev/null
+++ b/yt/yt/library/numeric/serialize/ya.make
@@ -0,0 +1,15 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ yt/yt/library/numeric
+ yt/yt/core
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ unittests
+)
+
diff --git a/yt/yt/library/numeric/unittests/binary_search_ut.cpp b/yt/yt/library/numeric/unittests/binary_search_ut.cpp
new file mode 100644
index 0000000000..f433566174
--- /dev/null
+++ b/yt/yt/library/numeric/unittests/binary_search_ut.cpp
@@ -0,0 +1,332 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <yt/yt/library/numeric/binary_search.h>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TBinarySearchTest
+ : public ::testing::Test
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TBinarySearchTest, TestDoubleToBitset)
+{
+ struct TTestCase
+ {
+ TString name;
+ double value;
+ uint64_t bitset;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* name */ "positive_zero",
+ /* value */ 0.0,
+ /* bitset */ 0x8000000000000000
+ },
+ {
+ /* name */ "negative_zero",
+ /* value */ -0.0,
+ /* bitset */ 0x7FFFFFFFFFFFFFFF
+ },
+ {
+ /* name */ "positive_infinity",
+ /* value */ std::numeric_limits<double>::infinity(),
+ /* bitset */ 0xFFF0000000000000
+ },
+ {
+ /* name */ "negative_infinity",
+ /* value */ -std::numeric_limits<double>::infinity(),
+ /* bitset */ 0x000FFFFFFFFFFFFF
+ },
+ {
+ /* name */ "lowest_finite_value",
+ /* value */ std::numeric_limits<double>::lowest(),
+ /* bitset */ 0x0010000000000000
+ },
+ {
+ /* name */ "max_finite_value",
+ /* value */ std::numeric_limits<double>::max(),
+ /* bitset */ 0xFFEFFFFFFFFFFFFF
+ },
+ {
+ /* name */ "PI",
+ /* value */ 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899863,
+ /* bitset */ NDetail::DoubleToBitset(3.14159265358979323846264338327950288419716939937510582097494459230781640628620899863)
+ },
+ {
+ /* name */ "random_bits",
+ /* value */ NDetail::BitsetToDouble(0xD9544A64AF4DB207),
+ /* bitset */ 0xD9544A64AF4DB207
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ EXPECT_EQ(testCase.bitset, NDetail::DoubleToBitset(testCase.value)) << "In the test case " << testCase.name;
+
+ EXPECT_EQ(testCase.value, NDetail::BitsetToDouble(testCase.bitset)) << "In the test case " << testCase.name;
+ }
+}
+
+TEST_F(TBinarySearchTest, TestFloatingPointLowerBound)
+{
+ struct TTestCase
+ {
+ TString name;
+ double lo;
+ double hi;
+ std::function<bool(double)> predicate;
+ std::optional<double> expectedResult;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* name */ "sqrt2",
+ /* lo */ 0,
+ /* hi */ 2,
+ /* predicate */ [] (double x) { return x * x >= 2; },
+ /* expectedResult */ std::nullopt
+ },
+ {
+ /* name */ "sqrt0.1",
+ /* lo */ 0,
+ /* hi */ 1,
+ /* predicate */ [] (double x) { return x * x >= 0.1; },
+ /* expectedResult */ std::nullopt
+ },
+ {
+ /* name */ "positive_infinity",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x >= std::numeric_limits<double>::infinity(); },
+ /* expectedResult */ std::numeric_limits<double>::infinity()
+ },
+ {
+ /* name */ "negative_infinity",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x >= -std::numeric_limits<double>::infinity(); },
+ /* expectedResult */ -std::numeric_limits<double>::infinity()
+ },
+ {
+ /* name */ "double_max",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x >= std::numeric_limits<double>::max(); },
+ /* expectedResult */ std::numeric_limits<double>::max()
+ },
+ {
+ /* name */ "before_double_max",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x >= std::nextafter(std::numeric_limits<double>::max(), 0); },
+ /* expectedResult */ std::nextafter(std::numeric_limits<double>::max(), 0)
+ },
+ {
+ /* name */ "minus_pi",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x >= -3.14159265358979323846264338327950288419716939937510582097494459230781640628620899863; },
+ /* expectedResult */ -3.14159265358979323846264338327950288419716939937510582097494459230781640628620899863
+ },
+ {
+ /* name */ "minus_zero",
+ /* lo */ std::numeric_limits<double>::lowest(),
+ /* hi */ std::numeric_limits<double>::max(),
+ /* predicate */ [] (double x) { return x >= 0; }, // Notice that |-0.0 >= 0|.
+ /* expectedResult */ -0.0
+ },
+ {
+ /* name */ "plus_zero",
+ /* lo */ std::numeric_limits<double>::lowest(),
+ /* hi */ std::numeric_limits<double>::max(),
+ /* predicate */ [] (double x) { return x >= 0 && !signbit(x); },
+ /* expectedResult */ 0.0
+ },
+ {
+ /* name */ "predicate_always_true",
+ /* lo */ -17,
+ /* hi */ 1000,
+ /* predicate */ [] (double /*x*/) { return true; },
+ /* expectedResult */ -17
+ },
+ {
+ /* name */ "min_positive_number_1",
+ /* lo */ -17,
+ /* hi */ 1000,
+ /* predicate */ [] (double x) { return x >= std::numeric_limits<double>::min(); },
+ /* expectedResult */ std::numeric_limits<double>::min()
+ },
+ {
+ /* name */ "min_positive_number_2",
+ /* lo */ std::numeric_limits<double>::lowest(),
+ /* hi */ std::numeric_limits<double>::max(),
+ /* predicate */ [] (double x) { return x >= std::numeric_limits<double>::min(); },
+ /* expectedResult */ std::numeric_limits<double>::min()
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ int callCount = 0;
+ const auto countingPredicate = [&callCount, &testCase] (double x) {
+ callCount++;
+ return testCase.predicate(x);
+ };
+
+ double result = FloatingPointLowerBound(testCase.lo, testCase.hi, countingPredicate);
+ EXPECT_TRUE(testCase.predicate(result))
+ << "Invalid result in the test case " << testCase.name;
+ if (testCase.expectedResult) {
+ EXPECT_EQ(result, *testCase.expectedResult)
+ << "The result differs from expected in the test case " << testCase.name;
+ if (result == *testCase.expectedResult) {
+ // To distinguish between 0.0 and -0.0, check the sign bit explicitly.
+ EXPECT_EQ(std::signbit(result), std::signbit(*testCase.expectedResult))
+ << "Signbit differs from expected in the test case " << testCase.name << ". "
+ << "Perhaps, invalid handling of -0.0 and 0.0";
+ }
+ }
+ if (result > testCase.lo) {
+ EXPECT_FALSE(testCase.predicate(std::nextafter(result, testCase.lo)))
+ << "The result is not minimal in the test case " << testCase.name;
+ }
+ EXPECT_LE(callCount, 70)
+ << "Too many calls to the predicate in the test case " << testCase.name;
+ }
+}
+
+TEST_F(TBinarySearchTest, TestFloatingPointInverseLowerBound)
+{
+ struct TTestCase
+ {
+ TString name;
+ double lo;
+ double hi;
+ std::function<bool(double)> predicate;
+ std::optional<double> expectedResult;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* name */ "sqrt2",
+ /* lo */ 0,
+ /* hi */ 2,
+ /* predicate */ [] (double x) { return x * x <= 2; },
+ /* expectedResult */ std::nullopt
+ },
+ {
+ /* name */ "sqrt0.1",
+ /* lo */ 0,
+ /* hi */ 1,
+ /* predicate */ [] (double x) { return x * x <= 0.1; },
+ /* expectedResult */ std::nullopt
+ },
+ {
+ /* name */ "positive_infinity",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x <= std::numeric_limits<double>::infinity(); },
+ /* expectedResult */ std::numeric_limits<double>::infinity()
+ },
+ {
+ /* name */ "negative_infinity",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x <= -std::numeric_limits<double>::infinity(); },
+ /* expectedResult */ -std::numeric_limits<double>::infinity()
+ },
+ {
+ /* name */ "double_max",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x <= std::numeric_limits<double>::max(); },
+ /* expectedResult */ std::numeric_limits<double>::max()
+ },
+ {
+ /* name */ "before_double_max",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x <= std::nextafter(std::numeric_limits<double>::max(), 0); },
+ /* expectedResult */ std::nextafter(std::numeric_limits<double>::max(), 0)
+ },
+ {
+ /* name */ "minus_pi",
+ /* lo */ -std::numeric_limits<double>::infinity(),
+ /* hi */ std::numeric_limits<double>::infinity(),
+ /* predicate */ [] (double x) { return x <= -3.14159265358979323846264338327950288419716939937510582097494459230781640628620899863; },
+ /* expectedResult */ -3.14159265358979323846264338327950288419716939937510582097494459230781640628620899863
+ },
+ {
+ /* name */ "minus_zero",
+ /* lo */ std::numeric_limits<double>::lowest(),
+ /* hi */ std::numeric_limits<double>::max(),
+ /* predicate */ [] (double x) { return x <= 0 && signbit(x); },
+ /* expectedResult */ -0.0
+ },
+ {
+ /* name */ "plus_zero",
+ /* lo */ std::numeric_limits<double>::lowest(),
+ /* hi */ std::numeric_limits<double>::max(),
+ /* predicate */ [] (double x) { return x <= 0; },
+ /* expectedResult */ 0.0
+ },
+ {
+ /* name */ "predicate_always_true",
+ /* lo */ -17,
+ /* hi */ 1000,
+ /* predicate */ [] (double /*x*/) { return true; },
+ /* expectedResult */ 1000
+ },
+ {
+ /* name */ "min_positive_number_1",
+ /* lo */ -17,
+ /* hi */ 1000,
+ /* predicate */ [] (double x) { return x <= std::numeric_limits<double>::min(); },
+ /* expectedResult */ std::numeric_limits<double>::min()
+ },
+ {
+ /* name */ "min_positive_number_2",
+ /* lo */ std::numeric_limits<double>::lowest(),
+ /* hi */ std::numeric_limits<double>::max(),
+ /* predicate */ [] (double x) { return x <= std::numeric_limits<double>::min(); },
+ /* expectedResult */ std::numeric_limits<double>::min()
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ int callCount = 0;
+ const auto countingPredicate = [&callCount, &testCase] (double x) {
+ callCount++;
+ return testCase.predicate(x);
+ };
+
+ double result = FloatingPointInverseLowerBound(testCase.lo, testCase.hi, countingPredicate);
+ EXPECT_TRUE(testCase.predicate(result))
+ << "Invalid result in the test case " << testCase.name;
+ if (testCase.expectedResult) {
+ EXPECT_EQ(result, *testCase.expectedResult)
+ << "The result differs from expected in the test case " << testCase.name;
+ if (result == *testCase.expectedResult) {
+ // To distinguish between 0.0 and -0.0, check the sign bit explicitly.
+ EXPECT_EQ(std::signbit(result), std::signbit(*testCase.expectedResult))
+ << "Signbit differs from expected in the test case " << testCase.name << ". "
+ << "Perhaps, invalid handling of -0.0 and 0.0";
+ }
+ }
+ if (result < testCase.hi) {
+ EXPECT_FALSE(testCase.predicate(std::nextafter(result, testCase.hi)))
+ << "The result is not maximal in the test case " << testCase.name;
+ }
+ EXPECT_LE(callCount, 70)
+ << "Too many calls to the predicate in the test case " << testCase.name;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/yt/yt/library/numeric/unittests/double_array_ut.cpp b/yt/yt/library/numeric/unittests/double_array_ut.cpp
new file mode 100644
index 0000000000..f7c927adbe
--- /dev/null
+++ b/yt/yt/library/numeric/unittests/double_array_ut.cpp
@@ -0,0 +1,94 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <yt/yt/library/numeric/double_array.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TDoubleArrayTest
+ : public testing::Test
+{
+protected:
+ TDoubleArrayTest() = default;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TDoubleArrayTest, TestOperatorAt)
+{
+ TDoubleArray<5> arr = {7, 3, 12, 43, -3.1};
+ EXPECT_EQ(7, arr[0]);
+ EXPECT_EQ(3, arr[1]);
+ EXPECT_EQ(12, arr[2]);
+ EXPECT_EQ(43, arr[3]);
+ EXPECT_EQ(-3.1, arr[4]);
+
+ arr[2] = 1.7;
+ EXPECT_EQ(1.7, arr[2]);
+}
+
+TEST_F(TDoubleArrayTest, TestExample)
+{
+ TDoubleArray<4> vec1 = {1, 2, 3, 4};
+ EXPECT_TRUE(vec1[3] == 4);
+ EXPECT_TRUE(TDoubleArray<4>::All(vec1, [] (double x) { return x > 0; }));
+ EXPECT_TRUE(MinComponent(vec1) == 1);
+
+ TDoubleArray<4> vec2 = {4, 3, 2, 1};
+ EXPECT_TRUE(vec1 + vec2 == TDoubleArray<4>::FromDouble(5));
+
+ // |vec1 * vec1| wouldn't work because multiplication is not defined for mathematical vectors.
+ auto vec1Square = TDoubleArray<4>::Apply(vec1, [] (double x) { return x * x; });
+ EXPECT_TRUE(TDoubleArray<4>::All(vec1, vec1Square, [] (double x, double y) { return y == x * x; }));
+}
+
+TEST_F(TDoubleArrayTest, TestPlusMinus)
+{
+ struct TTestCase
+ {
+ TString Name;
+ TDoubleArray<4> Arg1;
+ TDoubleArray<4> Arg2;
+ TDoubleArray<4> ExpectedSum;
+ TDoubleArray<4> ExpectedDiff;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "Zero_&_Zero",
+ /* Arg1 */ TDoubleArray<4>::Zero(),
+ /* Arg2 */ TDoubleArray<4>::Zero(),
+ /* ExpectedSum */ TDoubleArray<4>::Zero(),
+ /* ExpectedDiff */ TDoubleArray<4>::Zero()
+ },
+ {
+ /* Name */ "Iota_&_Zero",
+ /* Arg1 */ {1, 2, 3, 4},
+ /* Arg2 */ {0, 0, 0, 0},
+ /* ExpectedSum */ {1, 2, 3, 4},
+ /* ExpectedDiff */ {1, 2, 3, 4}
+ },
+ {
+ /* Name */ "Iota_&_Random",
+ /* Arg1 */ {17, 4.1, 14.23, 14},
+ /* Arg2 */ {1, 2, 3, 4},
+ /* ExpectedSum */ {18, 6.1, 17.23, 18},
+ /* ExpectedDiff */ {16, 2.1, 11.23, 10}
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = "In the test case " + testCase.Name;
+
+ EXPECT_TRUE(TDoubleArray<4>::Near(testCase.Arg1 + testCase.Arg2, testCase.ExpectedSum, 1e-9)) << testCaseMsg;
+ EXPECT_TRUE(TDoubleArray<4>::Near(testCase.Arg2 + testCase.Arg1, testCase.ExpectedSum, 1e-9)) << testCaseMsg;
+
+ EXPECT_TRUE(TDoubleArray<4>::Near(testCase.Arg1 - testCase.Arg2, testCase.ExpectedDiff, 1e-9)) << testCaseMsg;
+ EXPECT_TRUE(TDoubleArray<4>::Near(testCase.Arg2 - testCase.Arg1, -testCase.ExpectedDiff, 1e-9)) << testCaseMsg;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/unittests/piecewise_linear_function_ut.cpp b/yt/yt/library/numeric/unittests/piecewise_linear_function_ut.cpp
new file mode 100644
index 0000000000..a77a3f7600
--- /dev/null
+++ b/yt/yt/library/numeric/unittests/piecewise_linear_function_ut.cpp
@@ -0,0 +1,1370 @@
+#include <yt/yt/library/numeric/double_array_format.h>
+#include <yt/yt/library/numeric/piecewise_linear_function.h>
+#include <yt/yt/library/numeric/piecewise_linear_function-test.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <util/string/cast.h>
+
+
+namespace NYT {
+
+using ::ToString;
+using NDetail::ToString;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TPiecewiseLinearFunctionTest
+ : public testing::Test
+{
+protected:
+ struct TSample {
+ double Argument;
+ double ExpectedLeftLimit;
+ double ExpectedRightLimit;
+ };
+
+ static std::vector<double> AddNeighbourPointsAndSort(
+ const std::vector<double>& points,
+ double leftBound,
+ double rightBound)
+ {
+ std::vector<double> result{};
+
+ for (double point : points) {
+ result.push_back(point);
+ result.push_back(std::nextafter(point, leftBound));
+ result.push_back(std::nextafter(point, rightBound));
+ }
+
+ std::sort(begin(result), end(result));
+ return result;
+ }
+
+ template <class TValue>
+ static TPiecewiseLinearFunction<TValue> BuildFunctionFromPoints(
+ const std::vector<typename TPiecewiseLinearFunction<TValue>::TBuilder::TPoint>& points)
+ {
+ typename TPiecewiseLinearFunction<TValue>::TBuilder builder;
+ for (const auto& point : points) {
+ builder.AddPoint(point);
+ }
+ return builder.Finish();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestInterpolationProperties)
+{
+ const std::vector<TPiecewiseSegment<double>> segments = {
+ {{0, 0}, {1, 1}},
+ {{0, 1}, {1, 0}},
+ {{12.7, 1.1}, {15.2, 3.517}},
+ {{-1.57324932741e7, -4.12387218764e8}, {8.24325235234e9, 5.1234325324324e9}},
+ {{-1.57324932741e7, 4.12387218764e8}, {8.24325235234e9, -5.1234325324324e9}},
+ {{-1.57324932741e7, 0.1}, {8.24325235234e9, 0.23123}},
+ {{-1.57324932741e7, 0.23123}, {8.24325235234e9, 0.1}},
+ {{1.123, 0.23123}, {1.123, 0.1}},
+ {{1.123, 0.1}, {1.123, 0.23123}},
+ {{std::nextafter(1.123, 0), 0.1}, {std::nextafter(1.123, 2), 0.23123}},
+ {{std::nextafter(1.123, 0), 0.23123}, {std::nextafter(1.123, 2), 0.1}},
+ {{std::nextafter(1.123, 0), -4.12387218764e8}, {std::nextafter(1.123, 2), 5.1234325324324e9}},
+ {{std::nextafter(1.123, 0), 5.1234325324324e9}, {std::nextafter(1.123, 2), -4.12387218764e8}},
+ };
+
+ const std::vector<double> normalizedSamplePoints = {
+ 0, std::nextafter(0, 1), 0.000000001, 0.1, 0.3, 0.31415926535897932384626,
+ 0.4, 0.455555555, 0.49999999999, 0.5, 0.5172, 0.6, 0.7123,
+ 0.8, 0.9, 0.999999999999, 1.0
+ };
+
+ // Test that interpolation is precise on trivial segment.
+ {
+ auto segment = TPiecewiseSegment<double>({0, 0}, {1, 1});
+ auto points = AddNeighbourPointsAndSort(normalizedSamplePoints, 0, 1);
+
+ for (double point : points) {
+ EXPECT_EQ(point, segment.ValueAt(point));
+ }
+ }
+
+ // Test that interpolation is monotonic and is exact at bounds on different segments.
+ for (int segmentIdx = 0; segmentIdx < std::ssize(segments); segmentIdx++) {
+ const auto& segment = segments[segmentIdx];
+ auto msg = TStringBuilder()
+ << "For segment #" << segmentIdx << "equal to {"
+ << "{" << segment.LeftBound() << ", " << segment.LeftValue() << "}, "
+ << "{" << segment.RightBound() << ", " << segment.RightValue() << "}}";
+
+ // Test on endpoints.
+ {
+ EXPECT_EQ(segment.LeftValue(), segment.LeftLimitAt(segment.LeftBound()));
+ EXPECT_EQ(segment.RightValue(), segment.RightLimitAt(segment.RightBound()));
+ }
+
+ // Test |InterpolateNormalized|.
+ {
+ // Prepare points for test.
+ std::vector<double> interpolatePoints = AddNeighbourPointsAndSort(normalizedSamplePoints, 0, 1);
+
+ // Test monotonicity.
+ double prevResult = segment.LeftValue();
+ for (double point : interpolatePoints) {
+ double result = segment.InterpolateNormalized(point);
+ if (segment.LeftValue() <= segment.RightValue()) {
+ EXPECT_LE(prevResult, result) << msg;
+ } else {
+ EXPECT_GE(prevResult, result) << msg;
+ }
+ prevResult = result;
+ }
+ }
+
+ // Test |Get|.
+ {
+ // Prepare points for test.
+ std::vector<double> interpolatePoints;
+ for (double normalizedPoint : normalizedSamplePoints) {
+ // NB: It is ironic that we use linear interpolation to test linear interpolation. But it is perfectly correct here.
+ double point = segment.LeftBound() + (segment.RightBound() - segment.LeftBound()) * normalizedPoint;
+ if (point > segment.RightBound()) {
+ point = segment.RightBound();
+ }
+
+ interpolatePoints.push_back(point);
+ }
+ interpolatePoints = AddNeighbourPointsAndSort(interpolatePoints, segment.LeftBound(), segment.RightBound());
+
+ // |RightBound()| might be missing because of the imperfect interpolation used inside the loop.
+ interpolatePoints.push_back(segment.RightBound());
+ interpolatePoints.push_back(std::nextafter(segment.RightBound(), segment.LeftBound()));
+ std::sort(begin(interpolatePoints), end(interpolatePoints));
+
+ // Test monotonicity.
+ double prevResult = segment.LeftValue();
+ for (double point : interpolatePoints) {
+ double result = segment.ValueAt(point);
+ if (segment.LeftValue() <= segment.RightValue()) {
+ EXPECT_LE(prevResult, result) << msg;
+ } else {
+ EXPECT_GE(prevResult, result) << msg;
+ }
+ prevResult = result;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestSortOrMergeImpl)
+{
+ struct TTestCase {
+ TString Name;
+ std::vector<double> Input;
+ int ExpectedNumberOfPivots;
+ std::vector<double> ExpectedOutput;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "sortedInput",
+ /* Input */ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ /* ExpectedNumberOfPivots */ 2,
+ /* ExpectedOutput */ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
+ },
+ {
+ /* Name */ "twoSortedSegments",
+ /* Input */ {
+ 4, 5, 6, 7, 8, 9,
+ 0, 1, 2, 3
+ },
+ /* ExpectedNumberOfPivots */ 3,
+ /* ExpectedOutput */ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
+ },
+ {
+ /* Name */ "threeSortedSegments",
+ /* Input */ {
+ 8, 9,
+ 4, 5, 6, 7,
+ 0, 1, 2, 3
+ },
+ /* ExpectedNumberOfPivots */ 4,
+ /* ExpectedOutput */ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
+ },
+ {
+ /* Name */ "fourSortedSegments",
+ /* Input */ {
+ 8, 9,
+ 6, 7,
+ 3, 4, 5,
+ 0, 1, 2
+ },
+ /* ExpectedNumberOfPivots */ 5,
+ /* ExpectedOutput */ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
+ },
+ {
+ /* Name */ "fiveSortedSegments",
+ /* Input */ {
+ 4, 4.1, 4.2, 4.3,
+ 3, 3.1,
+ 2, 2.1,
+ 1, 1.1, 1.2,
+ 0, 0.1
+ },
+ /* ExpectedNumberOfPivots */ 6,
+ /* ExpectedOutput */ {
+ 0, 0.1,
+ 1, 1.1, 1.2,
+ 2, 2.1,
+ 3, 3.1,
+ 4, 4.1, 4.2, 4.3
+ }
+ },
+ {
+ /* Name */ "sixSortedSegments",
+ /* Input */ {
+ 5, 5.1,
+ 4, 4.1, 4.2, 4.3,
+ 3, 3.1,
+ 2, 2.1,
+ 1, 1.1, 1.2,
+ 0, 0.1
+ },
+ /* ExpectedNumberOfPivots */ 7,
+ /* ExpectedOutput */ {
+ 0, 0.1,
+ 1, 1.1, 1.2,
+ 2, 2.1,
+ 3, 3.1,
+ 4, 4.1, 4.2, 4.3,
+ 5, 5.1
+ }
+ },
+ {
+ /* Name */ "sevenSortedSegments",
+ /* Input */ {
+ 6, 6.1, 6.2,
+ 5, 5.1,
+ 4, 4.1, 4.2, 4.3,
+ 3, 3.1,
+ 2, 2.1,
+ 1, 1.1, 1.2,
+ 0, 0.1
+ },
+ /* ExpectedNumberOfPivots */ 8,
+ /* ExpectedOutput */ {
+ 0, 0.1,
+ 1, 1.1, 1.2,
+ 2, 2.1,
+ 3, 3.1,
+ 4, 4.1, 4.2, 4.3,
+ 5, 5.1,
+ 6, 6.1, 6.2
+ }
+ },
+ {
+ /* Name */ "eightSortedSegments",
+ /* Input */ {
+ 7, 7.1,
+ 6, 6.1, 6.2,
+ 5, 5.1,
+ 4, 4.1, 4.2, 4.3,
+ 3, 3.1,
+ 2, 2.1,
+ 1, 1.1, 1.2,
+ 0, 0.1
+ },
+ /* ExpectedNumberOfPivots */ 9,
+ /* ExpectedOutput */ {
+ 0, 0.1,
+ 1, 1.1, 1.2,
+ 2, 2.1,
+ 3, 3.1,
+ 4, 4.1, 4.2, 4.3,
+ 5, 5.1,
+ 6, 6.1, 6.2,
+ 7, 7.1
+ }
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = TStringBuilder() << "In the test case " << testCase.Name;
+
+ std::vector<double> vec = testCase.Input;
+ std::vector<double> buffer(vec.size());
+ NDetail::TPivotsVector mergePivots;
+ NDetail::TPivotsVector pivotsBuffer;
+
+ EXPECT_TRUE(NDetail::FindMergePivots(&vec, &mergePivots)) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedNumberOfPivots, std::ssize(mergePivots)) << testCaseMsg;
+ NDetail::SortOrMergeImpl(&vec, &buffer, &mergePivots, &pivotsBuffer);
+
+ EXPECT_TRUE(std::is_sorted(begin(vec), end(vec))) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedOutput, vec) << testCaseMsg;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestSum)
+{
+ struct TTestCase {
+ TString Name;
+ std::vector<TPiecewiseLinearFunction<double>> Functions;
+ double ExpectedLeftBound;
+ double ExpectedRightBound;
+ std::vector<TSample> Samples;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "sumOfOneFunction",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42)
+ },
+ /* ExpectedLeftBound */ 0.1,
+ /* ExpectedRightBound */ 1.2,
+ /* Samples */ {
+ {
+ /* Argument */ 0.1,
+ /* ExpectedLeftLimit */ 17,
+ /* ExpectedRightLimit */ 17,
+ },
+ {
+ /* Argument */ 0.3,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 42,
+ /* ExpectedRightLimit */ 42,
+ }
+ }
+ },
+ {
+ /* Name */ "sumOfLinearFunctionAndConstantSameBounds",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ TPiecewiseLinearFunction<double>::Constant(0.1, 1.2, 1)
+ },
+ /* ExpectedLeftBound */ 0.1,
+ /* ExpectedRightBound */ 1.2,
+ /* Samples */ {
+ {
+ /* Argument */ 0.1,
+ /* ExpectedLeftLimit */ 18,
+ /* ExpectedRightLimit */ 18,
+ },
+ {
+ /* Argument */ 0.3,
+ /* ExpectedLeftLimit */ 22.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 22.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 43,
+ /* ExpectedRightLimit */ 43,
+ }
+ }
+ },
+ {
+ /* Name */ "sumOfLinearFunctionAndConstantDifferentBounds",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ TPiecewiseLinearFunction<double>::Constant(0.3, 1.5, 1)
+ },
+ /* ExpectedLeftBound */ 0.3,
+ /* ExpectedRightBound */ 1.2,
+ /* Samples */ {
+ {
+ /* Argument */ 0.3,
+ /* ExpectedLeftLimit */ 22.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 22.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 43,
+ /* ExpectedRightLimit */ 43,
+ }
+ }
+ },
+ {
+ /* Name */ "sumOfTwoLinearFunctions",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(1, 1, 3, 5),
+ TPiecewiseLinearFunction<double>::Linear(1, 5, 3, 1)
+ },
+ /* ExpectedLeftBound */ 1,
+ /* ExpectedRightBound */ 3,
+ /* Samples */ {
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 6,
+ /* ExpectedRightLimit */ 6,
+ },
+ {
+ /* Argument */ 2.117117117,
+ /* ExpectedLeftLimit */ 6,
+ /* ExpectedRightLimit */ 6,
+ },
+ {
+ /* Argument */ 3,
+ /* ExpectedLeftLimit */ 6,
+ /* ExpectedRightLimit */ 6,
+ },
+ }
+ },
+ {
+ /* Name */ "sumOfThreeLinearFunctions",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(1, 1, 5, 5),
+ TPiecewiseLinearFunction<double>::Linear(2, 1, 7, 0),
+ TPiecewiseLinearFunction<double>::Linear(-3, 9, 5, 1)
+ },
+ /* ExpectedLeftBound */ 2,
+ /* ExpectedRightBound */ 5,
+ /* Samples */ {
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 7,
+ /* ExpectedRightLimit */ 7,
+ },
+ {
+ /* Argument */ 3,
+ /* ExpectedLeftLimit */ 6.8,
+ /* ExpectedRightLimit */ 6.8,
+ },
+ {
+ /* Argument */ 5,
+ /* ExpectedLeftLimit */ 6.4,
+ /* ExpectedRightLimit */ 6.4,
+ },
+ }
+ },
+ {
+ /* Name */ "sumWithDiscontinuousFunction",
+ /* Functions */ {
+ BuildFunctionFromPoints<double>({{0, 0}, {1, 0}, {1, 0.5}, {2, 1}}),
+ TPiecewiseLinearFunction<double>::Linear(0, 1, 2, 0),
+ },
+ /* ExpectedLeftBound */ 0,
+ /* ExpectedRightBound */ 2,
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ {
+ /* Argument */ 0.5,
+ /* ExpectedLeftLimit */ 0.75,
+ /* ExpectedRightLimit */ 0.75,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 0.5,
+ /* ExpectedRightLimit */ 1,
+ },
+ {
+ /* Argument */ 1.5,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ }
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = TStringBuilder() << "In the test case " << testCase.Name;
+
+ auto sumResult = TPiecewiseLinearFunction<double>::Sum(testCase.Functions);
+ EXPECT_EQ(testCase.ExpectedLeftBound, sumResult.LeftFunctionBound()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedRightBound, sumResult.RightFunctionBound()) << testCaseMsg;
+
+ for (const auto& sample : testCase.Samples) {
+ auto sampleMsg = TStringBuilder() << "At point " << sample.Argument;
+
+ EXPECT_EQ(sample.ExpectedLeftLimit, sumResult.ValueAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ EXPECT_EQ(sample.ExpectedLeftLimit, sumResult.LeftLimitAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ EXPECT_EQ(sample.ExpectedRightLimit, sumResult.RightLimitAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestPointwiseMin)
+{
+ struct TTestCase {
+ TString Name;
+ std::vector<TPiecewiseLinearFunction<double>> Functions;
+ double ExpectedLeftBound;
+ double ExpectedRightBound;
+ std::vector<TSample> Samples;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "minOfOneFunction",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42)
+ },
+ /* ExpectedLeftBound */ 0.1,
+ /* ExpectedRightBound */ 1.2,
+ /* Samples */ {
+ {
+ /* Argument */ 0.1,
+ /* ExpectedLeftLimit */ 17,
+ /* ExpectedRightLimit */ 17,
+ },
+ {
+ /* Argument */ 0.3,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 42,
+ /* ExpectedRightLimit */ 42,
+ }
+ }
+ },
+ {
+ /* Name */ "minOfLinearFunctionAndConstantSameBounds",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ TPiecewiseLinearFunction<double>::Constant(0.1, 1.2, 33)
+ },
+ /* ExpectedLeftBound */ 0.1,
+ /* ExpectedRightBound */ 1.2,
+ /* Samples */ {
+ {
+ /* Argument */ 0.1,
+ /* ExpectedLeftLimit */ 17,
+ /* ExpectedRightLimit */ 17,
+ },
+ {
+ /* Argument */ 0.3,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 33,
+ /* ExpectedRightLimit */ 33,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 33,
+ /* ExpectedRightLimit */ 33,
+ }
+ }
+ },
+ {
+ /* Name */ "minOfLinearFunctionAndConstantDifferentBounds",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ TPiecewiseLinearFunction<double>::Constant(0.3, 1.5, 33)
+ },
+ /* ExpectedLeftBound */ 0.3,
+ /* ExpectedRightBound */ 1.2,
+ /* Samples */ {
+ {
+ /* Argument */ 0.3,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 33,
+ /* ExpectedRightLimit */ 33,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 33,
+ /* ExpectedRightLimit */ 33,
+ }
+ }
+ },
+ {
+ /* Name */ "minOfTwoLinearFunctions",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(1, 1, 5, 5),
+ TPiecewiseLinearFunction<double>::Linear(1, 5, 5, 1)
+ },
+ /* ExpectedLeftBound */ 1,
+ /* ExpectedRightBound */ 5,
+ /* Samples */ {
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 2,
+ },
+ {
+ /* Argument */ 3,
+ /* ExpectedLeftLimit */ 3,
+ /* ExpectedRightLimit */ 3,
+ },
+ {
+ /* Argument */ 5,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ }
+ },
+ {
+ /* Name */ "minOfThreeLinearFunctions",
+ /* Functions */ {
+ TPiecewiseLinearFunction<double>::Linear(1, 1, 5, 5),
+ TPiecewiseLinearFunction<double>::Linear(0, 2, 10, 2),
+ TPiecewiseLinearFunction<double>::Linear(-3, 9, 5, 1)
+ },
+ /* ExpectedLeftBound */ 1,
+ /* ExpectedRightBound */ 5,
+ /* Samples */ {
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 2,
+ },
+ {
+ /* Argument */ 3,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 2,
+ },
+ {
+ /* Argument */ 4,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 2,
+ },
+ {
+ /* Argument */ 5,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ }
+ },
+ {
+ /* Name */ "minWithDiscontinuousFunctionContinuousResult",
+ /* Functions */ {
+ BuildFunctionFromPoints<double>({{0, 0}, {1, 0}, {1, 1}, {2, 2}}),
+ TPiecewiseLinearFunction<double>::Linear(0, 0, 2, -1),
+ },
+ /* ExpectedLeftBound */ 0,
+ /* ExpectedRightBound */ 2,
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ {
+ /* Argument */ 0.5,
+ /* ExpectedLeftLimit */ -0.25,
+ /* ExpectedRightLimit */ -0.25,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ -0.5,
+ /* ExpectedRightLimit */ -0.5,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ -1,
+ /* ExpectedRightLimit */ -1,
+ },
+ }
+ },
+ {
+ /* Name */ "minWithDiscontinuousFunctionDiscontinuousResult",
+ /* Functions */ {
+ BuildFunctionFromPoints<double>({{0, 0}, {1, 0}, {1, 1}, {2, 2}}),
+ TPiecewiseLinearFunction<double>::Linear(0, 1, 2, 0),
+ },
+ /* ExpectedLeftBound */ 0,
+ /* ExpectedRightBound */ 2,
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ {
+ /* Argument */ 0.5,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0.5,
+ },
+ {
+ /* Argument */ 1.5,
+ /* ExpectedLeftLimit */ 0.25,
+ /* ExpectedRightLimit */ 0.25,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ }
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = TStringBuilder() << "In the test case " << testCase.Name;
+
+ auto minResult = PointwiseMin<double>(testCase.Functions);
+ EXPECT_EQ(testCase.ExpectedLeftBound, minResult.LeftFunctionBound()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedRightBound, minResult.RightFunctionBound()) << testCaseMsg;
+
+ for (const auto& sample : testCase.Samples) {
+ auto sampleMsg = TStringBuilder() << "At point %v" << sample.Argument;
+
+ EXPECT_EQ(sample.ExpectedLeftLimit, minResult.ValueAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ EXPECT_EQ(sample.ExpectedLeftLimit, minResult.LeftLimitAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ EXPECT_EQ(sample.ExpectedRightLimit, minResult.RightLimitAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestCompose)
+{
+ struct TTestCase {
+ TString Name;
+ TPiecewiseLinearFunction<double> Lhs;
+ TPiecewiseLinearFunction<double> Rhs;
+ std::vector<TSample> Samples;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "compositionLinearWithIdentity",
+ /* Lhs */ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ /* Rhs */ TPiecewiseLinearFunction<double>::Linear(0.1, 0.1, 1.2, 1.2),
+ /* Samples */ {
+ {
+ /* Argument */ 0.1,
+ /* ExpectedLeftLimit */ 17,
+ /* ExpectedRightLimit */ 17,
+ },
+ {
+ /* Argument */ 0.3,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 42,
+ /* ExpectedRightLimit */ 42,
+ }
+ }
+ },
+ {
+ /* Name */ "compositionLinearWithLinear",
+ /* Lhs */ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ /* Rhs */ TPiecewiseLinearFunction<double>::Linear(0, 0.1, 100, 1.2),
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 17,
+ /* ExpectedRightLimit */ 17,
+ },
+ {
+ /* Argument */ 20,
+ /* ExpectedLeftLimit */ 22,
+ /* ExpectedRightLimit */ 22,
+ },
+ {
+ /* Argument */ 100,
+ /* ExpectedLeftLimit */ 42,
+ /* ExpectedRightLimit */ 42,
+ }
+ }
+ },
+ {
+ /* Name */ "compositionLinearWithLinearInjection",
+ /* Lhs */ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ /* Rhs */ TPiecewiseLinearFunction<double>::Linear(0, 0.3, 100, 0.98),
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 100,
+ /* ExpectedLeftLimit */ 37,
+ /* ExpectedRightLimit */ 37,
+ }
+ }
+ },
+ {
+ /* Name */ "compositionLinearWithDiscontinuous",
+ /* Lhs */ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ /* Rhs */ BuildFunctionFromPoints<double>({{0, 0.1}, {1, 0.3}, {1, 0.98}, {2, 1.2}}),
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 17,
+ /* ExpectedRightLimit */ 17,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 37,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 42,
+ /* ExpectedRightLimit */ 42,
+ }
+ }
+ },
+ {
+ /* Name */ "compositionLinearWithPlateauing",
+ /* Lhs */ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ /* Rhs */ BuildFunctionFromPoints<double>({{0, 0.1}, {1, 0.3}, {2, 0.3}, {3, 1.2}}),
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 17,
+ /* ExpectedRightLimit */ 17,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 21.5454545454545454545454545454545454545454545454,
+ /* ExpectedRightLimit */ 21.5454545454545454545454545454545454545454545454,
+ },
+ {
+ /* Argument */ 3,
+ /* ExpectedLeftLimit */ 42,
+ /* ExpectedRightLimit */ 42,
+ }
+ }
+ },
+ {
+ // NB (eshcherbin): We use a pair of simpler functions here to avoid precision problems.
+ /* Name */ "compositionDiscontinuousWithLinear",
+ /* Lhs */ BuildFunctionFromPoints<double>({{0, 0}, {1, 1}, {1, 2}, {2, 3}}),
+ /* Rhs */ TPiecewiseLinearFunction<double>::Linear(0, 0, 2, 2),
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 2,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 3,
+ /* ExpectedRightLimit */ 3,
+ },
+ }
+ },
+ {
+ /* Name */ "compositionPlateauingWithLinear",
+ /* Lhs */ BuildFunctionFromPoints<double>({{17, 0}, {22, 1}, {37, 1}, {42, 2}}),
+ /* Rhs */ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ /* Samples */ {
+ {
+ /* Argument */ 0.1,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ {
+ /* Argument */ 0.32,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ {
+ /* Argument */ 0.98,
+ /* ExpectedLeftLimit */ 1,
+ /* ExpectedRightLimit */ 1,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 2,
+ },
+ }
+ },
+ {
+ /* Name */ "compositionDiscontinuousWithPlateauing",
+ /* Lhs */ BuildFunctionFromPoints<double>({{0, 0}, {3, 3}, {3, 6}, {6, 9}}),
+ /* Rhs */ BuildFunctionFromPoints<double>({{0, 0}, {1, 3}, {3, 3}, {4, 6}}),
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 3,
+ /* ExpectedRightLimit */ 3,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 3,
+ /* ExpectedRightLimit */ 3,
+ },
+ {
+ /* Argument */ 3,
+ /* ExpectedLeftLimit */ 3,
+ /* ExpectedRightLimit */ 6,
+ },
+ {
+ /* Argument */ 4,
+ /* ExpectedLeftLimit */ 9,
+ /* ExpectedRightLimit */ 9,
+ },
+ }
+ },
+ {
+ /* Name */ "compositionMonster",
+ /* Lhs */ BuildFunctionFromPoints<double>({{0, 0}, {3, 3}, {3, 5}, {5, 5}, {7, 7}}),
+ /* Rhs */ BuildFunctionFromPoints<double>({{0, 0}, {1, 2}, {2, 2}, {2, 4}, {4, 6}}),
+ /* Samples */ {
+ {
+ /* Argument */ 0,
+ /* ExpectedLeftLimit */ 0,
+ /* ExpectedRightLimit */ 0,
+ },
+ {
+ /* Argument */ 1,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 2,
+ },
+ {
+ /* Argument */ 1.2,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 2,
+ },
+ {
+ /* Argument */ 2,
+ /* ExpectedLeftLimit */ 2,
+ /* ExpectedRightLimit */ 5,
+ },
+ {
+ /* Argument */ 3,
+ /* ExpectedLeftLimit */ 5,
+ /* ExpectedRightLimit */ 5,
+ },
+ {
+ /* Argument */ 4,
+ /* ExpectedLeftLimit */ 6,
+ /* ExpectedRightLimit */ 6,
+ },
+ }
+ },
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = TStringBuilder() << "In the test case " << testCase.Name;
+
+ auto result = testCase.Lhs.Compose(testCase.Rhs);
+ EXPECT_EQ(testCase.Rhs.LeftFunctionBound(), result.LeftFunctionBound()) << testCaseMsg;
+ EXPECT_EQ(testCase.Rhs.RightFunctionBound(), result.RightFunctionBound()) << testCaseMsg;
+
+ for (const auto& sample : testCase.Samples) {
+ auto sampleMsg = TStringBuilder() << "At point %v" << sample.Argument;
+
+ EXPECT_EQ(sample.ExpectedLeftLimit, result.ValueAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ EXPECT_EQ(sample.ExpectedLeftLimit, result.LeftLimitAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ EXPECT_EQ(sample.ExpectedRightLimit, result.RightLimitAt(sample.Argument)) << testCaseMsg << sampleMsg;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestTransformations)
+{
+ struct TTestCase {
+ TString Name;
+ TPiecewiseLinearFunction<double> Function;
+ TPiecewiseLinearFunction<double> ExpectedTransposedFunction;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "linear",
+ /* Function */ TPiecewiseLinearFunction<double>::Linear(0.1, 17, 1.2, 42),
+ /* ExpectedTransposedFunction */ TPiecewiseLinearFunction<double>::Linear(17, 0.1, 42, 1.2),
+ },
+ {
+ /* Name */ "constant",
+ /* Function */ TPiecewiseLinearFunction<double>::Constant(0.1, 1.2, 42),
+ /* ExpectedTransposedFunction */ TPiecewiseLinearFunction<double>::Linear(42, 0.1, 42, 1.2),
+ },
+ {
+ /* Name */ "discontinous",
+ /* Function */ BuildFunctionFromPoints<double>({{0, 0}, {1, 1}, {1, 2}, {2, 3}}),
+ /* ExpectedTransposedFunction */ BuildFunctionFromPoints<double>({{0, 0}, {1, 1}, {2, 1}, {3, 2}}),
+ },
+ {
+ /* Name */ "plateauing",
+ /* Function */ BuildFunctionFromPoints<double>({{0, 0}, {1, 1}, {2, 1}, {3, 2}}),
+ /* ExpectedTransposedFunction */ BuildFunctionFromPoints<double>({{0, 0}, {1, 1}, {1, 2}, {2, 3}}),
+ },
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = TStringBuilder() << "In the test case " << testCase.Name;
+
+ const auto& function = testCase.Function;
+ EXPECT_EQ(testCase.ExpectedTransposedFunction, function.Transpose()) << testCaseMsg;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestPiecewiseSegmentScalar)
+{
+ struct TDefinedOnInterval {
+ double LeftBound;
+ double RightBound;
+ bool ExpectedIsDefined;
+ };
+
+ struct TTestCase {
+ TString Name;
+ TPiecewiseSegment<double> Segment;
+ std::pair<double, double> ExpectedBounds;
+ std::pair<double, double> ExpectedValues;
+ std::vector<TDefinedOnInterval> Intervals;
+ std::vector<TSample> Samples;
+ bool ExpectedIsVertical;
+ bool ExpectedIsHorizontal;
+ bool ExpectedIsPoint;
+ bool ExpectedIsTilted;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "tiltedSegment",
+ /* Segment */ {{0, 0}, {2, 1}},
+ /* ExpectedBounds */ {0, 2},
+ /* ExpectedValues */ {0, 1},
+ /* Intervals */ {
+ {0, 2, true},
+ {0, 1, true},
+ {1, 2, true},
+ {0.3, 0.6, true},
+ {-1, 3, false},
+ {-1, 1, false},
+ {10, 11, false},
+ {2, 3, false}
+ },
+ /* Samples */ {
+ {0, 0, 0},
+ {0.5, 0.25, 0.25},
+ {1, 0.5, 0.5},
+ {2, 1, 1},
+ },
+ /* ExpectedIsVertical */ false,
+ /* ExpectedIsHorizontal */ false,
+ /* ExpectedIsPoint */ false,
+ /* ExpectedIsTilted */ true
+ },
+ {
+ /* Name */ "diagonalSegment",
+ /* Segment */ {{0, 0}, {1, 1}},
+ /* ExpectedBounds */ {0, 1},
+ /* ExpectedValues */ {0, 1},
+ /* Intervals */ {
+ {0, 1, true},
+ {0, 0.5, true},
+ {0.5, 1, true},
+ {0.3, 0.6, true},
+ {-1, 2, false},
+ {-1, 0.5, false},
+ {10, 11, false},
+ {1, 2, false}
+ },
+ /* Samples */ {
+ {0, 0, 0},
+ {0.5, 0.5, 0.5},
+ {1, 1, 1},
+ },
+ /* ExpectedIsVertical */ false,
+ /* ExpectedIsHorizontal */ false,
+ /* ExpectedIsPoint */ false,
+ /* ExpectedIsTilted */ true
+ },
+ {
+ /* Name */ "verticalSegment",
+ /* Segment */ {{0, 0}, {0, 1}},
+ /* ExpectedBounds */ {0, 0},
+ /* ExpectedValues */ {0, 1},
+ /* Intervals */ {
+ {0, 0, true},
+ {0, 0.5, false},
+ {0.3, 0.6, false},
+ {-1, 2, false},
+ },
+ /* Samples */ {
+ {0, 0, 1},
+ },
+ /* ExpectedIsVertical */ true,
+ /* ExpectedIsHorizontal */ false,
+ /* ExpectedIsPoint */ false,
+ /* ExpectedIsTilted */ false
+ },
+ {
+ /* Name */ "horizontalSegment",
+ /* Segment */ {{0, 0}, {1, 0}},
+ /* ExpectedBounds */ {0, 1},
+ /* ExpectedValues */ {0, 0},
+ /* Intervals */ {
+ {0, 1, true},
+ {0, 0.5, true},
+ {0.5, 1, true},
+ {0.3, 0.6, true},
+ {-1, 2, false},
+ {-1, 0.5, false},
+ {10, 11, false},
+ {1, 2, false}
+ },
+ /* Samples */ {
+ {0, 0, 0},
+ {0.5, 0, 0},
+ {1, 0, 0},
+ },
+ /* ExpectedIsVertical */ false,
+ /* ExpectedIsHorizontal */ true,
+ /* ExpectedIsPoint */ false,
+ /* ExpectedIsTilted */ false
+ },
+ {
+ /* Name */ "pointSegment",
+ /* Segment */ {{0, 0}, {0, 0}},
+ /* ExpectedBounds */ {0, 0},
+ /* ExpectedValues */ {0, 0},
+ /* Intervals */ {
+ {0, 0, true},
+ {0, 0.5, false},
+ {0.3, 0.6, false},
+ {-1, 2, false},
+ },
+ /* Samples */ {
+ {0, 0, 0},
+ },
+ /* ExpectedIsVertical */ false,
+ /* ExpectedIsHorizontal */ false,
+ /* ExpectedIsPoint */ true,
+ /* ExpectedIsTilted */ false
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = TStringBuilder() << "In the test case " << testCase.Name;
+
+ const auto& segment = testCase.Segment;
+
+ EXPECT_EQ(testCase.ExpectedBounds.first, segment.LeftBound()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedBounds.second, segment.RightBound()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedValues.first, segment.LeftValue()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedValues.second, segment.RightValue()) << testCaseMsg;
+
+ for (const auto& [leftBound, rightBound, expectedIsDefined] : testCase.Intervals) {
+ auto sampleMsg = TStringBuilder() << "At interval [" << leftBound << ", " << rightBound << "]";
+
+ EXPECT_EQ(expectedIsDefined, segment.IsDefinedOn(leftBound, rightBound)) << testCaseMsg << ". " << sampleMsg;
+ }
+
+ for (const auto& [arg, expectedLeftLimit, expectedRightLimit] : testCase.Samples) {
+ auto sampleMsg = TStringBuilder() << "At point " << arg;
+
+ EXPECT_TRUE(segment.IsDefinedAt(arg)) << testCaseMsg << ". " << sampleMsg;
+ EXPECT_EQ(expectedLeftLimit, segment.LeftLimitAt(arg)) << testCaseMsg << ". " << sampleMsg;
+ EXPECT_EQ(expectedRightLimit, segment.RightLimitAt(arg)) << testCaseMsg << ". " << sampleMsg;
+ auto expectedLeftRightLimit = std::make_pair(expectedLeftLimit, expectedRightLimit);
+ EXPECT_EQ(expectedLeftRightLimit, segment.LeftRightLimitAt(arg)) << testCaseMsg << ". " << sampleMsg;
+ EXPECT_EQ(expectedLeftLimit, segment.ValueAt(arg)) << testCaseMsg << ". " << sampleMsg;
+ }
+
+ EXPECT_EQ(testCase.ExpectedIsVertical, segment.IsVertical()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedIsHorizontal, segment.IsHorizontal()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedIsPoint, segment.IsPoint()) << testCaseMsg;
+ EXPECT_EQ(testCase.ExpectedIsTilted, segment.IsTilted()) << testCaseMsg;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TPiecewiseLinearFunctionTest, TestPiecewiseSegmentTransformationsScalar)
+{
+ struct TScaledSegment {
+ double Scale;
+ TPiecewiseSegment<double> ExpectedSegment;
+ };
+
+ struct TShiftedSegment {
+ double DeltaBound;
+ double DeltaValue;
+ TPiecewiseSegment<double> ExpectedSegment;
+ };
+
+ struct TTestCase {
+ TString Name;
+ TPiecewiseSegment<double> Segment;
+ TPiecewiseSegment<double> ExpectedTransposedSegment;
+ std::vector<TScaledSegment> ExpectedScaledSegments;
+ std::vector<TShiftedSegment> ExpectedShiftedSegments;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* Name */ "tiltedSegment",
+ /* Segment */ {{0, 0}, {2, 1}},
+ /* ExpectedTransposedSegment */ {{0, 0}, {1, 2}},
+ /* ExpectedScaledSegments */ {
+ {0.5, {{0, 0}, {4, 1}}},
+ {1, {{0, 0}, {2, 1}}},
+ {2, {{0, 0}, {1, 1}}},
+ },
+ /* ExpectedShiftedSegments */ {
+ {1, 0, {{1, 0}, {3, 1}}},
+ {0, 1, {{0, 1}, {2, 2}}},
+ {1, 1, {{1, 1}, {3, 2}}},
+ },
+ },
+ {
+ /* Name */ "tiltedSegment2",
+ /* Segment */ {{10, 0}, {12, 1}},
+ /* ExpectedTransposedSegment */ {{0, 10}, {1, 12}},
+ /* ExpectedScaledSegments */ {
+ {0.5, {{20, 0}, {24, 1}}},
+ {1, {{10, 0}, {12, 1}}},
+ {2, {{5, 0}, {6, 1}}},
+ },
+ /* ExpectedShiftedSegments */ {
+ {1, 0, {{11, 0}, {13, 1}}},
+ {0, 1, {{10, 1}, {12, 2}}},
+ {1, 1, {{11, 1}, {13, 2}}},
+ },
+ },
+ {
+ /* Name */ "diagonalSegment",
+ /* Segment */ {{0, 0}, {1, 1}},
+ /* ExpectedTransposedSegment */ {{0, 0}, {1, 1}},
+ /* ExpectedScaledSegments */ {
+ {0.5, {{0, 0}, {2, 1}}},
+ {1, {{0, 0}, {1, 1}}},
+ {2, {{0, 0}, {0.5, 1}}},
+ },
+ /* ExpectedShiftedSegments */ {
+ {1, 0, {{1, 0}, {2, 1}}},
+ {0, 1, {{0, 1}, {1, 2}}},
+ {1, 1, {{1, 1}, {2, 2}}},
+ },
+ },
+ {
+ /* Name */ "verticalSegment",
+ /* Segment */ {{0, 0}, {0, 1}},
+ /* ExpectedTransposedSegment */ {{0, 0}, {1, 0}},
+ /* ExpectedScaledSegments */ {
+ {0.5, {{0, 0}, {0, 1}}},
+ {1, {{0, 0}, {0, 1}}},
+ {2, {{0, 0}, {0, 1}}},
+ },
+ /* ExpectedShiftedSegments */ {
+ {1, 0, {{1, 0}, {1, 1}}},
+ {0, 1, {{0, 1}, {0, 2}}},
+ {1, 1, {{1, 1}, {1, 2}}},
+ },
+ },
+ {
+ /* Name */ "horizontalSegment",
+ /* Segment */ {{0, 0}, {1, 0}},
+ /* ExpectedTransposedSegment */ {{0, 0}, {0, 1}},
+ /* ExpectedScaledSegments */ {
+ {0.5, {{0, 0}, {2, 0}}},
+ {1, {{0, 0}, {1, 0}}},
+ {2, {{0, 0}, {0.5, 0}}},
+ },
+ /* ExpectedShiftedSegments */ {
+ {1, 0, {{1, 0}, {2, 0}}},
+ {0, 1, {{0, 1}, {1, 1}}},
+ {1, 1, {{1, 1}, {2, 1}}},
+ },
+ },
+ {
+ /* Name */ "pointSegment",
+ /* Segment */ {{0, 0}, {0, 0}},
+ /* ExpectedTransposedSegment */ {{0, 0}, {0, 0}},
+ /* ExpectedScaledSegments */ {
+ {0.5, {{0, 0}, {0, 0}}},
+ {1, {{0, 0}, {0, 0}}},
+ {2, {{0, 0}, {0, 0}}},
+ },
+ /* ExpectedShiftedSegments */ {
+ {1, 0, {{1, 0}, {1, 0}}},
+ {0, 1, {{0, 1}, {0, 1}}},
+ {1, 1, {{1, 1}, {1, 1}}},
+ },
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ auto testCaseMsg = TStringBuilder() << "In the test case " << testCase.Name;
+
+ const auto& segment = testCase.Segment;
+
+ EXPECT_EQ(testCase.ExpectedTransposedSegment, segment.Transpose()) << testCaseMsg;
+
+ for (const auto& [scale, expectedSegment] : testCase.ExpectedScaledSegments) {
+ auto sampleMsg = TStringBuilder() << "At scale " << scale;
+
+ EXPECT_EQ(expectedSegment, segment.ScaleArgument(scale)) << testCaseMsg << ". " << sampleMsg;
+ }
+
+ for (const auto& [deltaBound, deltaValue, expectedSegment] : testCase.ExpectedShiftedSegments) {
+ auto sampleMsg = TStringBuilder() << "At shift [" << deltaBound << ", " << deltaValue << "]";
+
+ EXPECT_EQ(expectedSegment, segment.Shift(deltaBound, deltaValue)) << testCaseMsg << ". " << sampleMsg;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TODO(eshcherbin): Add tests for vector-valued segment and function, when they are officially out.
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/unittests/util_ut.cpp b/yt/yt/library/numeric/unittests/util_ut.cpp
new file mode 100644
index 0000000000..56b7af7f85
--- /dev/null
+++ b/yt/yt/library/numeric/unittests/util_ut.cpp
@@ -0,0 +1,105 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <yt/yt/library/numeric/util.h>
+
+#include <limits>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TUtilTest
+ : public ::testing::Test
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(TUtilTest, TestMidpoint)
+{
+ struct TTestCase
+ {
+ TString name;
+ int64_t a;
+ int64_t b;
+ int64_t expected;
+ };
+
+ const std::vector<TTestCase> testCases = {
+ {
+ /* name */ "0_to_9",
+ /* a */ 0,
+ /* b */ 9,
+ /* expected */ 4
+ },
+ {
+ /* name */ "0_to_10",
+ /* a */ 0,
+ /* b */ 10,
+ /* expected */ 5
+ },
+ {
+ /* name */ "9_to_0",
+ /* a */ 9,
+ /* b */ 0,
+ /* expected */ 5
+ },
+ {
+ /* name */ "5_to_6",
+ /* a */ 5,
+ /* b */ 6,
+ /* expected */ 5
+ },
+ {
+ /* name */ "6_to_5",
+ /* a */ 6,
+ /* b */ 5,
+ /* expected */ 6
+ },
+ {
+ /* name */ "-5_to_-6",
+ /* a */ -5,
+ /* b */ -6,
+ /* expected */ -5
+ },
+ {
+ /* name */ "5_to_-6",
+ /* a */ 5,
+ /* b */ -6,
+ /* expected */ 0
+ },
+ {
+ /* name */ "0_to_-10",
+ /* a */ 0,
+ /* b */ -10,
+ /* expected */ -5
+ },
+ {
+ /* name */ "minint_to_maxint",
+ /* a */ std::numeric_limits<int64_t>::min(),
+ /* b */ std::numeric_limits<int64_t>::max(),
+ /* expected */ -1
+ },
+ {
+ /* name */ "-5_to_maxint",
+ /* a */ -5,
+ /* b */ std::numeric_limits<int64_t>::max(),
+ /* expected */ (std::numeric_limits<int64_t>::max() - 5) / 2
+ },
+ {
+ /* name */ "5_to_maxint",
+ /* a */ 5,
+ /* b */ std::numeric_limits<int64_t>::max(),
+ /* expected */ 5 + (std::numeric_limits<int64_t>::max() - 5) / 2
+ }
+ };
+
+ for (const auto& testCase : testCases) {
+ EXPECT_EQ(testCase.expected, Midpoint(testCase.a, testCase.b)) << "In the test case " << testCase.name;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/yt/yt/library/numeric/unittests/ya.make b/yt/yt/library/numeric/unittests/ya.make
new file mode 100644
index 0000000000..567c274e3a
--- /dev/null
+++ b/yt/yt/library/numeric/unittests/ya.make
@@ -0,0 +1,23 @@
+GTEST(unittester-library-numeric)
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ binary_search_ut.cpp
+ double_array_ut.cpp
+ piecewise_linear_function_ut.cpp
+ util_ut.cpp
+)
+
+ADDINCL(
+ yt/yt/library/numeric
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/opensource_tests.inc)
+
+PEERDIR(
+ yt/yt/library/numeric
+ library/cpp/testing/gtest
+)
+
+END()
diff --git a/yt/yt/library/numeric/util.h b/yt/yt/library/numeric/util.h
new file mode 100644
index 0000000000..2a18e1b606
--- /dev/null
+++ b/yt/yt/library/numeric/util.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <util/system/compiler.h>
+
+#include <type_traits>
+#include <cstring>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// See: |std::bit_cast| from header <bit> from C++2a.
+// Remove this implementation and use the standard one when it becomes available.
+template <class TTo, class TFrom>
+TTo BitCast(const TFrom &src) noexcept
+{
+ static_assert(sizeof(TTo) == sizeof(TFrom));
+ static_assert(std::is_trivially_copyable_v<TFrom>);
+ static_assert(std::is_trivial_v<TTo>);
+
+ TTo dst;
+ std::memcpy(&dst, &src, sizeof(TTo));
+ return dst;
+}
+
+// See: |std::midpoint| from header <numeric> from C++2a.
+// Remove this implementation and use the standard one when it becomes available.
+template <class TInt>
+TInt Midpoint(TInt a, TInt b) noexcept
+{
+ static_assert(std::is_integral_v<TInt>);
+ static_assert(std::is_same_v<std::remove_cv_t<TInt>, TInt>);
+ static_assert(!std::is_same_v<TInt, bool>);
+
+ using TUInt = std::make_unsigned_t<TInt>;
+
+ int k = 1;
+ TUInt mn = a;
+ TUInt mx = b;
+ if (a > b) {
+ k = -1;
+ mn = b;
+ mx = a;
+ }
+
+ return a + k * TInt(TUInt(mx - mn) >> 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/numeric/vector_format.h b/yt/yt/library/numeric/vector_format.h
new file mode 100644
index 0000000000..6a4e5ebe98
--- /dev/null
+++ b/yt/yt/library/numeric/vector_format.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <util/string/builder.h>
+
+namespace NYT::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+TString ToString(const std::vector<T>& vec)
+{
+ ::TStringBuilder outputStream;
+ outputStream << "[";
+ for (size_t index = 0; index < vec.size(); ++index) {
+ outputStream << vec[index];
+ if (index + 1 < vec.size()) {
+ outputStream << ", ";
+ }
+ }
+ outputStream << "]";
+ return std::move(outputStream);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDetail
diff --git a/yt/yt/library/numeric/ya.make b/yt/yt/library/numeric/ya.make
new file mode 100644
index 0000000000..b95d314c0b
--- /dev/null
+++ b/yt/yt/library/numeric/ya.make
@@ -0,0 +1,37 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ piecewise_linear_function.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/small_containers
+ util
+)
+
+CHECK_DEPENDENT_DIRS(
+ ALLOW_ONLY ALL
+ build
+ contrib
+ library
+ util
+ library/cpp/yt/small_containers
+)
+
+END()
+
+RECURSE(
+ serialize
+)
+
+IF (NOT OPENSOURCE)
+ RECURSE(
+ benchmark
+ )
+ENDIF()
+
+RECURSE_FOR_TESTS(
+ unittests
+)
diff --git a/yt/yt/library/quantile_digest/CMakeLists.linux-aarch64.txt b/yt/yt/library/quantile_digest/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..c0f1ecf932
--- /dev/null
+++ b/yt/yt/library/quantile_digest/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,55 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+
+add_library(yt-library-quantile_digest)
+target_compile_options(yt-library-quantile_digest PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-quantile_digest PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yt-memory
+ library-cpp-tdigest
+ yt-yt-core
+ contrib-libs-protobuf
+)
+target_proto_messages(yt-library-quantile_digest PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/quantile_digest/proto/quantile_digest.proto
+)
+target_sources(yt-library-quantile_digest PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/quantile_digest/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/quantile_digest/quantile_digest.cpp
+)
+target_proto_addincls(yt-library-quantile_digest
+ ./
+ ${CMAKE_SOURCE_DIR}/
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+)
+target_proto_outs(yt-library-quantile_digest
+ --cpp_out=${CMAKE_BINARY_DIR}/
+ --cpp_styleguide_out=${CMAKE_BINARY_DIR}/
+)
diff --git a/yt/yt/library/quantile_digest/CMakeLists.linux-x86_64.txt b/yt/yt/library/quantile_digest/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..c0f1ecf932
--- /dev/null
+++ b/yt/yt/library/quantile_digest/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,55 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+
+add_library(yt-library-quantile_digest)
+target_compile_options(yt-library-quantile_digest PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-quantile_digest PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ cpp-yt-memory
+ library-cpp-tdigest
+ yt-yt-core
+ contrib-libs-protobuf
+)
+target_proto_messages(yt-library-quantile_digest PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/quantile_digest/proto/quantile_digest.proto
+)
+target_sources(yt-library-quantile_digest PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/quantile_digest/config.cpp
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/quantile_digest/quantile_digest.cpp
+)
+target_proto_addincls(yt-library-quantile_digest
+ ./
+ ${CMAKE_SOURCE_DIR}/
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+)
+target_proto_outs(yt-library-quantile_digest
+ --cpp_out=${CMAKE_BINARY_DIR}/
+ --cpp_styleguide_out=${CMAKE_BINARY_DIR}/
+)
diff --git a/yt/yt/library/quantile_digest/CMakeLists.txt b/yt/yt/library/quantile_digest/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/library/quantile_digest/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/library/quantile_digest/config.cpp b/yt/yt/library/quantile_digest/config.cpp
new file mode 100644
index 0000000000..568a5716a2
--- /dev/null
+++ b/yt/yt/library/quantile_digest/config.cpp
@@ -0,0 +1,17 @@
+#include "config.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TTDigestConfig::Register(TRegistrar registrar)
+{
+ registrar.Parameter("delta", &TThis::Delta)
+ .Default(0.01);
+ registrar.Parameter("compression_frequency", &TThis::CompressionFrequency)
+ .Default(25);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/quantile_digest/config.h b/yt/yt/library/quantile_digest/config.h
new file mode 100644
index 0000000000..27193cd427
--- /dev/null
+++ b/yt/yt/library/quantile_digest/config.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTDigestConfig
+ : public NYTree::TYsonStruct
+{
+public:
+ double Delta;
+ double CompressionFrequency;
+
+ REGISTER_YSON_STRUCT(TTDigestConfig);
+
+ static void Register(TRegistrar registrar);
+};
+
+DEFINE_REFCOUNTED_TYPE(TTDigestConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/quantile_digest/proto/quantile_digest.proto b/yt/yt/library/quantile_digest/proto/quantile_digest.proto
new file mode 100644
index 0000000000..ec29e4d3da
--- /dev/null
+++ b/yt/yt/library/quantile_digest/proto/quantile_digest.proto
@@ -0,0 +1,20 @@
+package NYT.NProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TQuantileDigest
+{
+ extensions 100 to max;
+};
+
+message TTDigest
+{
+ extend TQuantileDigest
+ {
+ optional TTDigest t_digest = 100;
+ }
+
+ optional bytes data = 1;
+};
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt/library/quantile_digest/public.h b/yt/yt/library/quantile_digest/public.h
new file mode 100644
index 0000000000..228fe17f42
--- /dev/null
+++ b/yt/yt/library/quantile_digest/public.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <library/cpp/yt/memory/intrusive_ptr.h>
+#include <library/cpp/yt/memory/ref_counted.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_STRUCT(IQuantileDigest)
+
+DECLARE_REFCOUNTED_CLASS(TTDigestConfig)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/quantile_digest/quantile_digest.cpp b/yt/yt/library/quantile_digest/quantile_digest.cpp
new file mode 100644
index 0000000000..b500ab1aa3
--- /dev/null
+++ b/yt/yt/library/quantile_digest/quantile_digest.cpp
@@ -0,0 +1,88 @@
+#include "quantile_digest.h"
+#include "config.h"
+
+#include <yt/yt/library/quantile_digest/proto/quantile_digest.pb.h>
+
+#include <yt/yt/core/misc/error.h>
+
+#include <library/cpp/yt/memory/new.h>
+
+#include <library/cpp/tdigest/tdigest.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TTDigestAdaptor
+ : public IQuantileDigest
+{
+public:
+ TTDigestAdaptor(double delta, double compressionFrequency)
+ : UnderlyingDigest_(delta, compressionFrequency)
+ { }
+
+ explicit TTDigestAdaptor(const NProto::TTDigest& serialized)
+ : UnderlyingDigest_(serialized.data())
+ { }
+
+ void AddValue(double value) override
+ {
+ UnderlyingDigest_.AddValue(value);
+ }
+
+ i64 GetCount() const override
+ {
+ return UnderlyingDigest_.GetCount();
+ }
+
+ double GetQuantile(double quantile) override
+ {
+ return UnderlyingDigest_.GetPercentile(quantile);
+ }
+
+ double GetRank(double value) override
+ {
+ return UnderlyingDigest_.GetRank(value);
+ }
+
+ TString Serialize() override
+ {
+ NProto::TQuantileDigest quantileDigest;
+
+ auto* tDigest = quantileDigest.MutableExtension(NProto::TTDigest::t_digest);
+ *tDigest->mutable_data() = UnderlyingDigest_.Serialize();
+
+ return quantileDigest.SerializeAsString();
+ }
+
+private:
+ TDigest UnderlyingDigest_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+IQuantileDigestPtr CreateTDigest(const TTDigestConfigPtr& config)
+{
+ return New<TTDigestAdaptor>(config->Delta, config->CompressionFrequency);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+IQuantileDigestPtr LoadQuantileDigest(TStringBuf serialized)
+{
+ NProto::TQuantileDigest quantileDigest;
+ if (!quantileDigest.ParseFromArray(serialized.begin(), serialized.Size())) {
+ THROW_ERROR_EXCEPTION("Failed to parse quantile digest from proto");
+ }
+
+ if (quantileDigest.HasExtension(NProto::TTDigest::t_digest)) {
+ const auto& protoTDigest = quantileDigest.GetExtension(NProto::TTDigest::t_digest);
+ return New<TTDigestAdaptor>(protoTDigest);
+ }
+
+ THROW_ERROR_EXCEPTION("No suitable quantile digest extensions found");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/quantile_digest/quantile_digest.h b/yt/yt/library/quantile_digest/quantile_digest.h
new file mode 100644
index 0000000000..67b849ea45
--- /dev/null
+++ b/yt/yt/library/quantile_digest/quantile_digest.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "public.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct IQuantileDigest
+ : public TRefCounted
+{
+ virtual void AddValue(double value) = 0;
+
+ virtual i64 GetCount() const = 0;
+
+ virtual double GetQuantile(double quantile) = 0;
+
+ virtual double GetRank(double value) = 0;
+
+ virtual TString Serialize() = 0;
+};
+
+DEFINE_REFCOUNTED_TYPE(IQuantileDigest)
+
+////////////////////////////////////////////////////////////////////////////////
+
+IQuantileDigestPtr CreateTDigest(const TTDigestConfigPtr& config);
+
+////////////////////////////////////////////////////////////////////////////////
+
+IQuantileDigestPtr LoadQuantileDigest(TStringBuf serialized);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/library/quantile_digest/ya.make b/yt/yt/library/quantile_digest/ya.make
new file mode 100644
index 0000000000..134761bf01
--- /dev/null
+++ b/yt/yt/library/quantile_digest/ya.make
@@ -0,0 +1,18 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ config.cpp
+ quantile_digest.cpp
+
+ proto/quantile_digest.proto
+)
+
+PEERDIR(
+ library/cpp/yt/memory
+ library/cpp/tdigest
+ yt/yt/core
+)
+
+END()
diff --git a/yt/yt/library/re2/CMakeLists.linux-aarch64.txt b/yt/yt/library/re2/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..6da3363004
--- /dev/null
+++ b/yt/yt/library/re2/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,23 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-re2)
+target_compile_options(yt-library-re2 PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-re2 PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+ contrib-libs-re2
+)
+target_sources(yt-library-re2 PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/re2/re2.cpp
+)
diff --git a/yt/yt/library/re2/CMakeLists.linux-x86_64.txt b/yt/yt/library/re2/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..6da3363004
--- /dev/null
+++ b/yt/yt/library/re2/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,23 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+
+add_library(yt-library-re2)
+target_compile_options(yt-library-re2 PRIVATE
+ -Wdeprecated-this-capture
+)
+target_link_libraries(yt-library-re2 PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt-yt-core
+ contrib-libs-re2
+)
+target_sources(yt-library-re2 PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt/library/re2/re2.cpp
+)
diff --git a/yt/yt/library/re2/CMakeLists.txt b/yt/yt/library/re2/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt/library/re2/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt/library/re2/public.h b/yt/yt/library/re2/public.h
new file mode 100644
index 0000000000..4e67da401e
--- /dev/null
+++ b/yt/yt/library/re2/public.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <yt/yt/core/misc/common.h>
+
+namespace NYT::NRe2 {
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TRe2)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NRe2
diff --git a/yt/yt/library/re2/re2.cpp b/yt/yt/library/re2/re2.cpp
new file mode 100644
index 0000000000..aad2a9fe33
--- /dev/null
+++ b/yt/yt/library/re2/re2.cpp
@@ -0,0 +1,57 @@
+#include "re2.h"
+
+#include <yt/yt/core/yson/consumer.h>
+
+#include <yt/yt/core/ytree/fluent.h>
+
+namespace NYT::NRe2 {
+
+using namespace NYson;
+using namespace NYTree;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TRe2Ptr& re, IYsonConsumer* consumer)
+{
+ if (re) {
+ BuildYsonFluently(consumer)
+ .Value(re->pattern());
+ } else {
+ consumer->OnEntity();
+ }
+}
+
+void Deserialize(TRe2Ptr& re, INodePtr node)
+{
+ if (node->GetType() != ENodeType::Entity) {
+ auto pattern = node->GetValue<TString>();
+ re = New<TRe2>(pattern);
+ if (!re->ok()) {
+ THROW_ERROR_EXCEPTION("Error parsing RE2 regex")
+ << TErrorAttribute("error", re->error());
+ }
+ } else {
+ re.Reset();
+ }
+}
+
+void Deserialize(TRe2Ptr& re, TYsonPullParserCursor* cursor)
+{
+ MaybeSkipAttributes(cursor);
+ if ((*cursor)->GetType() == EYsonItemType::EntityValue) {
+ re.Reset();
+ return;
+ }
+
+ EnsureYsonToken("TRe2", *cursor, EYsonItemType::StringValue);
+ re = New<TRe2>((*cursor)->UncheckedAsString());
+ cursor->Next();
+ if (!re->ok()) {
+ THROW_ERROR_EXCEPTION("Error parsing RE2 regex")
+ << TErrorAttribute("error", re->error());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NRe2
diff --git a/yt/yt/library/re2/re2.h b/yt/yt/library/re2/re2.h
new file mode 100644
index 0000000000..d540dfdd4f
--- /dev/null
+++ b/yt/yt/library/re2/re2.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <contrib/libs/re2/re2/re2.h>
+
+namespace NYT::NRe2 {
+
+using namespace re2;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// We create a ref-counted version of re2 to deal with an issue of regular re2::RE2
+// being not default-constructible which is not convenient when using regexps in
+// YSON-serializable configs.
+
+//! Ref-counted version of re2::RE2.
+class TRe2
+ : public RE2
+ , public TRefCounted
+{
+ using RE2::RE2;
+};
+
+DEFINE_REFCOUNTED_TYPE(TRe2)
+
+void Serialize(const TRe2Ptr& re, NYson::IYsonConsumer* consumer);
+void Deserialize(TRe2Ptr& re, NYTree::INodePtr node);
+void Deserialize(TRe2Ptr& re, NYson::TYsonPullParserCursor* cursor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NRe2
diff --git a/yt/yt/library/re2/ya.make b/yt/yt/library/re2/ya.make
new file mode 100644
index 0000000000..7368950f85
--- /dev/null
+++ b/yt/yt/library/re2/ya.make
@@ -0,0 +1,14 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+SRCS(
+ re2.cpp
+)
+
+PEERDIR(
+ yt/yt/core
+ contrib/libs/re2
+)
+
+END()
diff --git a/yt/yt/library/skiff_ext/parser-inl.h b/yt/yt/library/skiff_ext/parser-inl.h
new file mode 100644
index 0000000000..0b569ed243
--- /dev/null
+++ b/yt/yt/library/skiff_ext/parser-inl.h
@@ -0,0 +1,224 @@
+#ifndef PARSER_INL_H_
+#error "Direct inclusion of this file is not allowed, include parser.h"
+// For the sake of sane code completion.
+#include "parser.h"
+#endif
+
+#include <library/cpp/skiff/skiff.h>
+
+#include <yt/yt/core/concurrency/coroutine.h>
+
+namespace NYT::NSkiffExt {
+
+using namespace NSkiff;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TConsumer>
+class TSkiffMultiTableParser<TConsumer>::TImpl
+{
+public:
+ TImpl(
+ TConsumer* consumer,
+ TSkiffSchemaList skiffSchemaList,
+ const std::vector<TSkiffTableColumnIds>& tablesColumnIds,
+ const TString& rangeIndexColumnName,
+ const TString& rowIndexColumnName)
+ : Consumer_(consumer)
+ , SkiffSchemaList_(std::move(skiffSchemaList))
+ {
+ auto genericTableDescriptions = CreateTableDescriptionList(SkiffSchemaList_, rangeIndexColumnName, rowIndexColumnName);
+ YT_VERIFY(tablesColumnIds.size() == genericTableDescriptions.size());
+
+ for (size_t tableIndex = 0; tableIndex < genericTableDescriptions.size(); ++tableIndex) {
+ YT_VERIFY(tablesColumnIds[tableIndex].DenseFieldColumnIds.size() == genericTableDescriptions[tableIndex].DenseFieldDescriptionList.size());
+ const auto& genericTableDescription = genericTableDescriptions[tableIndex];
+ auto& parserTableDescription = TableDescriptions_.emplace_back();
+ parserTableDescription.HasOtherColumns = genericTableDescription.HasOtherColumns;
+ for (size_t fieldIndex = 0; fieldIndex < genericTableDescription.DenseFieldDescriptionList.size(); ++fieldIndex) {
+ const auto& denseFieldDescription = genericTableDescription.DenseFieldDescriptionList[fieldIndex];
+ parserTableDescription.DenseFields.emplace_back(
+ denseFieldDescription.Name(),
+ denseFieldDescription.ValidatedSimplify(),
+ tablesColumnIds[tableIndex].DenseFieldColumnIds[fieldIndex],
+ denseFieldDescription.IsRequired()
+ );
+ }
+
+ YT_VERIFY(tablesColumnIds[tableIndex].SparseFieldColumnIds.size() == genericTableDescriptions[tableIndex].SparseFieldDescriptionList.size());
+
+ for (size_t fieldIndex = 0;
+ fieldIndex < tablesColumnIds[tableIndex].SparseFieldColumnIds.size();
+ ++fieldIndex)
+ {
+ const auto& fieldDescription = genericTableDescriptions[tableIndex].SparseFieldDescriptionList[fieldIndex];
+ parserTableDescription.SparseFields.emplace_back(
+ fieldDescription.Name(),
+ fieldDescription.ValidatedSimplify(),
+ tablesColumnIds[tableIndex].SparseFieldColumnIds[fieldIndex],
+ true
+ );
+ }
+ }
+ }
+
+ Y_FORCE_INLINE void ParseField(ui16 columnId, const TString& name, EWireType wireType, bool required = false)
+ {
+ if (!required) {
+ ui8 tag = Parser_->ParseVariant8Tag();
+ if (tag == 0) {
+ Consumer_->OnEntity(columnId);
+ return;
+ } else if (tag > 1) {
+ THROW_ERROR_EXCEPTION(
+ "Found bad variant8 tag %Qv when parsing optional field %Qv",
+ tag,
+ name);
+ }
+ }
+ switch (wireType) {
+ case EWireType::Yson32:
+ Consumer_->OnYsonString(Parser_->ParseYson32(), columnId);
+ break;
+ case EWireType::Int64:
+ Consumer_->OnInt64Scalar(Parser_->ParseInt64(), columnId);
+ break;
+ case EWireType::Uint64:
+ Consumer_->OnUint64Scalar(Parser_->ParseUint64(), columnId);
+ break;
+ case EWireType::Double:
+ Consumer_->OnDoubleScalar(Parser_->ParseDouble(), columnId);
+ break;
+ case EWireType::Boolean:
+ Consumer_->OnBooleanScalar(Parser_->ParseBoolean(), columnId);
+ break;
+ case EWireType::String32:
+ Consumer_->OnStringScalar(Parser_->ParseString32(), columnId);
+ break;
+ default:
+ // Other types should be filtered out when we parse skiff schema.
+ YT_ABORT();
+ }
+ }
+
+ void DoParse(IZeroCopyInput* stream)
+ {
+ Parser_ = std::make_unique<TCheckedInDebugSkiffParser>(CreateVariant16Schema(SkiffSchemaList_), stream);
+
+ while (Parser_->HasMoreData()) {
+ auto tag = Parser_->ParseVariant16Tag();
+ if (tag >= TableDescriptions_.size()) {
+ THROW_ERROR_EXCEPTION("Unknown table index varint16 tag")
+ << TErrorAttribute("tag", tag);
+ }
+
+ Consumer_->OnBeginRow(tag);
+
+ for (const auto& field : TableDescriptions_[tag].DenseFields) {
+ ParseField(field.ColumnId, field.Name, field.WireType, field.Required);
+ }
+
+ if (!TableDescriptions_[tag].SparseFields.empty()) {
+ for (auto sparseFieldIdx = Parser_->ParseVariant16Tag();
+ sparseFieldIdx != EndOfSequenceTag<ui16>();
+ sparseFieldIdx = Parser_->ParseVariant16Tag())
+ {
+ if (sparseFieldIdx >= TableDescriptions_[tag].SparseFields.size()) {
+ THROW_ERROR_EXCEPTION("Bad sparse field index %Qv, total sparse field count %Qv",
+ sparseFieldIdx,
+ TableDescriptions_[tag].SparseFields.size());
+ }
+
+ const auto& field = TableDescriptions_[tag].SparseFields[sparseFieldIdx];
+ ParseField(field.ColumnId, field.Name, field.WireType, true);
+ }
+ }
+
+ if (TableDescriptions_[tag].HasOtherColumns) {
+ auto buf = Parser_->ParseYson32();
+ Consumer_->OnOtherColumns(buf);
+ }
+
+ Consumer_->OnEndRow();
+ }
+ }
+
+ ui64 GetReadBytesCount()
+ {
+ return Parser_->GetReadBytesCount();
+ }
+
+private:
+ struct TField
+ {
+ TString Name;
+ EWireType WireType;
+ ui16 ColumnId = 0;
+ bool Required = false;
+
+ TField(TString name, EWireType wireType, ui16 columnId, bool required)
+ : Name(std::move(name))
+ , WireType(wireType)
+ , ColumnId(columnId)
+ , Required(required)
+ { }
+ };
+
+ struct TTableDescription
+ {
+ std::vector<TField> DenseFields;
+ std::vector<TField> SparseFields;
+ bool HasOtherColumns = false;
+ };
+
+ TConsumer* const Consumer_;
+ TSkiffSchemaList SkiffSchemaList_;
+
+ std::unique_ptr<TCheckedInDebugSkiffParser> Parser_;
+ std::vector<TTableDescription> TableDescriptions_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TConsumer>
+TSkiffMultiTableParser<TConsumer>::TSkiffMultiTableParser(
+ TConsumer* consumer,
+ TSkiffSchemaList schemaList,
+ const std::vector<TSkiffTableColumnIds>& tablesColumnIds,
+ const TString& rangeIndexColumnName,
+ const TString& rowIndexColumnName)
+ : ParserImpl_(new TImpl(consumer,
+ schemaList,
+ tablesColumnIds,
+ rangeIndexColumnName,
+ rowIndexColumnName))
+ , ParserCoroPipe_(BIND([this] (IZeroCopyInput* stream) {
+ ParserImpl_->DoParse(stream);
+ }))
+{ }
+
+template <class TConsumer>
+TSkiffMultiTableParser<TConsumer>::~TSkiffMultiTableParser()
+{ }
+
+template <class TConsumer>
+void TSkiffMultiTableParser<TConsumer>::Read(TStringBuf data)
+{
+ ParserCoroPipe_.Feed(data);
+}
+
+template <class TConsumer>
+void TSkiffMultiTableParser<TConsumer>::Finish()
+{
+ ParserCoroPipe_.Finish();
+}
+
+template <class TConsumer>
+ui64 TSkiffMultiTableParser<TConsumer>::GetReadBytesCount()
+{
+ return ParserImpl_->GetReadBytesCount();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSkiffExt
diff --git a/yt/yt/library/skiff_ext/parser.h b/yt/yt/library/skiff_ext/parser.h
new file mode 100644
index 0000000000..d9fb4c43fa
--- /dev/null
+++ b/yt/yt/library/skiff_ext/parser.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <library/cpp/skiff/skiff_schema.h>
+
+#include <yt/yt/core/concurrency/coroutine.h>
+#include <yt/yt/core/misc/coro_pipe.h>
+
+#include <util/generic/buffer.h>
+
+namespace NYT::NSkiffExt {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TConsumer>
+class TSkiffMultiTableParser
+{
+public:
+ TSkiffMultiTableParser(
+ TConsumer* consumer,
+ NSkiff::TSkiffSchemaList schemaList,
+ const std::vector<TSkiffTableColumnIds>& tablesColumnIds,
+ const TString& rangeIndexColumnName,
+ const TString& rowIndexColumnName);
+
+ ~TSkiffMultiTableParser();
+
+ void Read(TStringBuf data);
+ void Finish();
+
+ ui64 GetReadBytesCount();
+
+private:
+ class TImpl;
+ std::unique_ptr<TImpl> ParserImpl_;
+
+ TCoroPipe ParserCoroPipe_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSkiffExt
+
+#define PARSER_INL_H_
+#include "parser-inl.h"
+#undef PARSER_INL_H_
diff --git a/yt/yt/library/skiff_ext/public.h b/yt/yt/library/skiff_ext/public.h
new file mode 100644
index 0000000000..4379bec1f7
--- /dev/null
+++ b/yt/yt/library/skiff_ext/public.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <yt/yt/core/misc/public.h>
+
+#include <library/cpp/skiff/public.h>
+
+namespace NYT::NSkiffExt {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TSkiffConsumerBase;
+
+struct TParserTableDescription;
+struct TParserFieldInfo;
+
+class TFieldDescription;
+
+struct TSkiffTableDescription;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSkiffExt
diff --git a/yt/yt/library/skiff_ext/schema_match.cpp b/yt/yt/library/skiff_ext/schema_match.cpp
new file mode 100644
index 0000000000..3caca87ff1
--- /dev/null
+++ b/yt/yt/library/skiff_ext/schema_match.cpp
@@ -0,0 +1,368 @@
+#include "schema_match.h"
+
+#include "serialize.h"
+
+#include <yt/yt/core/ytree/node.h>
+#include <yt/yt/core/ytree/serialize.h>
+#include <yt/yt/core/ytree/yson_struct.h>
+
+namespace NYT::NSkiffExt {
+
+using namespace NYson;
+using namespace NYTree;
+using namespace NSkiff;
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TString KeySwitchColumnName = "$key_switch";
+const TString OtherColumnsName = "$other_columns";
+const TString SparseColumnsName = "$sparse_columns";
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void ThrowInvalidSkiffTypeError(const TString& columnName, std::shared_ptr<TSkiffSchema> expectedType, std::shared_ptr<TSkiffSchema> actualType)
+{
+ THROW_ERROR_EXCEPTION("Column %Qv has unexpected Skiff type: expected %Qv, found type %Qv",
+ columnName,
+ GetShortDebugString(expectedType),
+ GetShortDebugString(actualType));
+}
+
+static ERowRangeIndexMode GetRowRangeIndexMode(const std::shared_ptr<TSkiffSchema>& skiffSchema, TStringBuf columnName)
+{
+ auto throwRowRangeIndexError = [&] {
+ THROW_ERROR_EXCEPTION("Column %Qv has unsupported Skiff type %Qv",
+ columnName,
+ GetShortDebugString(skiffSchema));
+ };
+
+ if (skiffSchema->GetWireType() != EWireType::Variant8) {
+ throwRowRangeIndexError();
+ }
+
+ std::vector<EWireType> children;
+ for (const auto& child : skiffSchema->GetChildren()) {
+ children.emplace_back(child->GetWireType());
+ }
+
+ if (children == std::vector{EWireType::Nothing, EWireType::Int64}) {
+ return ERowRangeIndexMode::Incremental;
+ } else if (children == std::vector{EWireType::Nothing, EWireType::Int64, EWireType::Nothing}) {
+ return ERowRangeIndexMode::IncrementalWithError;
+ }
+ throwRowRangeIndexError();
+ Y_UNREACHABLE();
+}
+
+static bool IsSkiffSpecialColumn(
+ TStringBuf columnName,
+ const TString& rangeIndexColumnName,
+ const TString& rowIndexColumnName)
+{
+ static const THashSet<TString> specialColumns = {
+ KeySwitchColumnName,
+ OtherColumnsName,
+ SparseColumnsName
+ };
+ return specialColumns.contains(columnName) || columnName == rangeIndexColumnName || columnName == rowIndexColumnName;
+}
+
+static std::pair<std::shared_ptr<TSkiffSchema>, bool> DeoptionalizeSchema(std::shared_ptr<TSkiffSchema> skiffSchema)
+{
+ if (skiffSchema->GetWireType() != EWireType::Variant8) {
+ return std::make_pair(skiffSchema, true);
+ }
+ auto children = skiffSchema->GetChildren();
+ if (children.size() != 2) {
+ return std::make_pair(skiffSchema, true);
+ }
+ if (children[0]->GetWireType() == EWireType::Nothing) {
+ return std::make_pair(children[1], false);
+ } else {
+ return std::make_pair(skiffSchema, true);
+ }
+}
+
+static TSkiffTableDescription CreateTableDescription(
+ const std::shared_ptr<TSkiffSchema>& skiffSchema,
+ const TString& rangeIndexColumnName,
+ const TString& rowIndexColumnName)
+{
+ TSkiffTableDescription result;
+ THashSet<TString> topLevelNames;
+ std::shared_ptr<TSkiffSchema> otherColumnsField;
+ std::shared_ptr<TSkiffSchema> sparseColumnsField;
+
+ if (skiffSchema->GetWireType() != EWireType::Tuple) {
+ THROW_ERROR_EXCEPTION("Invalid wire type for table row: expected %Qlv, found %Qlv",
+ EWireType::Tuple,
+ skiffSchema->GetWireType());
+ }
+
+ auto children = skiffSchema->GetChildren();
+
+ if (!children.empty() && children.back()->GetName() == OtherColumnsName) {
+ otherColumnsField = children.back();
+ result.HasOtherColumns = true;
+ children.pop_back();
+
+ if (otherColumnsField->GetWireType() != EWireType::Yson32) {
+ THROW_ERROR_EXCEPTION("Invalid wire type for column %Qv: expected %Qlv, found %Qlv",
+ OtherColumnsName,
+ EWireType::Yson32,
+ otherColumnsField->GetWireType());
+ }
+ }
+
+ if (!children.empty() && children.back()->GetName() == SparseColumnsName) {
+ sparseColumnsField = children.back();
+ children.pop_back();
+ if (sparseColumnsField->GetWireType() != EWireType::RepeatedVariant16) {
+ THROW_ERROR_EXCEPTION("Invalid wire type for column %Qv: expected %Qlv, found %Qlv",
+ SparseColumnsName,
+ EWireType::RepeatedVariant16,
+ sparseColumnsField->GetWireType());
+ }
+ }
+
+ // Dense fields.
+ for (size_t i = 0; i != children.size(); ++i) {
+ const auto& child = children[i];
+ const auto& childName = child->GetName();
+ if (childName.empty()) {
+ THROW_ERROR_EXCEPTION("Element #%v of row Skiff schema must have a name",
+ i);
+ } else if (childName == OtherColumnsName || childName == SparseColumnsName) {
+ THROW_ERROR_EXCEPTION("Invalid placement of special column %Qv",
+ childName);
+ }
+ auto res = topLevelNames.emplace(childName);
+ if (!res.second) {
+ THROW_ERROR_EXCEPTION("Name %Qv is found multiple times",
+ childName);
+ }
+ if (childName == KeySwitchColumnName) {
+ if (child->GetWireType() != EWireType::Boolean) {
+ ThrowInvalidSkiffTypeError(
+ childName,
+ CreateSimpleTypeSchema(EWireType::Boolean),
+ child);
+ }
+ result.KeySwitchFieldIndex = i;
+ } else if (childName == rowIndexColumnName) {
+ result.RowIndexFieldIndex = i;
+ result.RowIndexMode = GetRowRangeIndexMode(child, childName);
+ } else if (childName == rangeIndexColumnName) {
+ result.RangeIndexFieldIndex = i;
+ result.RangeIndexMode = GetRowRangeIndexMode(child, childName);
+ }
+ result.DenseFieldDescriptionList.emplace_back(childName, child);
+ }
+
+ // Sparse fields.
+ if (sparseColumnsField) {
+ for (const auto& child : sparseColumnsField->GetChildren()) {
+ const auto& name = child->GetName();
+ if (name.empty()) {
+ THROW_ERROR_EXCEPTION("Children of %Qv must have nonempty name",
+ SparseColumnsName);
+ }
+ if (IsSkiffSpecialColumn(name, rangeIndexColumnName, rowIndexColumnName)) {
+ THROW_ERROR_EXCEPTION("Skiff special column %Qv cannot be a child of %Qv",
+ name,
+ SparseColumnsName);
+ }
+ auto res = topLevelNames.emplace(name);
+ if (!res.second) {
+ THROW_ERROR_EXCEPTION("Name %Qv is found multiple times",
+ name);
+ }
+ result.SparseFieldDescriptionList.emplace_back(name, child);
+ }
+ }
+
+ return result;
+}
+
+std::vector<TSkiffTableDescription> CreateTableDescriptionList(
+ const std::vector<std::shared_ptr<TSkiffSchema>>& skiffSchemas,
+ const TString& rangeIndexColumnName,
+ const TString& rowIndexColumnName)
+{
+ std::vector<TSkiffTableDescription> result;
+ for (ui16 index = 0; index < skiffSchemas.size(); ++index) {
+ result.emplace_back(CreateTableDescription(
+ skiffSchemas[index],
+ rangeIndexColumnName,
+ rowIndexColumnName));
+ }
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr char ReferencePrefix = '$';
+
+////////////////////////////////////////////////////////////////////////////////
+
+DECLARE_REFCOUNTED_CLASS(TSkiffSchemaRepresentation)
+
+class TSkiffSchemaRepresentation
+ : public TYsonStruct
+{
+public:
+ TString Name;
+ EWireType WireType;
+ std::optional<std::vector<INodePtr>> Children;
+
+ REGISTER_YSON_STRUCT(TSkiffSchemaRepresentation);
+
+ static void Register(TRegistrar registrar) {
+ registrar.Parameter("name", &TThis::Name)
+ .Default();
+ registrar.Parameter("wire_type", &TThis::WireType);
+ registrar.Parameter("children", &TThis::Children)
+ .Default();
+ }
+};
+
+DEFINE_REFCOUNTED_TYPE(TSkiffSchemaRepresentation)
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::shared_ptr<TSkiffSchema> ParseSchema(
+ const INodePtr& schemaNode,
+ const IMapNodePtr& registry,
+ THashMap<TString, std::shared_ptr<TSkiffSchema>>* parsedRegistry,
+ THashSet<TString>* parseInProgressNames)
+{
+ auto schemaNodeType = schemaNode->GetType();
+ if (schemaNodeType == ENodeType::String) {
+ auto name = schemaNode->AsString()->GetValue();
+ if (!name.StartsWith(ReferencePrefix)) {
+ THROW_ERROR_EXCEPTION(
+ "Invalid reference %Qv, reference must start with %Qv",
+ name,
+ ReferencePrefix);
+ }
+ name = name.substr(1);
+ auto it = parsedRegistry->find(name);
+ if (it != parsedRegistry->end()) {
+ return it->second;
+ } else if (parseInProgressNames->contains(name)) {
+ THROW_ERROR_EXCEPTION(
+ "Type %Qv is recursive, recursive types are forbidden",
+ name);
+ } else {
+ auto schemaFromRegistry = registry->FindChild(name);
+ if (!schemaFromRegistry) {
+ THROW_ERROR_EXCEPTION(
+ "Cannot resolve type reference %Qv",
+ name);
+ }
+ parseInProgressNames->insert(name);
+ auto result = ParseSchema(schemaFromRegistry, registry, parsedRegistry, parseInProgressNames);
+ parseInProgressNames->erase(name);
+ parsedRegistry->emplace(name, result);
+ return result;
+ }
+ } else if (schemaNodeType == ENodeType::Map) {
+ auto schemaMapNode = schemaNode->AsMap();
+ auto schemaRepresentation = ConvertTo<TSkiffSchemaRepresentationPtr>(schemaMapNode);
+ if (IsSimpleType(schemaRepresentation->WireType)) {
+ return CreateSimpleTypeSchema(schemaRepresentation->WireType)->SetName(schemaRepresentation->Name);
+ } else {
+ if (!schemaRepresentation->Children) {
+ THROW_ERROR_EXCEPTION(
+ "Complex type %Qlv lacks children",
+ schemaRepresentation->WireType);
+ }
+ std::vector<std::shared_ptr<TSkiffSchema>> childSchemaList;
+ for (const auto& childNode : *schemaRepresentation->Children) {
+ auto childSchema = ParseSchema(childNode, registry, parsedRegistry, parseInProgressNames);
+ childSchemaList.push_back(childSchema);
+ }
+
+ switch (schemaRepresentation->WireType) {
+ case EWireType::Variant8:
+ return CreateVariant8Schema(childSchemaList)->SetName(schemaRepresentation->Name);
+ case EWireType::Variant16:
+ return CreateVariant16Schema(childSchemaList)->SetName(schemaRepresentation->Name);
+ case EWireType::RepeatedVariant8:
+ return CreateRepeatedVariant8Schema(childSchemaList)->SetName(schemaRepresentation->Name);
+ case EWireType::RepeatedVariant16:
+ return CreateRepeatedVariant16Schema(childSchemaList)->SetName(schemaRepresentation->Name);
+ case EWireType::Tuple:
+ return CreateTupleSchema(childSchemaList)->SetName(schemaRepresentation->Name);
+ default:
+ YT_ABORT();
+ }
+ }
+ } else {
+ THROW_ERROR_EXCEPTION(
+ "Invalid type for Skiff schema description; expected %Qlv or %Qlv, found %Qlv",
+ ENodeType::Map,
+ ENodeType::String,
+ schemaNodeType
+ );
+ }
+}
+
+std::vector<std::shared_ptr<TSkiffSchema>> ParseSkiffSchemas(
+ const NYTree::IMapNodePtr& skiffSchemaRegistry,
+ const NYTree::IListNodePtr& tableSkiffSchemas)
+{
+ THashMap<TString, std::shared_ptr<TSkiffSchema>> parsedRegistry;
+ std::vector<std::shared_ptr<TSkiffSchema>> result;
+ for (const auto& node : tableSkiffSchemas->GetChildren()) {
+ THashSet<TString> parseInProgressNames;
+ auto skiffSchema = ParseSchema(node, skiffSchemaRegistry, &parsedRegistry, &parseInProgressNames);
+ result.push_back(skiffSchema);
+ }
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TFieldDescription::TFieldDescription(TString name, std::shared_ptr<TSkiffSchema> schema)
+ : Name_(std::move(name))
+ , Schema_(std::move(schema))
+{ }
+
+EWireType TFieldDescription::ValidatedSimplify() const
+{
+ auto result = Simplify();
+ if (!result) {
+ THROW_ERROR_EXCEPTION("Column %Qv cannot be represented with Skiff schema %Qv",
+ Name_,
+ GetShortDebugString(Schema_));
+ }
+ return *result;
+}
+
+bool TFieldDescription::IsNullable() const
+{
+ return !IsRequired();
+}
+
+bool TFieldDescription::IsRequired() const
+{
+ return DeoptionalizeSchema(Schema_).second;
+}
+
+std::optional<EWireType> TFieldDescription::Simplify() const
+{
+ const auto& [deoptionalized, required] = DeoptionalizeSchema(Schema_);
+ auto wireType = deoptionalized->GetWireType();
+ if (IsSimpleType(wireType)) {
+ if (wireType != EWireType::Nothing || required) {
+ return wireType;
+ }
+ }
+ return std::nullopt;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSkiffExt
diff --git a/yt/yt/library/skiff_ext/schema_match.h b/yt/yt/library/skiff_ext/schema_match.h
new file mode 100644
index 0000000000..dc5a6c2dbc
--- /dev/null
+++ b/yt/yt/library/skiff_ext/schema_match.h
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/misc/property.h>
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <library/cpp/skiff/skiff_schema.h>
+
+namespace NYT::NSkiffExt {
+
+////////////////////////////////////////////////////////////////////////////////
+
+extern const TString SparseColumnsName;
+extern const TString OtherColumnsName;
+extern const TString KeySwitchColumnName;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ERowRangeIndexMode,
+ (Incremental)
+ (IncrementalWithError)
+);
+
+class TFieldDescription
+{
+public:
+ DEFINE_BYREF_RO_PROPERTY(TString, Name);
+ DEFINE_BYREF_RO_PROPERTY(std::shared_ptr<NSkiff::TSkiffSchema>, Schema);
+
+public:
+ TFieldDescription(TString name, std::shared_ptr<NSkiff::TSkiffSchema> schema);
+
+ bool IsRequired() const;
+ bool IsNullable() const;
+ std::optional<NSkiff::EWireType> Simplify() const;
+ NSkiff::EWireType ValidatedSimplify() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSkiffTableDescription
+{
+ // Dense fields of the row.
+ std::vector<TFieldDescription> DenseFieldDescriptionList;
+
+ // Sparse fields of the row.
+ std::vector<TFieldDescription> SparseFieldDescriptionList;
+
+ // Indexes of $key_switch/$row_index/$range_index field inside dense part of the row.
+ std::optional<size_t> KeySwitchFieldIndex;
+
+ std::optional<size_t> RowIndexFieldIndex;
+ std::optional<size_t> RangeIndexFieldIndex;
+
+ // $row_index/$range_index field can be written in several modes.
+ ERowRangeIndexMode RowIndexMode = ERowRangeIndexMode::Incremental;
+ ERowRangeIndexMode RangeIndexMode = ERowRangeIndexMode::Incremental;
+
+ // Whether or not row contains $other_columns field.
+ bool HasOtherColumns = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TSkiffTableColumnIds
+{
+ std::vector<ui16> DenseFieldColumnIds;
+ std::vector<ui16> SparseFieldColumnIds;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::vector<TSkiffTableDescription> CreateTableDescriptionList(
+ const std::vector<std::shared_ptr<NSkiff::TSkiffSchema>>& skiffSchema,
+ const TString& rangeIndexColumnName,
+ const TString& rowIndexColumnName);
+
+std::vector<std::shared_ptr<NSkiff::TSkiffSchema>> ParseSkiffSchemas(
+ const NYTree::IMapNodePtr& skiffSchemaRegistry,
+ const NYTree::IListNodePtr& tableSkiffSchemas);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NSkiffExt
diff --git a/yt/yt/library/skiff_ext/serialize.cpp b/yt/yt/library/skiff_ext/serialize.cpp
new file mode 100644
index 0000000000..191210f69d
--- /dev/null
+++ b/yt/yt/library/skiff_ext/serialize.cpp
@@ -0,0 +1,38 @@
+#include "serialize.h"
+
+#include <yt/yt/core/yson/pull_parser.h>
+#include <yt/yt/core/yson/pull_parser_deserialize.h>
+
+#include <yt/yt/core/ytree/node.h>
+#include <yt/yt/core/ytree/convert.h>
+
+namespace NSkiff {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(EWireType wireType, NYT::NYson::IYsonConsumer* consumer)
+{
+ consumer->OnStringScalar(::ToString(wireType));
+}
+
+void Deserialize(EWireType& wireType, NYT::NYTree::INodePtr node)
+{
+ if (node->GetType() != NYT::NYTree::ENodeType::String) {
+ THROW_ERROR_EXCEPTION("Cannot deserialize Skiff wire type from %Qlv node, expected %Qlv",
+ node->GetType(),
+ NYT::NYTree::ENodeType::String);
+ }
+ wireType = ::FromString<EWireType>(node->GetValue<TString>());
+}
+
+void Deserialize(EWireType& wireType, NYT::NYson::TYsonPullParserCursor* cursor)
+{
+ NYT::NYson::MaybeSkipAttributes(cursor);
+ NYT::NYson::EnsureYsonToken("Skiff wire type", *cursor, NYT::NYson::EYsonItemType::StringValue);
+ wireType = ::FromString<EWireType>((*cursor)->UncheckedAsString());
+ cursor->Next();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NSkiff
diff --git a/yt/yt/library/skiff_ext/serialize.h b/yt/yt/library/skiff_ext/serialize.h
new file mode 100644
index 0000000000..0124702556
--- /dev/null
+++ b/yt/yt/library/skiff_ext/serialize.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/public.h>
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <library/cpp/skiff/public.h>
+
+namespace NSkiff {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(EWireType wireType, NYT::NYson::IYsonConsumer* consumer);
+void Deserialize(EWireType& wireType, NYT::NYTree::INodePtr node);
+void Deserialize(EWireType& wireType, NYT::NYson::TYsonPullParserCursor* cursor);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NSkiff
diff --git a/yt/yt/library/skiff_ext/ya.make b/yt/yt/library/skiff_ext/ya.make
new file mode 100644
index 0000000000..feb40ff631
--- /dev/null
+++ b/yt/yt/library/skiff_ext/ya.make
@@ -0,0 +1,16 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/yt/ya_cpp.make.inc)
+
+
+SRCS(
+ serialize.cpp
+ schema_match.cpp
+)
+
+PEERDIR(
+ yt/yt/core
+ library/cpp/skiff
+)
+
+END()
diff --git a/yt/yt_proto/yt/CMakeLists.darwin-x86_64.txt b/yt/yt_proto/yt/CMakeLists.darwin-x86_64.txt
new file mode 100644
index 0000000000..d8f6bf9771
--- /dev/null
+++ b/yt/yt_proto/yt/CMakeLists.darwin-x86_64.txt
@@ -0,0 +1,10 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(core)
+add_subdirectory(formats)
diff --git a/yt/yt_proto/yt/CMakeLists.linux-aarch64.txt b/yt/yt_proto/yt/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..aa7a2b73aa
--- /dev/null
+++ b/yt/yt_proto/yt/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,11 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(client)
+add_subdirectory(core)
+add_subdirectory(formats)
diff --git a/yt/yt_proto/yt/CMakeLists.linux-x86_64.txt b/yt/yt_proto/yt/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..aa7a2b73aa
--- /dev/null
+++ b/yt/yt_proto/yt/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,11 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(client)
+add_subdirectory(core)
+add_subdirectory(formats)
diff --git a/yt/yt_proto/yt/CMakeLists.txt b/yt/yt_proto/yt/CMakeLists.txt
index d8f6bf9771..f8b31df0c1 100644
--- a/yt/yt_proto/yt/CMakeLists.txt
+++ b/yt/yt_proto/yt/CMakeLists.txt
@@ -6,5 +6,12 @@
# original buildsystem will not be accepted.
-add_subdirectory(core)
-add_subdirectory(formats)
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ include(CMakeLists.darwin-x86_64.txt)
+elseif (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" AND NOT HAVE_CUDA)
+ include(CMakeLists.windows-x86_64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt_proto/yt/CMakeLists.windows-x86_64.txt b/yt/yt_proto/yt/CMakeLists.windows-x86_64.txt
new file mode 100644
index 0000000000..d8f6bf9771
--- /dev/null
+++ b/yt/yt_proto/yt/CMakeLists.windows-x86_64.txt
@@ -0,0 +1,10 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+add_subdirectory(core)
+add_subdirectory(formats)
diff --git a/yt/yt_proto/yt/client/CMakeLists.linux-aarch64.txt b/yt/yt_proto/yt/client/CMakeLists.linux-aarch64.txt
new file mode 100644
index 0000000000..b21f7290e7
--- /dev/null
+++ b/yt/yt_proto/yt/client/CMakeLists.linux-aarch64.txt
@@ -0,0 +1,297 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+
+add_library(yt_proto-yt-client)
+target_include_directories(yt_proto-yt-client PUBLIC
+ ${CMAKE_BINARY_DIR}/yt
+)
+target_link_libraries(yt_proto-yt-client PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt_proto-yt-core
+ contrib-libs-protobuf
+)
+target_proto_messages(yt_proto-yt-client PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/api/rpc_proxy/proto/discovery_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/cell_master/proto/cell_directory.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chaos_client/proto/replication_card.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/data_statistics.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/chunk_meta.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/read_limit.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/confirm_chunk_replica_info.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/discovery_client/proto/discovery_client_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/hive/proto/timestamp_map.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/hive/proto/cluster_directory.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/node_tracker_client/proto/node.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/table_chunk_format/proto/column_meta.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/transaction_client/proto/timestamp_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/query_client/proto/query_statistics.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/misc/proto/workload.proto
+)
+target_proto_addincls(yt_proto-yt-client
+ ./yt
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+)
+target_proto_outs(yt_proto-yt-client
+ --cpp_out=${CMAKE_BINARY_DIR}/yt
+ --cpp_styleguide_out=${CMAKE_BINARY_DIR}/yt
+)
diff --git a/yt/yt_proto/yt/client/CMakeLists.linux-x86_64.txt b/yt/yt_proto/yt/client/CMakeLists.linux-x86_64.txt
new file mode 100644
index 0000000000..b21f7290e7
--- /dev/null
+++ b/yt/yt_proto/yt/client/CMakeLists.linux-x86_64.txt
@@ -0,0 +1,297 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+get_built_tool_path(
+ TOOL_protoc_bin
+ TOOL_protoc_dependency
+ contrib/tools/protoc/bin
+ protoc
+)
+get_built_tool_path(
+ TOOL_cpp_styleguide_bin
+ TOOL_cpp_styleguide_dependency
+ contrib/tools/protoc/plugins/cpp_styleguide
+ cpp_styleguide
+)
+
+add_library(yt_proto-yt-client)
+target_include_directories(yt_proto-yt-client PUBLIC
+ ${CMAKE_BINARY_DIR}/yt
+)
+target_link_libraries(yt_proto-yt-client PUBLIC
+ contrib-libs-linux-headers
+ contrib-libs-cxxsupp
+ yutil
+ yt_proto-yt-core
+ contrib-libs-protobuf
+)
+target_proto_messages(yt_proto-yt-client PRIVATE
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/api/rpc_proxy/proto/discovery_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/cell_master/proto/cell_directory.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chaos_client/proto/replication_card.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/data_statistics.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/chunk_meta.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/read_limit.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/chunk_client/proto/confirm_chunk_replica_info.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/discovery_client/proto/discovery_client_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/hive/proto/timestamp_map.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/hive/proto/cluster_directory.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/node_tracker_client/proto/node.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/table_chunk_format/proto/column_meta.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/transaction_client/proto/timestamp_service.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/query_client/proto/query_statistics.proto
+ ${CMAKE_SOURCE_DIR}/yt/yt_proto/yt/client/misc/proto/workload.proto
+)
+target_proto_addincls(yt_proto-yt-client
+ ./yt
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_SOURCE_DIR}/yt
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/contrib/libs/protobuf/src
+)
+target_proto_outs(yt_proto-yt-client
+ --cpp_out=${CMAKE_BINARY_DIR}/yt
+ --cpp_styleguide_out=${CMAKE_BINARY_DIR}/yt
+)
diff --git a/yt/yt_proto/yt/client/CMakeLists.txt b/yt/yt_proto/yt/client/CMakeLists.txt
new file mode 100644
index 0000000000..4d48dcdee6
--- /dev/null
+++ b/yt/yt_proto/yt/client/CMakeLists.txt
@@ -0,0 +1,13 @@
+
+# This file was generated by the build system used internally in the Yandex monorepo.
+# Only simple modifications are allowed (adding source-files to targets, adding simple properties
+# like target_include_directories). These modifications will be ported to original
+# ya.make files by maintainers. Any complex modifications which can't be ported back to the
+# original buildsystem will not be accepted.
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-aarch64.txt)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT HAVE_CUDA)
+ include(CMakeLists.linux-x86_64.txt)
+endif()
diff --git a/yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.proto b/yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.proto
new file mode 100644
index 0000000000..4fbab6b571
--- /dev/null
+++ b/yt/yt_proto/yt/client/api/rpc_proxy/proto/api_service.proto
@@ -0,0 +1,2774 @@
+package NYT.NApi.NRpcProxy.NProto;
+
+option java_package = "tech.ytsaurus.rpcproxy";
+option java_outer_classname = "ApiProtos";
+option java_multiple_files = true;
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/api/rpc_proxy";
+
+import "yt_proto/yt/core/misc/proto/guid.proto";
+import "yt_proto/yt/core/misc/proto/error.proto";
+import "yt_proto/yt/core/ytree/proto/attributes.proto";
+import "yt_proto/yt/client/hive/proto/timestamp_map.proto";
+import "yt_proto/yt/client/chunk_client/proto/data_statistics.proto";
+import "yt_proto/yt/client/chaos_client/proto/replication_card.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+// Scalars
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+ TDurations are serialized as uint64, in microseconds.
+ TInstants are serialized as uint64, in microseconds since unix epoch.
+*/
+
+enum ETransactionType
+{
+ TT_MASTER = 0;
+ TT_TABLET = 1;
+}
+
+enum ERowModificationType
+{
+ RMT_WRITE = 0;
+ RMT_DELETE = 1;
+ RMT_MODIFY = 3;
+}
+
+enum EAtomicity
+{
+ A_FULL = 0;
+ A_NONE = 1;
+}
+
+enum EDurability
+{
+ D_SYNC = 0;
+ D_ASYNC = 1;
+}
+
+enum ETableReplicaMode
+{
+ TRM_SYNC = 0;
+ TRM_ASYNC = 1;
+ TRM_ASYNC_TO_SYNC = 2;
+ TRM_SYNC_TO_SYNC = 3;
+}
+
+enum EReplicaConsistency
+{
+ RRM_NONE = 0;
+ RRM_SYNC = 1;
+}
+
+enum EMasterReadKind
+{
+ MRK_LEADER = 0;
+ MRK_FOLLOWER = 1;
+ MRK_CACHE = 2;
+ MRK_MASTER_CACHE = 3;
+}
+
+enum ERowsetKind
+{
+ RK_UNVERSIONED = 1;
+ RK_VERSIONED = 2;
+}
+
+enum ERowsetFormat
+{
+ RF_YT_WIRE = 0;
+ RF_ARROW = 1;
+ RF_FORMAT = 2;
+}
+
+enum ETabletReadKind
+{
+ TRK_LEADER = 0;
+ TRK_FOLLOWER = 1;
+ TRK_LEADER_OR_FOLLOWER = 2;
+}
+
+enum EOperationType
+{
+ OT_UNKNOWN = 100;
+
+ OT_MAP = 0;
+ OT_MERGE = 1;
+ OT_ERASE = 2;
+ OT_SORT = 3;
+ OT_REDUCE = 4;
+ OT_MAP_REDUCE = 5;
+ OT_REMOTE_COPY = 6;
+ OT_JOIN_REDUCE = 7;
+ OT_VANILLA = 8;
+}
+
+enum EOperationState
+{
+ OS_UNKNOWN = 100;
+
+ OS_NONE = 0;
+ OS_STARTING = 1;
+ OS_ORPHANED = 2;
+ OS_WAITING_FOR_AGENT = 3;
+ OS_INITIALIZING = 4;
+ OS_PREPARING = 5;
+ OS_MATERIALIZING = 6;
+ OS_REVIVING = 7;
+ OS_REVIVING_JOBS = 8;
+ OS_PENDING = 9;
+ OS_RUNNING = 10;
+ OS_COMPLETING = 11;
+ OS_COMPLETED = 12;
+ OS_ABORTING = 13;
+ OS_ABORTED = 14;
+ OS_FAILING = 15;
+ OS_FAILED = 16;
+ OS_REVIVE_INITIALIZING = 17;
+}
+
+enum EOperationSortDirection
+{
+ OSD_NONE = 0;
+ OSD_PAST = 1;
+ OSD_FUTURE = 2;
+}
+
+enum EJobType
+{
+ JT_UNKNOWN = 1000;
+
+ JT_MAP = 1;
+ JT_PARTITION_MAP = 2;
+ JT_SORTED_MERGE = 3;
+ JT_ORDERED_MERGE = 4;
+ JT_UNORDERED_MERGE = 5;
+ JT_PARTITION = 6;
+ JT_SIMPLE_SORT = 7;
+ JT_FINAL_SORT = 8;
+ JT_SORTED_REDUCE = 9;
+ JT_PARTITION_REDUCE = 10;
+ JT_REDUCE_COMBINER = 11;
+ JT_REMOTE_COPY = 12;
+ JT_INTERMEDIATE_SORT = 13;
+ JT_ORDERED_MAP = 14;
+ JT_JOIN_REDUCE = 15;
+ JT_VANILLA = 16;
+ JT_SHALLOW_MERGE = 17;
+ JT_SCHEDULER_UNKNOWN = 98;
+
+ // Master jobs
+ JT_REPLICATE_CHUNK = 100;
+ JT_REMOVE_CHUNK = 101;
+ JT_REPAIR_CHUNK = 102;
+ JT_SEAL_CHUNK = 103;
+ JT_MERGE_CHUNKS = 104;
+ JT_AUTOTOMIZE_CHUNK = 105;
+ JT_REINCARNATE_CHUNK = 106;
+}
+
+enum EJobState
+{
+ JS_UNKNOWN = 100;
+
+ JS_WAITING = 0;
+ JS_RUNNING = 1;
+ JS_ABORTING = 2;
+ JS_COMPLETED = 3;
+ JS_FAILED = 4;
+ JS_ABORTED = 5;
+ JS_LOST = 7;
+ JS_NONE = 8;
+}
+
+enum EJobSortField
+{
+ JSF_NONE = 0;
+ JSF_TYPE = 1;
+ JSF_STATE = 2;
+ JSF_START_TIME = 3;
+ JSF_FINISH_TIME = 4;
+ JSF_ADDRESS = 5;
+ JSF_DURATION = 6;
+ JSF_PROGRESS = 7;
+ JSF_ID = 8;
+}
+
+enum EJobSortDirection
+{
+ JSD_ASCENDING = 0;
+ JSD_DESCENDING = 1;
+}
+
+enum EDataSource
+{
+ DS_ARCHIVE = 0;
+ DS_RUNTIME = 1;
+ DS_AUTO = 2;
+ DS_MANUAL = 3;
+}
+
+enum ESecurityAction
+{
+ SA_UNDEFINED = 0;
+ SA_ALLOW = 1;
+ SA_DENY = 2;
+}
+
+enum EMultiplexingBand
+{
+ MB_DEFAULT = 0;
+ MB_CONTROL = 1;
+ MB_HEAVY = 2;
+ MB_INTERACTIVE = 3;
+}
+
+enum ETableSchemaModification
+{
+ TSM_NONE = 0;
+ TSM_UNVERSIONED_UPDATE = 1;
+ TSM_UNVERSIONED_UPDATE_UNSORTED = 2;
+}
+
+enum EColumnarStatisticsFetcherMode
+{
+ CSFM_FROM_NODES = 0;
+ CSFM_FROM_MASTER = 1;
+ CSFM_FALLBACK = 2;
+}
+
+enum EPartitionTablesMode
+{
+ TPM_SORTED = 0;
+ TPM_ORDERED = 1;
+ TPM_UNORDERED = 2;
+}
+
+enum EMaintenanceType
+{
+ MT_BAN = 1;
+ MT_DECOMMISSION = 2;
+ MT_DISABLE_SCHEDULER_JOBS = 3;
+ MT_DISABLE_WRITE_SESSIONS = 4;
+ MT_DISABLE_TABLET_CELLS = 5;
+ MT_PENDING_RESTART = 6;
+}
+
+enum EMaintenanceComponent
+{
+ MC_CLUSTER_NODE = 1;
+ MC_HTTP_PROXY = 2;
+ MC_RPC_PROXY = 3;
+ MC_HOST = 4;
+}
+
+enum EJobSpecSource
+{
+ JSS_NODE = 1;
+ JSS_ARCHIVE = 2;
+ JSS_AUTO = 0xFFFF;
+}
+
+// COMPAT(max42).
+// A legacy analog of NYT.NYTree.NProto.TAttributeFilter.
+// It is different in that universal filter is encoded via all = true
+// rather than absence of filter at all.
+// Modern clients serialize attribute filters using TAttributeFilter, so
+// hopefully in a couple of years we will be able to drop support of it...
+message TLegacyAttributeKeys
+{
+ optional bool all = 1;
+ repeated string keys = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Rowsets.
+////////////////////////////////////////////////////////////////////////////////
+
+// Each rowset is decodeable solely with the descriptor, which encodes rowset kind,
+// column names and column types. Actual data is passed via attachments in the wire
+// protocol.
+message TRowsetDescriptor
+{
+ // Currently, there is only one version. This field is reserved for future changes.
+ optional int32 wire_format_version = 1 [default = 1];
+
+ // Specifies the kind of rows comprising the rowset.
+ optional ERowsetKind rowset_kind = 2 [default = RK_UNVERSIONED];
+
+ // Specifies the format of data representation.
+ optional ERowsetFormat rowset_format = 4 [default = RF_YT_WIRE];
+
+ message TNameTableEntry
+ {
+ optional string name = 1;
+ // COMPAT(babenko)
+ optional int32 type = 2;
+ // COMPAT(babenko)
+ optional int32 logical_type = 3;
+ }
+
+ // Specifies column names.
+ // COMPAT(babenko): also types.
+ repeated TNameTableEntry name_table_entries = 3;
+
+ // Specifies rowset schema.
+ // Could be missing (in this case name table is always present).
+ optional TTableSchema schema = 5;
+}
+
+// Could be embedded into response attachement block sequence along with the actual data
+// to indidate the progress of table reader/writer.
+message TRowsetStatistics
+{
+ required int64 total_row_count = 1;
+ required NYT.NChunkClient.NProto.TDataStatistics data_statistics = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TRANSACTIONS
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqStartTransaction
+{
+ required ETransactionType type = 1;
+
+ optional int64 timeout = 2;
+
+ // If not null then the transaction must use this externally provided id.
+ // Only applicable to tablet transactions.
+ optional NYT.NProto.TGuid id = 3;
+
+ optional NYT.NProto.TGuid parent_id = 4;
+
+ // COMPAT(kiselyovp) next option is unused and should get removed
+ optional bool auto_abort = 5 [default = false];
+ optional bool sticky = 6 [default = false];
+ optional bool ping = 7 [default = true];
+ optional bool ping_ancestors = 8 [default = true];
+
+ optional EAtomicity atomicity = 9 [default = A_FULL];
+ optional EDurability durability = 10 [default = D_SYNC];
+
+ optional NYT.NYTree.NProto.TAttributeDictionary attributes = 11;
+
+ optional uint64 deadline = 12;
+
+ repeated NYT.NProto.TGuid prerequisite_transaction_ids = 13;
+
+ optional uint64 start_timestamp = 14;
+
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspStartTransaction
+{
+ required NYT.NProto.TGuid id = 1;
+ required uint64 start_timestamp = 2;
+ // Sequence number source id allocated for this client if transaction is tablet.
+ // Client is free not to use this source id and generate source id on its own,
+ // for example, for backward compatibility. In this case client is responsible for
+ // source id uniqueness between all transaction clients.
+ // See TReqModifyRows.sequence_number_source_id for details.
+ optional int64 sequence_number_source_id = 3;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqPingTransaction
+{
+ required NYT.NProto.TGuid transaction_id = 1;
+ reserved 6;
+ optional bool ping_ancestors = 7 [default = true];
+}
+
+message TRspPingTransaction
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCommitTransaction
+{
+ required NYT.NProto.TGuid transaction_id = 1;
+ reserved 6;
+ repeated NYT.NProto.TGuid additional_participant_cell_ids = 7;
+
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspCommitTransaction
+{
+ optional NHiveClient.NProto.TTimestampMap commit_timestamps = 1;
+ optional uint64 primary_commit_timestamp = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqFlushTransaction
+{
+ required NYT.NProto.TGuid transaction_id = 1;
+}
+
+message TRspFlushTransaction
+{
+ repeated NYT.NProto.TGuid participant_cell_ids = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAbortTransaction
+{
+ required NYT.NProto.TGuid transaction_id = 1;
+
+ optional TMutatingOptions mutating_options = 103;
+
+ reserved 6;
+}
+
+message TRspAbortTransaction
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqLookupRows
+{
+ required string path = 1;
+
+ optional uint64 timestamp = 3 [default = 0x3fffffffffffff01];
+ optional uint64 retention_timestamp = 10 [default = 0];
+
+ repeated string columns = 2;
+ optional bool keep_missing_rows = 4 [default = true];
+
+ optional bool enable_partial_result = 7 [default = false];
+ optional bool use_lookup_cache = 9 [default = false];
+
+ optional TTabletReadOptions tablet_read_options = 106;
+ optional EReplicaConsistency replica_consistency = 11;
+
+ optional EMultiplexingBand multiplexing_band = 8;
+
+ required TRowsetDescriptor rowset_descriptor = 200;
+
+ reserved 5;
+ reserved 6;
+}
+
+message TRspLookupRows
+{
+ required TRowsetDescriptor rowset_descriptor = 200;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TRetentionConfig
+{
+ optional uint64 min_data_versions = 1 [default = 1];
+ optional uint64 max_data_versions = 2 [default = 1];
+ optional uint64 min_data_ttl = 3 [default = 1800000000]; // TDuration, 30 minutes by default
+ optional uint64 max_data_ttl = 4 [default = 1800000000]; // TDuration, 30 minutes by default
+ optional bool ignore_major_timestamp = 5 [default = false];
+}
+
+message TReqVersionedLookupRows
+{
+ required string path = 1;
+
+ optional uint64 timestamp = 3 [default = 0x3fffffffffffff01];
+
+ repeated string columns = 2;
+ optional bool keep_missing_rows = 4 [default = true];
+
+ optional TRetentionConfig retention_config = 6;
+
+ optional bool enable_partial_result = 7 [default = false];
+ optional bool use_lookup_cache = 9 [default = false];
+
+ optional TTabletReadOptions tablet_read_options = 106;
+ optional EReplicaConsistency replica_consistency = 10;
+
+ optional EMultiplexingBand multiplexing_band = 8;
+
+ required TRowsetDescriptor rowset_descriptor = 200;
+
+ reserved 5;
+}
+
+message TRspVersionedLookupRows
+{
+ required TRowsetDescriptor rowset_descriptor = 200;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqMultiLookup
+{
+ message TSubrequest
+ {
+ required string path = 1;
+
+ repeated string columns = 2;
+ optional bool keep_missing_rows = 3 [default = true];
+
+ optional bool enable_partial_result = 4 [default = false];
+ optional bool use_lookup_cache = 5 [default = false];
+
+ required TRowsetDescriptor rowset_descriptor = 6;
+
+ required int32 attachment_count = 7;
+ }
+
+ repeated TSubrequest subrequests = 1;
+
+ optional uint64 timestamp = 2 [default = 0x3fffffffffffff01];
+ optional uint64 retention_timestamp = 5 [default = 0];
+ optional TTabletReadOptions tablet_read_options = 3;
+ optional EReplicaConsistency replica_consistency = 6;
+ optional EMultiplexingBand multiplexing_band = 4;
+}
+
+message TRspMultiLookup
+{
+ message TSubresponse
+ {
+ required TRowsetDescriptor rowset_descriptor = 1;
+
+ required int32 attachment_count = 2;
+ }
+
+ repeated TSubresponse subresponses = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TQueryStatistics
+{
+ optional int64 rows_read = 1;
+ optional int64 data_weight_read = 2;
+ optional int64 rows_written = 3;
+ optional uint64 sync_time = 4; // TDuration
+ optional uint64 async_time = 5; // TDuration
+ optional uint64 execute_time = 6; // TDuration
+ optional uint64 read_time = 7; // TDuration
+ optional uint64 write_time = 8; // TDuration
+ optional uint64 codegen_time = 9; // TDuration
+ optional uint64 wait_on_ready_event_time = 10; // TDuration
+ optional bool incomplete_input = 11;
+ optional bool incomplete_output = 12;
+ optional uint64 memory_usage = 15;
+
+ repeated TQueryStatistics inner_statistics = 14;
+}
+
+message TReqSelectRows
+{
+ required string query = 1;
+
+ //optional NYT.NProto.TGuid transaction_id = 2;
+ optional uint64 timestamp = 3 [default = 0x3fffffffffffff01];
+ optional uint64 retention_timestamp = 16 [default = 0];
+
+ optional uint64 input_row_limit = 4;
+ optional uint64 output_row_limit = 5;
+ optional uint64 range_expansion_limit = 6;
+ optional bool fail_on_incomplete_result = 7;
+ optional bool verbose_logging = 8;
+ optional bool enable_code_cache = 9;
+ optional int32 max_subqueries = 10;
+ optional bool allow_full_scan = 11;
+ optional bool allow_join_without_index = 12;
+ optional string udf_registry_path = 13;
+ optional uint64 memory_limit_per_node = 14;
+ optional string execution_pool = 15;
+ optional EReplicaConsistency replica_consistency = 17;
+ optional bytes placeholder_values = 18; // YSON
+ optional bool new_range_inference = 19;
+
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+}
+
+message TRspSelectRows
+{
+ required TRowsetDescriptor rowset_descriptor = 200;
+ optional TQueryStatistics statistics = 201;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TRowBatchReadOptions
+{
+ optional int64 max_row_count = 1;
+ optional int64 max_data_weight = 2;
+ optional int64 data_weight_per_row_hint = 3;
+}
+
+message TReqPullQueue
+{
+ optional string queue_path = 1;
+ optional int64 offset = 2;
+ optional int32 partition_index = 3;
+ optional TRowBatchReadOptions row_batch_read_options = 4;
+
+ optional bool use_native_tablet_node_api = 5 [default = false];
+ optional EReplicaConsistency replica_consistency = 6;
+}
+
+message TRspPullQueue
+{
+ optional TRowsetDescriptor rowset_descriptor = 1;
+ optional int64 start_offset = 2;
+}
+
+message TReqPullConsumer
+{
+ optional string consumer_path = 1;
+ optional string queue_path = 2;
+ optional int64 offset = 3;
+ optional int32 partition_index = 4;
+ optional TRowBatchReadOptions row_batch_read_options = 5;
+
+ optional EReplicaConsistency replica_consistency = 6;
+}
+
+message TRspPullConsumer
+{
+ optional TRowsetDescriptor rowset_descriptor = 1;
+ optional int64 start_offset = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqRegisterQueueConsumer
+{
+ optional string queue_path = 1;
+ optional string consumer_path = 2;
+ optional bool vital = 3;
+
+ // The most reasonable way to make a std::optional<std::vector<int>> field.
+ message TRegistrationPartitions
+ {
+ repeated int32 items = 1;
+ }
+ optional TRegistrationPartitions partitions = 4;
+}
+
+message TRspRegisterQueueConsumer
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqUnregisterQueueConsumer
+{
+ optional string queue_path = 1;
+ optional string consumer_path = 2;
+}
+
+message TRspUnregisterQueueConsumer
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqListQueueConsumerRegistrations
+{
+ optional string queue_path = 1;
+ optional string consumer_path = 2;
+}
+
+message TRspListQueueConsumerRegistrations
+{
+ message TQueueConsumerRegistration
+ {
+ optional string queue_path = 1;
+ optional string consumer_path = 2;
+ optional bool vital = 3;
+
+ // The most reasonable way to make a std::optional<std::vector<int>> field.
+ message TRegistrationPartitions
+ {
+ repeated int32 items = 1;
+ }
+ optional TRegistrationPartitions partitions = 4;
+ }
+
+ repeated TQueueConsumerRegistration registrations = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqExplainQuery
+{
+ required string query = 1;
+
+ optional uint64 timestamp = 3 [default = 0x3fffffffffffff01];
+
+ optional uint64 input_row_limit = 4;
+ optional uint64 output_row_limit = 5;
+ optional uint64 range_expansion_limit = 6;
+ optional int32 max_subqueries = 7;
+ optional bool allow_full_scan = 8;
+ optional bool allow_join_without_index = 9;
+ optional string udf_registry_path = 10;
+ optional string execution_pool = 11;
+ optional bool new_range_inference = 12;
+}
+
+message TRspExplainQuery
+{
+ required bytes value = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReplicationRowIndex
+{
+ required NYT.NProto.TGuid tablet_id = 1;
+ required int64 row_index = 2;
+}
+
+message TReqPullRows
+{
+ required string path = 1;
+
+ required NYT.NProto.TGuid upstream_replica_id = 2;
+ required bool order_rows_by_timestamp = 3;
+ required int64 tablet_rows_per_read = 4;
+ required NYT.NChaosClient.NProto.TReplicationProgress replication_progress = 5;
+ optional uint64 upper_timestamp = 6;
+ repeated TReplicationRowIndex start_replication_row_indexes = 7;
+}
+
+message TRspPullRows
+{
+ required int64 row_count = 1;
+ required int64 data_weight = 2;
+ required NYT.NChaosClient.NProto.TReplicationProgress replication_progress = 3;
+ repeated TReplicationRowIndex end_replication_row_indexes = 4;
+ required TRowsetDescriptor rowset_descriptor = 5;
+ optional bool versioned = 6 [default = true];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetInSyncReplicas
+{
+ required string path = 1;
+
+ optional uint64 timestamp = 2 [default = 0];
+
+ optional uint64 cached_sync_replicas_timeout = 3; // TDuration
+
+ optional TRowsetDescriptor rowset_descriptor = 200;
+}
+
+message TRspGetInSyncReplicas
+{
+ repeated NYT.NProto.TGuid replica_ids = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetTabletInfos
+{
+ required string path = 1;
+ repeated int32 tablet_indexes = 2;
+ optional bool request_errors = 3 [default = false];
+}
+
+message TRspGetTabletInfos
+{
+ message TTabletInfo
+ {
+ message TReplicaInfo
+ {
+ required NYT.NProto.TGuid replica_id = 1;
+ required uint64 last_replication_timestamp = 2;
+ required int32 mode = 3; // ETableReplicaMode
+ required int64 current_replication_row_index = 4;
+ required int64 committed_replication_row_index = 6;
+ optional NYT.NProto.TError replication_error = 5;
+ }
+
+ required int64 total_row_count = 1;
+ required int64 trimmed_row_count = 2;
+ optional int64 delayed_lockless_row_count = 7;
+ optional uint64 barrier_timestamp = 3;
+ optional uint64 last_write_timestamp = 4;
+
+ repeated TReplicaInfo replicas = 5;
+
+ repeated NYT.NProto.TError tablet_errors = 6;
+ }
+
+ repeated TTabletInfo tablets = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetTabletErrors
+{
+ required string path = 1;
+ optional int64 limit = 2;
+}
+
+message TRspGetTabletErrors
+{
+ message TErrorList
+ {
+ repeated NYT.NProto.TError errors = 1;
+ }
+
+ repeated NYT.NProto.TGuid tablet_ids = 1;
+ repeated TErrorList tablet_errors = 2;
+ repeated NYT.NProto.TGuid replica_ids = 3;
+ repeated TErrorList replication_errors = 4;
+ optional bool incomplete = 5;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqModifyRows
+{
+ // Modifications are sent asynchronously. Sequential numbering is
+ // required to restore their order (optional for compatibility).
+ optional int64 sequence_number = 6;
+
+ // Modifications can be sent from several sources in case of several clients
+ // attached to the same transaction.
+ //
+ // Modifications within one source will be serialized by this source sequence numbers.
+ // Modifications from different sources will be serialized arbitrarily, that is why
+ // different sources must send independent modifications.
+ //
+ // If sequence number is missing, source id is ignored.
+ // Otherwise missing source id is interpreted as source id = 0.
+ optional int64 sequence_number_source_id = 9;
+
+ required NYT.NProto.TGuid transaction_id = 1;
+ required string path = 2;
+ repeated ERowModificationType row_modification_types = 3;
+ // COMPAT(lukyan): Remove after RPC protocol version update
+ repeated uint32 row_read_locks = 7;
+ repeated uint64 row_locks = 8;
+
+ optional bool require_sync_replica = 4;
+ optional NYT.NProto.TGuid upstream_replica_id = 5;
+
+ optional bool allow_missing_key_columns = 10;
+
+ required TRowsetDescriptor rowset_descriptor = 200;
+}
+
+message TRspModifyRows
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Actual ModifyRows requests are passed as attachments: one for subrequest's
+// protobuf header, then its own attachments.
+// part_counts field corresponds to the amounts of subrequests' own attachments.
+
+message TReqBatchModifyRows
+{
+ required NYT.NProto.TGuid transaction_id = 1;
+ repeated int64 part_counts = 2;
+}
+
+message TRspBatchModifyRows
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqBuildSnapshot
+{
+ optional NYT.NProto.TGuid cell_id = 1;
+ optional bool set_read_only = 2 [default = false];
+ optional bool wait_for_snapshot_completion = 3 [default = true];
+}
+
+message TRspBuildSnapshot
+{
+ required int64 snapshot_id = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGCCollect
+{
+ optional NYT.NProto.TGuid cell_id = 1;
+}
+
+message TRspGCCollect
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqSuspendCoordinator
+{
+ optional NYT.NProto.TGuid coordinator_cell_id = 1;
+}
+
+message TRspSuspendCoordinator
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqResumeCoordinator
+{
+ optional NYT.NProto.TGuid coordinator_cell_id = 1;
+}
+
+message TRspResumeCoordinator
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqMigrateReplicationCards
+{
+ required NYT.NProto.TGuid chaos_cell_id = 1;
+ optional NYT.NProto.TGuid destination_cell_id = 2;
+ repeated NYT.NProto.TGuid replication_card_ids = 3;
+}
+
+message TRspMigrateReplicationCards
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqSuspendChaosCells
+{
+ repeated NYT.NProto.TGuid cell_ids = 1;
+}
+
+message TRspSuspendChaosCells
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqResumeChaosCells
+{
+ repeated NYT.NProto.TGuid cell_ids = 1;
+}
+
+message TRspResumeChaosCells
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAddMaintenance
+{
+ required EMaintenanceComponent component = 1;
+ required string address = 2;
+ required EMaintenanceType type = 3;
+ required string comment = 4;
+}
+
+message TRspAddMaintenance
+{
+ required NYT.NProto.TGuid id = 1; // TMaintenanceId
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqRemoveMaintenance
+{
+ required EMaintenanceComponent component = 1;
+ required string address = 2;
+ repeated NYT.NProto.TGuid ids = 3; // TMaintenanceId
+ optional EMaintenanceType type = 4;
+ optional string user = 5;
+ optional bool mine = 6 [default = false];
+}
+
+message TRspRemoveMaintenance
+{
+ // COMPAT(kvk1920): Can be removed after all clients, proxies and masters will be updated.
+ optional int32 ban = 1;
+ optional int32 decommission = 2;
+ optional int32 disable_scheduler_jobs = 3;
+ optional int32 disable_write_sessions = 4;
+ optional int32 disable_tablet_cells = 5;
+
+ // COMPAT(kvk1920)
+ optional bool use_map_instead_of_fields = 6 [default = false];
+ map<int32, int32> removed_maintenance_counts = 7; // Key: EMaintenanceType
+
+ optional int32 pending_restart = 8;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OPTIONS
+////////////////////////////////////////////////////////////////////////////////
+
+// 100
+message TTransactionalOptions
+{
+ optional NYT.NProto.TGuid transaction_id = 1;
+ optional bool ping = 2 [default = false];
+ optional bool ping_ancestors = 3 [default = false];
+ reserved 4;
+ optional bool suppress_transaction_coordinator_sync = 5;
+ optional bool suppress_upstream_sync = 6;
+}
+
+// 101
+message TPrerequisiteOptions
+{
+ message TTransactionPrerequisite
+ {
+ required NYT.NProto.TGuid transaction_id = 1;
+ }
+
+ message TRevisionPrerequisite
+ {
+ required string path = 2; // YPath
+ required uint64 revision = 3;
+ reserved 1; // former transaction_id
+ }
+
+ repeated TTransactionPrerequisite transactions = 1;
+ repeated TRevisionPrerequisite revisions = 2;
+}
+
+// 102
+message TMasterReadOptions
+{
+ optional EMasterReadKind read_from = 1 [default = MRK_FOLLOWER];
+ optional int64 expire_after_successful_update_time = 2; // TDuration
+ optional int64 expire_after_failed_update_time = 3; // TDuration
+ optional int32 cache_sticky_group_size = 4;
+ optional bool disable_per_user_cache = 5;
+ optional int64 success_staleness_bound = 6; // TDuration
+}
+
+// 103
+message TMutatingOptions
+{
+ optional NYT.NProto.TGuid mutation_id = 1;
+ optional bool retry = 2;
+}
+
+// 104
+message TSuppressableAccessTrackingOptions
+{
+ optional bool suppress_access_tracking = 1 [default = false];
+ optional bool suppress_modification_tracking = 2 [default = false];
+ optional bool suppress_expiration_timeout_renewal = 3 [default = false];
+}
+
+// 105
+message TTabletRangeOptions
+{
+ optional int32 first_tablet_index = 1;
+ optional int32 last_tablet_index = 2;
+}
+
+// 106
+message TTabletReadOptions
+{
+ optional ETabletReadKind read_from = 1 [default = TRK_LEADER];
+
+ optional uint64 cached_sync_replicas_timeout = 2; // TDuration
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// META
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGenerateTimestamps
+{
+ optional int32 count = 1 [default = 1];
+ optional int32 clock_cluster_tag = 2;
+}
+
+message TRspGenerateTimestamps
+{
+ required uint64 timestamp = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CYPRESS
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqExistsNode
+{
+ required string path = 1; // YPath
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMasterReadOptions master_read_options = 102;
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+}
+
+message TRspExistsNode
+{
+ required bool exists = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetNode
+{
+ required string path = 1; // YPath
+
+ // COMPAT(max42): see comment around TLegacyAttributeKeys.
+ optional TLegacyAttributeKeys legacy_attributes = 2;
+ optional NYT.NYTree.NProto.TAttributeFilter attributes = 4;
+
+ optional int64 max_size = 3;
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMasterReadOptions master_read_options = 102;
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+
+ optional NYT.NYTree.NProto.TAttributeDictionary options = 200;
+
+ reserved 5;
+}
+
+message TRspGetNode
+{
+ required bytes value = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqListNode
+{
+ required string path = 1; // YPath
+
+ // COMPAT(max42): see comment around TLegacyAttributeKeys.
+ optional TLegacyAttributeKeys legacy_attributes = 2;
+ optional NYT.NYTree.NProto.TAttributeFilter attributes = 4;
+
+ optional int64 max_size = 3;
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMasterReadOptions master_read_options = 102;
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+
+ reserved 5;
+}
+
+message TRspListNode
+{
+ required bytes value = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCreateObject
+{
+ required int32 type = 1; // NObjectClient::EObjectType
+ optional bool ignore_existing = 3;
+ optional bool sync = 4;
+ optional NYT.NYTree.NProto.TAttributeDictionary attributes = 2;
+
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspCreateObject
+{
+ required NYT.NProto.TGuid object_id = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TColumnSchema
+{
+ required string name = 1;
+ optional string stable_name = 12;
+
+ optional int32 type = 2;
+ optional int32 logical_type = 3;
+ optional bytes type_v3 = 11; // YSON representation of type_v3
+
+ optional string lock = 4;
+ optional string expression = 5;
+ optional string aggregate = 6;
+ optional int32 sort_order = 7;
+ optional string group = 8;
+ optional bool required = 9 [default = false];
+ optional int64 max_inline_hunk_size = 10;
+}
+
+message TDeletedColumn
+{
+ required string stable_name = 1;
+}
+
+message TTableSchema
+{
+ repeated TColumnSchema columns = 1;
+ optional bool strict = 2 [default = true];
+ optional bool unique_keys = 3 [default = false];
+ repeated TDeletedColumn deleted_columns = 5;
+}
+
+message TTabletInfo
+{
+ required NYT.NProto.TGuid tablet_id = 1;
+ required uint64 mount_revision = 2;
+ required int32 state = 3;
+ required bytes pivot_key = 4;
+ optional NYT.NProto.TGuid cell_id = 5;
+ optional int32 cell_config_version = 6;
+}
+
+message TReplicaInfo
+{
+ required NYT.NProto.TGuid replica_id = 1;
+ required string cluster_name = 2;
+ required string replica_path = 3;
+ required int32 mode = 4; // ETableReplicaMode
+}
+
+message TReqGetTableMountInfo
+{
+ required string path = 1;
+}
+
+message TRspGetTableMountInfo
+{
+ required NYT.NProto.TGuid table_id = 1;
+ required TTableSchema schema = 2;
+ repeated TTabletInfo tablets = 3;
+ required bool dynamic = 4;
+ required NYT.NProto.TGuid upstream_replica_id = 5;
+ repeated TReplicaInfo replicas = 6;
+ optional string physical_path = 7;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetTablePivotKeys
+{
+ required string path = 1;
+ optional bool represent_key_as_list = 2;
+}
+
+message TRspGetTablePivotKeys
+{
+ required bytes value = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCreateNode
+{
+ required string path = 1; // YPath
+ required int32 type = 2; // NObjectClient::EObjectType
+
+ optional NYT.NYTree.NProto.TAttributeDictionary attributes = 3;
+ optional bool recursive = 4 [default = false];
+ optional bool force = 5 [default = false];
+ optional bool ignore_existing = 6 [default = false];
+ optional bool lock_existing = 7 [default = false];
+ optional bool ignore_type_mismatch = 8 [default = false];
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspCreateNode
+{
+ required NYT.NProto.TGuid node_id = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqRemoveNode
+{
+ required string path = 1; // YPath
+
+ optional bool recursive = 2 [default = true];
+ optional bool force = 3 [default = false];
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspRemoveNode
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqSetNode
+{
+ required string path = 1; // YPath
+ required bytes value = 2; // YSON
+ optional bool recursive = 3;
+ optional bool force = 4;
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+}
+
+message TRspSetNode
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqMultisetAttributesNode
+{
+ required string path = 1; // YPath
+
+ message TSubrequest
+ {
+ required string attribute = 1;
+ required bytes value = 2; // YSON
+ }
+ repeated TSubrequest subrequests = 2;
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+}
+
+message TRspMultisetAttributesNode
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqLockNode
+{
+ required string path = 1; // YPath
+ required int32 mode = 2; // NCypressClient::ELockMode
+
+ optional bool waitable = 3 [default = false];
+ optional string child_key = 4;
+ optional string attribute_key = 5;
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspLockNode
+{
+ required NYT.NProto.TGuid node_id = 1;
+ required NYT.NProto.TGuid lock_id = 2;
+ optional uint64 revision = 3;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqUnlockNode
+{
+ required string path = 1; // YPath
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspUnlockNode
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCopyNode
+{
+ required string src_path = 1; // YPath
+ required string dst_path = 2; // YPath
+
+ optional bool recursive = 3 [default = false];
+ optional bool force = 4 [default = false];
+ optional bool preserve_account = 5 [default = false];
+ optional bool preserve_creation_time = 7 [default = false];
+ optional bool preserve_modification_time = 10 [default = false];
+ optional bool preserve_expiration_time = 6 [default = false];
+ optional bool preserve_expiration_timeout = 14 [default = false];
+ optional bool preserve_owner = 11 [default = false];
+ optional bool preserve_acl = 12 [default = false];
+ optional bool ignore_existing = 8 [default = false];
+ optional bool lock_existing = 13 [default = false];
+ optional bool pessimistic_quota_check = 9 [default = true];
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspCopyNode
+{
+ required NYT.NProto.TGuid node_id = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqMoveNode
+{
+ required string src_path = 1; // YPath
+ required string dst_path = 2; // YPath
+
+ optional bool recursive = 3 [default = false];
+ optional bool force = 4 [default = false];
+ optional bool preserve_account = 5 [default = false];
+ optional bool preserve_creation_time = 8 [default = false];
+ optional bool preserve_modification_time = 9 [default = false];
+ optional bool preserve_expiration_time = 6 [default = false];
+ optional bool preserve_expiration_timeout = 12 [default = false];
+ optional bool preserve_owner = 11 [default = false];
+ optional bool pessimistic_quota_check = 7 [default = true];
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspMoveNode
+{
+ required NYT.NProto.TGuid node_id = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqLinkNode
+{
+ required string src_path = 1; // YPath
+ required string dst_path = 2; // YPath
+
+ optional bool recursive = 3 [default = false];
+ optional bool force = 4 [default = false];
+ optional bool ignore_existing = 5 [default = false];
+ optional bool lock_existing = 6 [default = false];
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspLinkNode
+{
+ required NYT.NProto.TGuid node_id = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqConcatenateNodes
+{
+ repeated string src_paths = 1; // YPath
+ required string dst_path = 2; // YPath
+
+ message TFetcher
+ {
+ optional uint64 node_rpc_timeout = 1 [default = 30000000]; // TDuration, 30 seconds by default
+ }
+
+ optional TFetcher fetcher = 3;
+
+ optional TTransactionalOptions transactional_options = 100;
+ // TODO(sandello): Prerequisite?
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspConcatenateNodes
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqExternalizeNode
+{
+ required string path = 1; // YPath
+ required int32 cell_tag = 2;
+
+ optional TTransactionalOptions transactional_options = 100;
+}
+
+message TRspExternalizeNode
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqInternalizeNode
+{
+ required string path = 1; // YPath
+
+ optional TTransactionalOptions transactional_options = 100;
+}
+
+message TRspInternalizeNode
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAttachTransaction
+{
+ required NYT.NProto.TGuid transaction_id = 1;
+
+ // COMPAT(kiselyovp) next option is unused and should get removed
+ optional bool auto_abort = 2 [default = false];
+ reserved 3;
+ optional uint64 ping_period = 4; // TDuration
+ optional bool ping = 5 [default = true];
+ optional bool ping_ancestors = 6 [default = false];
+}
+
+message TRspAttachTransaction
+{
+ required ETransactionType type = 1;
+ required uint64 start_timestamp = 2;
+ required EAtomicity atomicity = 3;
+ required EDurability durability = 4;
+ required int64 timeout = 5;
+ // Sequence number source id allocated for this client if transaction is tablet.
+ // Client is free not to use this source id and generate source id on its own,
+ // for example, for backward compatibility. In this case client is responsible for
+ // source id uniqueness between all transaction clients.
+ // See TReqModifyRows.sequence_number_source_id for details.
+ optional int64 sequence_number_source_id = 6;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqDetachTransaction
+{
+ required NYT.NProto.TGuid transaction_id = 1;
+}
+
+message TRspDetachTransaction
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TABLES (NON-TRANSACTIONAL)
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqMountTable
+{
+ required string path = 1; // YPath
+
+ optional NYT.NProto.TGuid cell_id = 2;
+ optional bool freeze = 3 [default = false];
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TTabletRangeOptions tablet_range_options = 104;
+
+ repeated NYT.NProto.TGuid target_cell_ids = 4;
+}
+
+message TRspMountTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqUnmountTable
+{
+ required string path = 1; // YPath
+
+ optional bool force = 2 [default = false];
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TTabletRangeOptions tablet_range_options = 104;
+}
+
+message TRspUnmountTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqRemountTable
+{
+ required string path = 1; // YPath
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TTabletRangeOptions tablet_range_options = 104;
+}
+
+message TRspRemountTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqFreezeTable
+{
+ required string path = 1; // YPath
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TTabletRangeOptions tablet_range_options = 104;
+}
+
+message TRspFreezeTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqUnfreezeTable
+{
+ required string path = 1; // YPath
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TTabletRangeOptions tablet_range_options = 104;
+}
+
+message TRspUnfreezeTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqReshardTable
+{
+ required string path = 1; // YPath
+
+ optional int32 tablet_count = 3;
+
+ optional bool uniform = 4;
+
+ optional bool enable_slicing = 5;
+ optional double slicing_accuracy = 6;
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TTabletRangeOptions tablet_range_options = 104;
+
+ optional TRowsetDescriptor rowset_descriptor = 200;
+}
+
+message TRspReshardTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqReshardTableAutomatic
+{
+ required string path = 1; // YPath
+ required bool keep_actions = 2 [default = false];
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TTabletRangeOptions tablet_range_options = 104;
+}
+
+message TRspReshardTableAutomatic
+{
+ repeated NYT.NProto.TGuid tablet_actions = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqTrimTable
+{
+ required string path = 1; // YPath
+ required int32 tablet_index = 2;
+ required uint64 trimmed_row_count = 3;
+
+ // XXX(sandello): Mutating?
+}
+
+message TRspTrimTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAlterTable
+{
+ required string path = 1; // YPath
+
+ optional bytes schema = 2; // YSON
+ optional bool dynamic = 3;
+ optional NYT.NProto.TGuid upstream_replica_id = 4;
+ optional ETableSchemaModification schema_modification = 5;
+ optional NYT.NChaosClient.NProto.TReplicationProgress replication_progress = 6;
+ optional NYT.NProto.TGuid schema_id = 7;
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TMutatingOptions mutating_options = 103;
+ // XXX(sandello): Prerequisite?
+}
+
+message TRspAlterTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAlterTableReplica
+{
+ required NYT.NProto.TGuid replica_id = 1;
+
+ optional bool enabled = 2;
+ optional ETableReplicaMode mode = 3;
+ optional bool preserve_timestamps = 4;
+ optional EAtomicity atomicity = 5;
+ optional bool enable_replicated_table_tracker = 6;
+
+ optional TMutatingOptions mutating_options = 100;
+}
+
+message TRspAlterTableReplica
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAlterReplicationCard
+{
+ required NYT.NProto.TGuid replication_card_id = 1; // NChaosClient::TReplicationCardId
+
+ optional bytes replicated_table_options = 2; // YSON
+ optional bool enable_replicated_table_tracker = 3;
+ optional NYT.NProto.TGuid replication_card_collocation_id = 4;
+
+ optional TMutatingOptions mutating_options = 100;
+}
+
+message TRspAlterReplicationCard
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TFetchChunkSpecConfig
+{
+ optional int32 max_chunk_per_fetch = 1;
+ optional int32 max_chunk_per_locate_request = 2;
+}
+
+message TFetcherConfig
+{
+ optional uint64 node_rpc_timeout = 1; // TDuration
+}
+
+message TReqGetColumnarStatistics
+{
+ repeated string paths = 1; // RichYPath
+ optional TFetchChunkSpecConfig fetch_chunk_spec_config = 2;
+ optional TFetcherConfig fetcher_config = 3;
+ optional EColumnarStatisticsFetcherMode fetcher_mode = 4;
+ optional bool enable_early_finish = 5 [default = false];
+
+ optional TTransactionalOptions transactional_options = 100;
+}
+
+message TColumnarStatistics
+{
+ repeated int64 column_data_weights = 1;
+ optional int64 timestamp_total_weight = 2;
+ optional int64 legacy_chunk_data_weight = 3 [default = 0];
+
+ // Per-column approximate minimum values. For more information check comments in NYT::NTableClient::TColumnarStatistics.
+ optional bytes column_min_values = 4;
+ // Per-column approximate maximum values. For more information check comments in NYT::NTableClient::TColumnarStatistics.
+ optional bytes column_max_values = 5;
+ // Number of non-null values in each column.
+ repeated int64 column_non_null_value_counts = 6;
+
+ // Total number of rows in all chunks whose meta contains columnar statistics.
+ optional int64 chunk_row_count = 7;
+ // Total number of rows in legacy chunks whose meta misses columnar statistics.
+ optional int64 legacy_chunk_row_count = 8;
+}
+
+message TRspGetColumnarStatistics
+{
+
+ repeated TColumnarStatistics statistics = 1;
+}
+
+message TReqDisableChunkLocations
+{
+ required string node_address = 1;
+ repeated NYT.NProto.TGuid location_uuids = 2;
+}
+
+message TRspDisableChunkLocations
+{
+ repeated NYT.NProto.TGuid location_uuids = 1;
+}
+
+message TReqDestroyChunkLocations
+{
+ required string node_address = 1;
+ repeated NYT.NProto.TGuid location_uuids = 2;
+}
+
+message TRspDestroyChunkLocations
+{
+ repeated NYT.NProto.TGuid location_uuids = 1;
+}
+
+message TReqResurrectChunkLocations
+{
+ required string node_address = 1;
+ repeated NYT.NProto.TGuid location_uuids = 2;
+}
+
+message TRspResurrectChunkLocations
+{
+ repeated NYT.NProto.TGuid location_uuids = 1;
+}
+
+message TReqRequestReboot
+{
+ required string node_address = 1;
+}
+
+message TRspRequestReboot
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqPartitionTables
+{
+ message TChunkSliceFetcherConfig
+ {
+ optional int32 max_slices_per_fetch = 1;
+ }
+
+ repeated string paths = 1; // RichYPath
+ optional TFetchChunkSpecConfig fetch_chunk_spec_config = 2;
+ optional TFetcherConfig fetcher_config = 3;
+ optional TChunkSliceFetcherConfig chunk_slice_fetcher_config = 4;
+ required EPartitionTablesMode partition_mode = 5;
+ required int64 data_weight_per_partition = 6;
+ optional int32 max_partition_count = 7;
+ optional bool enable_key_guarantee = 8;
+ optional bool adjust_data_weight_per_partition = 9;
+
+ optional TTransactionalOptions transactional_options = 100;
+}
+
+message TMultiTablePartition
+{
+ repeated string table_ranges = 1; // RichYPath
+
+ message TStatistics
+ {
+ optional int64 chunk_count = 1;
+ optional int64 data_weight = 2;
+ optional int64 row_count = 3;
+ }
+
+ optional TStatistics aggregate_statistics = 2;
+}
+
+message TRspPartitionTables
+{
+ repeated TMultiTablePartition partitions = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqBalanceTabletCells
+{
+ required string bundle = 1;
+ repeated string movable_tables = 2;
+ required bool keep_actions = 3 [default = false];
+
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspBalanceTabletCells
+{
+ repeated NYT.NProto.TGuid tablet_actions = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FILE CACHING
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetFileFromCache
+{
+ required string md5 = 1;
+ required string cache_path = 2; // YPath
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TMasterReadOptions master_read_options = 102;
+}
+
+message TRspGetFileFromCache
+{
+ required TGetFileFromCacheResult result = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqPutFileToCache
+{
+ required string path = 1; // YPath
+ required string md5 = 2;
+ required string cache_path = 3; // YPath
+ optional bool preserve_expiration_timeout = 4 [default = false];
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMasterReadOptions master_read_options = 102;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspPutFileToCache
+{
+ required TPutFileToCacheResult result = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OPERATIONS
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqStartOperation
+{
+ required EOperationType type = 1;
+ required bytes spec = 2; // YSON
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspStartOperation
+{
+ required NYT.NProto.TGuid operation_id = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAbortOperation
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 3;
+ }
+ optional string abort_message = 2;
+}
+
+message TRspAbortOperation
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqSuspendOperation
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 3;
+ }
+ optional bool abort_running_jobs = 2 [default = false];
+}
+
+message TRspSuspendOperation
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqResumeOperation
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 2;
+ }
+}
+
+message TRspResumeOperation
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCompleteOperation
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 2;
+ }}
+
+message TRspCompleteOperation
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqUpdateOperationParameters
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 3;
+ }
+ required bytes parameters = 2; // YSON
+}
+
+message TRspUpdateOperationParameters
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetOperation
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 4;
+ }
+
+ // COMPAT(max42): see comment around TLegacyAttributeKeys with the only
+ // difference that attributes were serialized as repeated string historically.
+ repeated string legacy_attributes = 2;
+ optional NYT.NYTree.NProto.TAttributeFilter attributes = 6;
+
+ optional bool include_runtime = 3 [default = false];
+ optional int64 maximum_cypress_progress_age = 5; // TDuration
+
+ optional TMasterReadOptions master_read_options = 102;
+}
+
+message TRspGetOperation
+{
+ required bytes meta = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqListOperations
+{
+ optional uint64 from_time = 1; // TInstant
+ optional uint64 to_time = 2; // TInstant
+ optional uint64 cursor_time = 3; // TInstant
+ optional EOperationSortDirection cursor_direction = 4 [default = OSD_PAST];
+ optional string user_filter = 5;
+
+ reserved 6;
+
+ optional EOperationState state_filter = 7;
+ optional EOperationType type_filter = 8;
+ optional string substr_filter = 9;
+ optional string pool = 10;
+ optional bool with_failed_jobs = 11;
+ optional bool include_archive = 12 [default = false];
+ optional bool include_counters = 13 [default = true];
+ optional uint64 limit = 14 [default = 100];
+
+ // COMPAT(max42): see comment around TLegacyAttributeKeys.
+ optional TLegacyAttributeKeys legacy_attributes = 15;
+ optional NYT.NYTree.NProto.TAttributeFilter attributes = 20;
+
+ optional bool enable_ui_mode = 16 [default = false];
+
+ optional bytes access_filter = 17; // YSON
+
+ optional uint64 archive_fetching_timeout = 18 [default = 3000000]; // TDuration, 3 seconds by default
+
+ optional string pool_tree = 19;
+
+ optional TMasterReadOptions master_read_options = 102;
+}
+
+message TRspListOperations
+{
+ required TListOperationsResult result = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// JOBS
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqListJobs
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 20;
+ }
+
+ optional EJobType type = 2;
+ optional EJobState state = 3;
+ optional string address = 4;
+ optional bool with_stderr = 5;
+ optional bool with_fail_context = 6;
+ optional bool with_spec = 7;
+
+ optional EJobSortField sort_field = 8 [default = JSF_NONE];
+ optional EJobSortDirection sort_order = 9 [default = JSD_ASCENDING];
+
+ optional int64 limit = 10 [default = 1000];
+ optional int64 offset = 11 [default = 0];
+
+ optional bool include_cypress = 12 [default = false];
+ optional bool include_controller_agent = 13 [default = false];
+ optional bool include_archive = 14 [default = false];
+
+ optional EDataSource data_source = 15 [default = DS_AUTO];
+ optional uint64 running_jobs_lookbehind_period = 16;
+
+ optional NYT.NProto.TGuid job_competition_id = 17;
+ optional bool with_competitors = 18;
+
+ optional string task_name = 19;
+
+ optional TMasterReadOptions master_read_options = 102;
+}
+
+message TRspListJobs
+{
+ required TListJobsResult result = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetJob
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 4;
+ }
+ required NYT.NProto.TGuid job_id = 2;
+
+ // COMPAT(max42): see comment around TLegacyAttributeKeys.
+ optional TLegacyAttributeKeys legacy_attributes = 3;
+ optional NYT.NYTree.NProto.TAttributeFilter attributes = 5;
+}
+
+message TRspGetJob
+{
+ required bytes info = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetJobInputPaths
+{
+ required NYT.NProto.TGuid job_id = 1;
+ optional EJobSpecSource job_spec_source = 2 [default = JSS_AUTO];
+}
+
+message TRspGetJobInputPaths
+{
+ required bytes paths = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetJobSpec
+{
+ required NYT.NProto.TGuid job_id = 1;
+
+ optional bool omit_node_directory = 2 [default = true];
+ optional bool omit_input_table_specs = 3 [default = false];
+ optional bool omit_output_table_specs = 4 [default = false];
+ optional EJobSpecSource job_spec_source = 5 [default = JSS_AUTO];
+}
+
+message TRspGetJobSpec
+{
+ required bytes job_spec = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetJobStderr
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 3;
+ }
+ required NYT.NProto.TGuid job_id = 2;
+}
+
+message TRspGetJobStderr
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetJobFailContext
+{
+ oneof operation_id_or_alias {
+ NYT.NProto.TGuid operation_id = 1;
+ string operation_alias = 3;
+ }
+ required NYT.NProto.TGuid job_id = 2;
+}
+
+message TRspGetJobFailContext
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqDumpJobContext
+{
+ required NYT.NProto.TGuid job_id = 1;
+ required string path = 2;
+}
+
+message TRspDumpJobContext
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAbandonJob
+{
+ required NYT.NProto.TGuid job_id = 1;
+}
+
+message TRspAbandonJob
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqPollJobShell
+{
+ required NYT.NProto.TGuid job_id = 1;
+ required bytes parameters = 2; // YSON
+ optional string shell_name = 3;
+}
+
+message TRspPollJobShell
+{
+ required bytes result = 1; // YSON
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAbortJob
+{
+ required NYT.NProto.TGuid job_id = 1;
+ optional int64 interrupt_timeout = 2;
+}
+
+message TRspAbortJob
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// STREAMING
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqReadFile
+{
+ required string path = 1; // YPath
+
+ optional int64 offset = 2;
+ optional int64 length = 3;
+ optional bytes config = 4; // YSON-serialized TFileReaderConfig
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+}
+
+message TReadFileMeta
+{
+ required uint64 revision = 1;
+ // COMPAT(shakurov): optional for old proxy compatibility.
+ optional NYT.NProto.TGuid id = 2;
+}
+
+message TRspReadFile
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqWriteFile
+{
+ required string path = 1; // RichYPath
+
+ optional bool compute_md5 = 2 [default = false];
+ optional bytes config = 3; // YSON-serialized TFileWriterConfig
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+}
+
+message TRspWriteFile
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqReadJournal
+{
+ required string path = 1; // YPath
+
+ optional int64 first_row_index = 2;
+ optional int64 row_count = 3;
+ optional bytes config = 4; // YSON-serialized TJournalReaderConfig
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TSuppressableAccessTrackingOptions suppressable_access_tracking_options = 104;
+}
+
+message TRspReadJournal
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqWriteJournal
+{
+ required string path = 1; // YPath
+
+ optional bytes config = 2; // YSON-serialized TJournalWriterConfig
+ optional bool enable_multiplexing = 3 [default = true];
+ optional bool enable_chunk_preallocation = 4 [default = false];
+ optional int64 replica_lag_limit = 5 [default = 32768];
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+}
+
+message TRspWriteJournal
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqTruncateJournal
+{
+ required string path = 1; // YPath
+ required int64 row_count = 2;
+
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspTruncateJournal
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqReadTable
+{
+ required string path = 1; // RichYPath
+
+ optional bool unordered = 2 [default = false];
+ optional bool omit_inaccessible_columns = 3 [default = false];
+ optional bool enable_table_index = 5 [default = false];
+ optional bool enable_row_index = 6 [default = false];
+ optional bool enable_range_index = 7 [default = false];
+ optional bytes config = 4; // YSON-serialized TTableReaderConfig
+ optional ERowsetFormat desired_rowset_format = 8 [default = RF_YT_WIRE];
+ optional ERowsetFormat arrow_fallback_rowset_format = 10 [default = RF_YT_WIRE];
+ optional bytes format = 9; // YSON-serialized TFormat
+
+ optional TTransactionalOptions transactional_options = 100;
+}
+
+message TRspReadTableMeta
+{
+ required int64 start_row_index = 1;
+ reserved 2;
+ repeated string omitted_inaccessible_columns = 3;
+ required TTableSchema schema = 5;
+ required TRowsetStatistics statistics = 4;
+}
+
+message TRspReadTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqWriteTable
+{
+ required string path = 1; // RichYPath
+
+ optional bytes config = 2; // YSON-serialized TTableWriterConfig
+
+ optional bytes format = 3; // YSON-serialized TFormat
+
+ optional TTransactionalOptions transactional_options = 100;
+}
+
+message TWriteTableMeta
+{
+ required TTableSchema schema = 1;
+}
+
+message TRspWriteTable
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGetJobInput
+{
+ required NYT.NProto.TGuid job_id = 1;
+ optional EJobSpecSource job_spec_source = 2 [default = JSS_AUTO];
+}
+
+message TRspGetJobInput
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ETC
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqAddMember
+{
+ required string group = 1;
+ required string member = 2;
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TPrerequisiteOptions prerequisite_options = 104;
+}
+
+message TRspAddMember
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqRemoveMember
+{
+ required string group = 1;
+ required string member = 2;
+
+ optional TMutatingOptions mutating_options = 103;
+ optional TPrerequisiteOptions prerequisite_options = 104;
+}
+
+message TRspRemoveMember
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCheckPermission
+{
+ required string user = 1;
+ required string path = 2; // YSON
+ required int32 permission = 3; // bit enum consisting of 7 bits
+
+ optional TTransactionalOptions transactional_options = 100;
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMasterReadOptions master_read_options = 102;
+
+ message TColumns
+ {
+ repeated string items = 1;
+ }
+ optional TColumns columns = 4;
+
+ optional bool vital = 5;
+}
+
+message TRspCheckPermission
+{
+ required TCheckPermissionResult result = 1;
+
+ message TColumns
+ {
+ repeated TCheckPermissionResult items = 1;
+ }
+ optional TColumns columns = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCheckPermissionByAcl
+{
+ optional string user = 1;
+ required int32 permission = 2; // bit enum consisting of 7 bits
+ required string acl = 3;
+
+ optional bool ignore_missing_subjects = 4 [default = false];
+
+ optional TPrerequisiteOptions prerequisite_options = 101;
+ optional TMasterReadOptions master_read_options = 102;
+}
+
+message TRspCheckPermissionByAcl
+{
+ required TCheckPermissionByAclResult result = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqTransferAccountResources
+{
+ required string src_account = 1;
+ required string dst_account = 2;
+ required bytes resource_delta = 3; // YSON
+
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspTransferAccountResources
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqTransferPoolResources
+{
+ required string src_pool = 1;
+ required string dst_pool = 2;
+ required string pool_tree = 3;
+ required bytes resource_delta = 4; // YSON
+
+ optional TMutatingOptions mutating_options = 103;
+}
+
+message TRspTransferPoolResources
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqCheckClusterLiveness
+{
+ required bool check_cypress_root = 1;
+ optional bool check_secondary_master_cells = 2;
+ optional string check_tablet_cell_bundle = 3;
+}
+
+message TRspCheckClusterLiveness
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RESULTS
+////////////////////////////////////////////////////////////////////////////////
+
+message TOperation
+{
+ message TStrings {
+ repeated string data = 1;
+ }
+
+ optional NYT.NProto.TGuid id = 1;
+ optional EOperationType type = 2;
+ optional EOperationState state = 3;
+
+ optional uint64 start_time = 4; // TInstant
+ optional uint64 finish_time = 5; // TInstant
+
+ optional string authenticated_user = 6;
+ reserved 7;
+ optional TStrings pools = 8;
+
+ optional bytes brief_spec = 9; // YSON
+ optional bytes spec = 10; // YSON
+ optional bytes provided_spec = 27; // YSON
+ optional bytes full_spec = 11; // YSON
+ optional bytes unrecognized_spec = 12; // YSON
+ reserved 13;
+
+ optional bytes brief_progress = 14; // YSON
+ optional bytes progress = 15; // YSON
+
+ optional bytes runtime_parameters = 16; // YSON
+
+ optional bool suspended = 17;
+
+ optional bytes events = 18; // YSON
+ optional bytes result = 19; // YSON
+
+ optional bytes slot_index_per_pool_tree = 20; // YSON
+
+ optional bytes task_names = 21; // YSON
+
+ optional bytes experiment_assignments = 22; // YSON
+ optional bytes experiment_assignment_names = 23; // YSON
+
+ optional bytes alerts = 24; // YSON
+
+ optional bytes controller_features = 25; // YSON
+
+ optional bytes alert_events = 26; // YSON
+
+ optional bytes other_attributes = 50; // YSON
+}
+
+message TListOperationsResult
+{
+ message TPoolCounts
+ {
+ message TPoolCount
+ {
+ required string pool = 1;
+ required int64 count = 2;
+ }
+
+ repeated TPoolCount entries = 1;
+ }
+
+ message TUserCounts
+ {
+ message TUserCount
+ {
+ required string user = 1;
+ required int64 count = 2;
+ }
+
+ repeated TUserCount entries = 1;
+ }
+
+ message TOperationStateCounts
+ {
+ message TOperationStateCount
+ {
+ required EOperationState state = 1;
+ required int64 count = 2;
+ }
+
+ repeated TOperationStateCount entries = 1;
+ }
+
+ message TOperationTypeCounts
+ {
+ message TOperationTypeCount
+ {
+ required EOperationType type = 1;
+ required int64 count = 2;
+ }
+
+ repeated TOperationTypeCount entries = 1;
+ }
+
+ message TPoolTreeCounts
+ {
+ map<string, int64> entries = 1;
+ }
+
+ repeated TOperation operations = 1;
+ optional TPoolCounts pool_counts = 2;
+ optional TUserCounts user_counts = 3;
+ optional TOperationStateCounts state_counts = 4;
+ optional TOperationTypeCounts type_counts = 5;
+ optional int64 failed_jobs_count = 6;
+ optional bool incomplete = 7 [default = false];
+ optional TPoolTreeCounts pool_tree_counts = 8;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TJob
+{
+ optional NYT.NProto.TGuid id = 1;
+ optional EJobType type = 2;
+ optional EJobState state = 3;
+ optional uint64 start_time = 4; // TInstant
+ optional uint64 finish_time = 5; // TInstant
+ optional string address = 6;
+ optional double progress = 7;
+ optional uint64 stderr_size = 8;
+ optional uint64 fail_context_size = 9;
+ optional bool has_spec = 10;
+ optional bytes error = 11; // YSON
+ optional bytes brief_statistics = 12; // YSON
+ optional bytes input_paths = 13; // YSON
+ optional bytes core_infos = 14; // YSON
+ optional NYT.NProto.TGuid operation_id = 15;
+ optional EJobState controller_state = 16;
+ optional EJobState archive_state = 17;
+ optional NYT.NProto.TGuid job_competition_id = 18;
+ optional bool has_competitors = 19;
+ optional bool is_stale = 20;
+ optional bytes exec_attributes = 21; // YSON
+ optional string task_name = 22;
+ optional string pool_tree = 23;
+ optional string pool = 24;
+ optional NYT.NProto.TGuid probing_job_competition_id = 25;
+ optional bool has_probing_competitors = 26;
+ optional uint64 job_cookie = 27;
+}
+
+message TListJobsStatistics
+{
+ message TJobStateCounts
+ {
+ message TJobStateCount
+ {
+ required EJobState state = 1;
+ required int64 count = 2;
+ }
+ repeated TJobStateCount entries = 1;
+
+ }
+
+ message TJobTypeCounts
+ {
+ message TJobTypeCount
+ {
+ required EJobType type = 1;
+ required int64 count = 2;
+ }
+ repeated TJobTypeCount entries = 1;
+ }
+
+ required TJobStateCounts state_counts = 1;
+ required TJobTypeCounts type_counts = 2;
+}
+
+message TListJobsResult
+{
+ repeated TJob jobs = 1;
+ optional int32 cypress_job_count = 2;
+ optional int32 controller_agent_job_count = 3;
+ optional int32 archive_job_count = 4;
+
+ required TListJobsStatistics statistics = 5;
+ repeated NYT.NProto.TError errors = 6;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TGetFileFromCacheResult
+{
+ required string path = 1; // YPath
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TPutFileToCacheResult
+{
+ required string path = 1; // YPath
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TCheckPermissionResult
+{
+ required ESecurityAction action = 1;
+ required NYT.NProto.TGuid object_id = 2;
+ optional string object_name = 3;
+ required NYT.NProto.TGuid subject_id = 4;
+ optional string subject_name = 5;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TCheckPermissionByAclResult
+{
+ required ESecurityAction action = 1;
+ required NYT.NProto.TGuid subject_id = 2;
+ optional string subject_name = 3;
+ repeated string missing_subjects = 4;
+}
diff --git a/yt/yt_proto/yt/client/api/rpc_proxy/proto/discovery_service.proto b/yt/yt_proto/yt/client/api/rpc_proxy/proto/discovery_service.proto
new file mode 100644
index 0000000000..8ac0df236f
--- /dev/null
+++ b/yt/yt_proto/yt/client/api/rpc_proxy/proto/discovery_service.proto
@@ -0,0 +1,32 @@
+package NYT.NApi.NRpcProxy.NProto;
+
+option java_package = "tech.ytsaurus";
+option java_outer_classname = "DiscoveryProtos";
+option java_multiple_files = true;
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/api/rpc_proxy";
+
+//------------------------------------------------------------------------------
+
+enum EAddressType
+{
+ AT_INTERNAL_RPC = 0;
+ AT_MONITORING_HTTP = 1;
+ AT_TVM_ONLY_INTERNAL_RPC = 2;
+}
+
+message TReqDiscoverProxies
+{
+ optional string role = 1;
+ optional EAddressType address_type = 2 [default = AT_INTERNAL_RPC];
+ optional string network_name = 3;
+}
+
+message TRspDiscoverProxies
+{
+ repeated string addresses = 1;
+}
+
+//------------------------------------------------------------------------------
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/cell_master/proto/cell_directory.proto b/yt/yt_proto/yt/client/cell_master/proto/cell_directory.proto
new file mode 100644
index 0000000000..e1835fab85
--- /dev/null
+++ b/yt/yt_proto/yt/client/cell_master/proto/cell_directory.proto
@@ -0,0 +1,19 @@
+package NYT.NCellMasterClient.NProto;
+
+import "yt_proto/yt/core/misc/proto/guid.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TCellDirectory
+{
+ message TItem
+ {
+ required NYT.NProto.TGuid cell_id = 1;
+ repeated uint32 roles = 2; // EMasterCellRole(s)
+ repeated string addresses = 3;
+ }
+
+ repeated TItem items = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/chaos_client/proto/replication_card.proto b/yt/yt_proto/yt/client/chaos_client/proto/replication_card.proto
new file mode 100644
index 0000000000..fed0cc4df2
--- /dev/null
+++ b/yt/yt_proto/yt/client/chaos_client/proto/replication_card.proto
@@ -0,0 +1,73 @@
+package NYT.NChaosClient.NProto;
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/chaos_client";
+
+import "yt_proto/yt/core/misc/proto/guid.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReplicationProgress
+{
+ message TSegment
+ {
+ required bytes lower_key = 1;
+ required uint64 timestamp = 2;
+ }
+
+ repeated TSegment segments = 1;
+ required bytes upper_key = 2;
+}
+
+message TReplicaHistoryItem
+{
+ required uint64 era = 1;
+ required uint64 timestamp = 2;
+ required int32 mode = 3;
+ required int32 state = 4;
+}
+
+message TReplicaInfo
+{
+ required string cluster_name = 1;
+ required string replica_path = 2;
+ required int32 content_type = 3; // NTabletClient::ETableReplicaContentType
+ required int32 mode = 4; // NTabletClient::ETableReplicaMode
+ required int32 state = 5; // NTabletClient::ETableReplicaState
+ optional TReplicationProgress progress = 6;
+ repeated TReplicaHistoryItem history = 7;
+ optional bool enable_replicated_table_tracker = 8;
+}
+
+message TReplicationCard
+{
+ message TReplicaEntry
+ {
+ required NYT.NProto.TGuid id = 1;
+ required TReplicaInfo info = 2;
+ }
+ repeated TReplicaEntry replicas = 1;
+ repeated NYT.NProto.TGuid coordinator_cell_ids = 2;
+ required uint64 era = 3;
+ optional NYT.NProto.TGuid table_id = 4;
+ optional string table_path = 5;
+ optional string table_cluster_name = 6;
+ optional uint64 current_timestamp = 7;
+ optional bytes replicated_table_options = 8; // NTabletClient::TReplicatedTableOptions
+ optional NYT.NProto.TGuid replication_card_collocation_id = 9;
+}
+
+message TUpstreamReplicationCard
+{
+ required NYT.NProto.TGuid chaos_cell_id = 1; // NObjectClient::TCellId
+ required NYT.NProto.TGuid replication_card_id = 2; // NChaosClient::TReplicationCardId
+}
+
+message TReplicationCardFetchOptions
+{
+ optional bool include_coordinators = 1;
+ optional bool include_progress = 2;
+ optional bool include_history = 3;
+ optional bool include_replicated_table_options = 4;
+};
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/chunk_client/proto/chunk_meta.proto b/yt/yt_proto/yt/client/chunk_client/proto/chunk_meta.proto
new file mode 100644
index 0000000000..e6c778f4eb
--- /dev/null
+++ b/yt/yt_proto/yt/client/chunk_client/proto/chunk_meta.proto
@@ -0,0 +1,166 @@
+package NYT.NChunkClient.NProto;
+
+import "yt_proto/yt/core/misc/proto/protobuf_helpers.proto";
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/chunk_client";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TBlockInfo
+{
+ required int64 offset = 1;
+ required int64 size = 2;
+ required uint64 checksum = 3;
+}
+
+// Chunk meta extension.
+message TBlocksExt
+{
+ // Addresses both data and system blocks.
+ repeated TBlockInfo blocks = 1;
+
+ // Whether chunk was written with `sync_on_close` option.
+ optional bool sync_on_close = 2 [default = true];
+}
+
+// Chunk meta extension.
+message TMiscExt
+{
+ // Total size of uncompressed blocks.
+ optional int64 uncompressed_data_size = 1 [default = 0];
+
+ // Total size of blocks with data sent to holder.
+ optional int64 compressed_data_size = 5 [default = 0];
+
+ // Total size of (key column names + values + 1) in each row.
+ // Must be greater than zero.
+ optional int64 data_weight = 7 [default = 1];
+
+ // Size of chunk meta (without TMiscExt), obtained via ByteSize.
+ optional int64 meta_size = 6 [default = 0];
+
+ // Number of rows in this chunk (tables and journals only).
+ optional int64 row_count = 2 [default = 0];
+
+ // ECompressionCodec
+ optional int32 compression_codec = 3 [default = 0];
+
+ // Indicates if the chunk contains a sorted rowset (tables only).
+ optional bool sorted = 4 [default = false];
+
+ // Number of key-value pairs across all rows in the chunk.
+ optional int64 value_count = 8 [default = 0];
+
+ // Uncompressed size of the largest data block.
+ optional int64 max_data_block_size = 9 [default = 0];
+
+ // Min/max timestamps for versioned table chunks.
+ optional uint64 min_timestamp = 10 [default = 0];
+ optional uint64 max_timestamp = 11 [default = 0];
+
+ // Is the chunk sealed (journals only)?
+ optional bool sealed = 13;
+
+ // For overlayed journal chunks, contains the journal-wide index of the first row.
+ optional int64 first_overlayed_row_index = 19 [default = 0];
+
+ // Does this chunk belong to Eden (dynamic tables only)?
+ optional bool eden = 14 [default = false];
+
+ // EErasureCodec
+ optional int32 erasure_codec = 15;
+
+ // Indicates if the chunk contains a sorted rowset with unique keys (tables only).
+ optional bool unique_keys = 16 [default = false];
+
+ // Chunk creation time.
+ optional uint64 creation_time = 17 [default = 0];
+
+ // When set, data nodes allow downloading this chunk by HTTP.
+ optional bool shared_to_skynet = 18 [default = false];
+
+ // System blocks contain internal information (e.g. chunk index).
+ optional int32 system_block_count = 21;
+
+ // Is it a striped erasure chunk?
+ optional bool striped_erasure = 22;
+
+ // If present, is used by corresponding block reader to distinguish between block versions.
+ optional int32 block_format_version = 23;
+
+ reserved 20;
+}
+
+message TPartInfo
+{
+ // Chunk-wide indexes of starting blocks of the stripes of a part.
+ repeated int32 first_block_index_per_stripe = 1;
+
+ // Block sizes.
+ repeated int64 block_sizes = 2;
+}
+
+// Chunk meta extension.
+message TErasurePlacementExt
+{
+ repeated TPartInfo part_infos = 1;
+ required int32 parity_part_count = 2;
+ required int64 parity_block_size = 4;
+
+ // Parity block count for each stripe of a part.
+ repeated int32 parity_block_count_per_stripe = 3;
+
+ // Last parity block size for each stripe of a part.
+ repeated int64 parity_last_block_size_per_stripe = 5;
+
+ // We cannot store checksums in TPartInfo,
+ // since part infos are present only for non-empty data parts.
+ repeated fixed64 part_checksums = 6;
+
+ repeated fixed64 block_checksums = 7;
+}
+
+// Chunk meta extension.
+message TStripedErasurePlacementExt
+{
+ message TPartInfo
+ {
+ // Sizes of segments in given part.
+ repeated int64 segment_sizes = 1;
+
+ // Checksums of segments in given part.
+ repeated fixed64 segment_checksums = 2;
+ }
+ repeated TPartInfo part_infos = 1;
+
+ // Numbers of blocks in segments.
+ repeated int32 segment_block_counts = 2;
+
+ // Sizes of the original blocks.
+ repeated int64 block_sizes = 3;
+
+ // Some of the input blocks can be padded to fit into the segment.
+ repeated int64 block_padding_sizes = 5;
+
+ // Checksums of the original blocks.
+ repeated fixed64 block_checksums = 4;
+}
+
+// TChunkMeta is stored in *.meta files on data nodes
+// and passed around (possibly with a different extension subset).
+message TChunkMeta
+{
+ // The type (EChunkType) of data stored in this chunk (e.g. tabular, file blob etc).
+ required int32 type = 1;
+
+ // Format (EChunkFormat) validated by reader.
+ required int32 format = 3;
+
+ // EChunkFeatures bitmask of the features that are used in this chunk.
+ // Chunk reader validates that it supports all the chunk's features.
+ optional uint64 features = 4;
+
+ required NYT.NProto.TExtensionSet extensions = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.proto b/yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.proto
new file mode 100644
index 0000000000..f89a1c2321
--- /dev/null
+++ b/yt/yt_proto/yt/client/chunk_client/proto/chunk_spec.proto
@@ -0,0 +1,90 @@
+package NYT.NChunkClient.NProto;
+
+import "yt_proto/yt/core/misc/proto/guid.proto";
+import "yt_proto/yt/client/chunk_client/proto/read_limit.proto";
+import "yt_proto/yt/client/chunk_client/proto/chunk_meta.proto";
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/chunk_client";
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Describes a portion of table chunk.
+message TChunkSpec
+{
+ // COMPAT(babenko)
+ // The way these fields are used is quite chaotic. Possible encodings are as follows:
+ // 1) chunk_id is the actual chunk id, others are not used (for static tables only);
+ // 2) chunk_id is a tablet id, cell_id is the id of the cell hosting this tablet (for queries);
+ // 3) chunk_id is a dynamic store id, tablet_id is the id of the tablet containing this store, and
+ // cell_id is the id of the cell hosting this tablet (for dynamic store reads).
+ // In all cases, cell_id could be missing (for legacy clients).
+ required NYT.NProto.TGuid chunk_id = 1;
+ optional NYT.NProto.TGuid tablet_id = 20;
+ optional NYT.NProto.TGuid cell_id = 22;
+
+ // Lower boundary, inclusive.
+ optional TReadLimit lower_limit = 2;
+
+ // Upper boundary, exclusive.
+ optional TReadLimit upper_limit = 3;
+
+ // COMPAT(babenko)
+ // Initial seed replicas. The reader may ask the master for additional ones.
+ // No medium indices.
+ repeated fixed32 legacy_replicas = 4;
+
+ // COMPAT(babenko)
+ // Initial seeds replicas. The reader may ask the master for additional ones.
+ repeated fixed64 replicas = 25;
+
+ optional int32 table_index = 7 [default = 0];
+
+ optional int32 erasure_codec = 9 [default = 0];
+
+ optional int64 table_row_index = 10 [default = 0];
+
+ // Chunk meta can be omitted for intermediate chunks.
+ optional TChunkMeta chunk_meta = 11;
+
+ // Only makes sense as a part of data split.
+ optional uint64 timestamp = 12;
+
+ // Mapping to ranges.
+ optional int32 range_index = 13;
+
+ // Overrides the corresponding values in TMiscExt.
+ optional int64 row_count_override = 14;
+ optional int64 data_weight_override = 15;
+
+ // Tag of the input data slice this chunk spec belongs to (if any).
+ // It helps us restore the correspondence between data slices that
+ // form the job input and the unread data slices returned as a
+ // job interruption result.
+ optional int64 data_slice_tag = 16;
+
+ // Global chunk index for the operation. Helps to identify chunk_spec
+ // if fetch returns the same chunk with different limits
+ // (e.g. the chunk is shared between dynamic table tablets).
+ optional int64 chunk_index = 17;
+
+ // If set, all values should be treated as having this timestamp, regardless of
+ // what is written at chunk meta or versioned values.
+ // May be set by some chunk view.
+ optional uint64 override_timestamp = 18;
+
+ // Denotes that all values with timestamps strictly greater than this timestamp
+ // should not be read. May be set by some chunk view.
+ optional uint64 max_clip_timestamp = 23;
+
+ optional int32 tablet_index = 19;
+
+ // True if row_index upper/lower_limit is absolute (table-wise or tablet-wise)
+ // rather than relative (chunk-wise).
+ optional bool row_index_is_absolute = 21 [default = false];
+
+ optional bool striped_erasure = 24;
+
+ reserved 5, 6, 8;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/chunk_client/proto/confirm_chunk_replica_info.proto b/yt/yt_proto/yt/client/chunk_client/proto/confirm_chunk_replica_info.proto
new file mode 100644
index 0000000000..e76a5b4807
--- /dev/null
+++ b/yt/yt_proto/yt/client/chunk_client/proto/confirm_chunk_replica_info.proto
@@ -0,0 +1,15 @@
+package NYT.NChunkClient.NProto;
+
+import "yt_proto/yt/core/misc/proto/guid.proto";
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/chunk_client";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TConfirmChunkReplicaInfo
+{
+ required fixed64 replica = 1;
+ required NYT.NProto.TGuid location_uuid = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/chunk_client/proto/data_statistics.proto b/yt/yt_proto/yt/client/chunk_client/proto/data_statistics.proto
new file mode 100644
index 0000000000..9696458aac
--- /dev/null
+++ b/yt/yt_proto/yt/client/chunk_client/proto/data_statistics.proto
@@ -0,0 +1,23 @@
+package NYT.NChunkClient.NProto;
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/chunk_client";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TDataStatistics
+{
+ optional int64 uncompressed_data_size = 1 [default = 0];
+ optional int64 compressed_data_size = 2 [default = 0];
+ optional int64 row_count = 3 [default = 0];
+ optional int64 chunk_count = 4 [default = 0];
+ optional int64 regular_disk_space = 6 [default = 0];
+ optional int64 erasure_disk_space = 7 [default = 0];
+
+ // For backward compatibility this can be -1 which means "invalid value".
+ optional int64 data_weight = 8 [default = 0];
+
+ optional int64 unmerged_row_count = 9 [default = 0];
+ optional int64 unmerged_data_weight = 10 [default = 0];
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/chunk_client/proto/read_limit.proto b/yt/yt_proto/yt/client/chunk_client/proto/read_limit.proto
new file mode 100644
index 0000000000..f6a49a84e5
--- /dev/null
+++ b/yt/yt_proto/yt/client/chunk_client/proto/read_limit.proto
@@ -0,0 +1,35 @@
+package NYT.NChunkClient.NProto;
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/chunk_client";
+
+////////////////////////////////////////////////////////////////////////////////
+
+// A boundary can be given by:
+// * row index (for tables and journals only)
+// * chunk index
+// * offset (for files only)
+// * key (for tables only)
+// A boundary is either inclusive or exclusive depending of the context.
+message TReadLimit
+{
+ optional int64 row_index = 1;
+ optional int32 chunk_index = 2;
+ optional int64 offset = 3;
+ optional bytes legacy_key = 4;
+ optional bytes key_bound_prefix = 7;
+ optional bool key_bound_is_inclusive = 8;
+ optional int32 tablet_index = 6;
+
+ // Sometimes keys in read limits are transported in wire format via attachments.
+ // This index indicates the proper key inside attachments' rowset.
+ // Attachment with key bound keyset shares same index.
+ optional int32 key_index = 5;
+}
+
+message TReadRange
+{
+ optional TReadLimit lower_limit = 1;
+ optional TReadLimit upper_limit = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/discovery_client/proto/discovery_client_service.proto b/yt/yt_proto/yt/client/discovery_client/proto/discovery_client_service.proto
new file mode 100644
index 0000000000..46be046134
--- /dev/null
+++ b/yt/yt_proto/yt/client/discovery_client/proto/discovery_client_service.proto
@@ -0,0 +1,60 @@
+package NYT.NDiscoveryClient.NProto;
+
+import "yt_proto/yt/core/ytree/proto/attributes.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TMemberInfo
+{
+ required string id = 1;
+ required int64 priority = 2;
+ required int64 revision = 3;
+ optional NYT.NYTree.NProto.TAttributeDictionary attributes = 4;
+}
+
+message TGroupMeta
+{
+ required int32 member_count = 1;
+}
+
+message TListMembersOptions
+{
+ required int32 limit = 1;
+ repeated string attribute_keys = 2;
+}
+
+message TReqListMembers
+{
+ required string group_id = 1;
+ required TListMembersOptions options = 2;
+}
+
+message TRspListMembers
+{
+ repeated TMemberInfo members = 1;
+}
+
+message TReqGetGroupMeta
+{
+ required string group_id = 1;
+}
+
+message TRspGetGroupMeta
+{
+ required TGroupMeta meta = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqHeartbeat
+{
+ required string group_id = 1;
+ required TMemberInfo member_info = 2;
+ required int64 lease_timeout = 3;
+}
+
+message TRspHeartbeat
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
diff --git a/yt/yt_proto/yt/client/hedging/proto/config.proto b/yt/yt_proto/yt/client/hedging/proto/config.proto
new file mode 100644
index 0000000000..c69bad5621
--- /dev/null
+++ b/yt/yt_proto/yt/client/hedging/proto/config.proto
@@ -0,0 +1,75 @@
+package NYT.NClient.NHedging.NRpc;
+
+import "yt_proto/yt/core/yson/proto/protobuf_interop.proto";
+
+option (NYT.NYson.NProto.derive_underscore_case_names) = true;
+
+enum ECompressionCodec {
+ None = 0;
+ Lz4 = 1;
+};
+
+// Connection options.
+message TConfig
+{
+ optional string ClusterName = 1;
+ optional string ProxyRole = 2;
+ optional uint32 ChannelPoolSize = 3;
+ optional uint32 ChannelPoolRebalanceIntervalSeconds = 4;
+
+ // All timeouts in milliseconds.
+ optional uint32 DefaultTransactionTimeout = 5;
+ optional uint32 DefaultSelectRowsTimeout = 6;
+ optional uint32 DefaultLookupRowsTimeout = 7;
+ optional uint32 DefaultTotalStreamingTimeout = 8;
+ optional uint32 DefaultStreamingStallTimeout = 9;
+ optional uint32 DefaultPingPeriod = 10;
+ optional uint32 ModifyRowsBatchCapacity = 11;
+
+ optional ECompressionCodec ResponseCodec = 12 [default = None];
+
+ optional bool EnableRetries = 13;
+ optional uint32 RetryBackoffTime = 14;
+ optional uint32 RetryAttempts = 15;
+ optional uint32 RetryTimeout = 16;
+};
+
+message TClustersConfig
+{
+ // In DefaultConfig and ClusterConfigs field .ClusterName is ignored
+ // and field .ProxyRole can be overwritten with explicitly provided in cluster url one.
+
+ // DefaultConfig is used for clusters not mentioned in ClusterConfigs.
+ optional TConfig DefaultConfig = 1;
+ // Per-cluster configs.
+ map<string, TConfig> ClusterConfigs = 2 [
+ (NYT.NYson.NProto.yson_map) = true
+ ];
+};
+
+message THedgingClientConfig
+{
+ // All timeouts also in milliseconds.
+ message TClientOptions {
+ optional TConfig ClientConfig = 1;
+ optional uint32 InitialPenalty = 2;
+ };
+
+ repeated TClientOptions Clients = 1;
+ optional uint32 BanPenalty = 2 [default = 1]; // The penalty increment during ban.
+ optional uint32 BanDuration = 3 [default = 50]; // How long need to ban cllent after error.
+ map<string, string> Tags = 4 [
+ (NYT.NYson.NProto.yson_map) = true
+ ]; // Tags for profiling.
+}
+
+message TReplicationLagPenaltyProviderConfig
+{
+ repeated string ReplicaClusters = 1; // Clusters that need checks for replication lag.
+ optional string TablePath = 2; // Table that needs checks for replication lag.
+ optional uint32 LagPenalty = 3 [default = 10]; // penalty in milliseconds, same as BanPenalty in THedgingClientConfig.
+ optional uint32 MaxTabletLag = 4 [default = 300]; // Tablet is considered "lagged" if CurrentTimestamp - TabletLastReplicationTimestamp >= MaxTabletLag (in seconds).
+ optional float MaxTabletsWithLagFraction = 5 [default = 0.05]; // Real value from 0.0 to 1.0. Replica cluster recieves LagPenalty if NumberOfTabletsWithLag >= MaxTabletsWithLagFraction * TotalNumberOfTablets.
+ optional uint32 CheckPeriod = 6 [default = 60]; // Replication lag check period (in seconds).
+ optional bool ClearPenaltiesOnErrors = 7 [default = false]; // In case of any erros from master client - clear all penalties.
+}
diff --git a/yt/yt_proto/yt/client/hedging/ya.make b/yt/yt_proto/yt/client/hedging/ya.make
new file mode 100644
index 0000000000..ed37725349
--- /dev/null
+++ b/yt/yt_proto/yt/client/hedging/ya.make
@@ -0,0 +1,11 @@
+PROTO_LIBRARY(yt-client-hedging-proto)
+
+SRCS(
+ proto/config.proto
+)
+
+PEERDIR(yt/yt_proto/yt/core)
+
+EXCLUDE_TAGS(GO_PROTO)
+
+END()
diff --git a/yt/yt_proto/yt/client/hive/proto/cluster_directory.proto b/yt/yt_proto/yt/client/hive/proto/cluster_directory.proto
new file mode 100644
index 0000000000..4a6a671b1b
--- /dev/null
+++ b/yt/yt_proto/yt/client/hive/proto/cluster_directory.proto
@@ -0,0 +1,18 @@
+package NYT.NHiveClient.NProto;
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/hive";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TClusterDirectory
+{
+ message TItem
+ {
+ required string name = 1;
+ required bytes config = 2;
+ }
+
+ repeated TItem items = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/hive/proto/timestamp_map.proto b/yt/yt_proto/yt/client/hive/proto/timestamp_map.proto
new file mode 100644
index 0000000000..43f96bcf9f
--- /dev/null
+++ b/yt/yt_proto/yt/client/hive/proto/timestamp_map.proto
@@ -0,0 +1,13 @@
+package NYT.NHiveClient.NProto;
+
+option go_package = "a.yandex-team.ru/yt/go/proto/client/hive";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TTimestampMap
+{
+ repeated int32 cell_tags = 1;
+ repeated uint64 timestamps = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/misc/proto/workload.proto b/yt/yt_proto/yt/client/misc/proto/workload.proto
new file mode 100644
index 0000000000..8cc4592cf6
--- /dev/null
+++ b/yt/yt_proto/yt/client/misc/proto/workload.proto
@@ -0,0 +1,23 @@
+package NYT.NProto;
+
+import "yt_proto/yt/core/rpc/proto/rpc.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TWorkloadDescriptor
+{
+ required int32 category = 1; // EWorkloadCategory
+ required int32 band = 2;
+ optional int64 instant = 3;
+ repeated string annotations = 4;
+}
+
+message TWorkloadDescriptorExt
+{
+ extend NRpc.NProto.TRequestHeader
+ {
+ optional TWorkloadDescriptor workload_descriptor = 114;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/node_tracker_client/proto/node.proto b/yt/yt_proto/yt/client/node_tracker_client/proto/node.proto
new file mode 100644
index 0000000000..497593d75d
--- /dev/null
+++ b/yt/yt_proto/yt/client/node_tracker_client/proto/node.proto
@@ -0,0 +1,243 @@
+package NYT.NNodeTrackerClient.NProto;
+
+import "yt_proto/yt/core/misc/proto/guid.proto";
+import "yt_proto/yt/core/misc/proto/error.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TODO(gritukan): Move it to TReqFullChunkHeartbeat after switching to new heartbeats.
+message TMediumChunkStatistics
+{
+ required int32 medium_index = 1;
+ required int32 chunk_count = 2;
+}
+
+message TIOStatistics
+{
+ optional int64 filesystem_read_rate = 1;
+ optional int64 filesystem_write_rate = 2;
+
+ optional int64 disk_read_rate = 3;
+ optional int64 disk_write_rate = 4;
+
+ optional int64 disk_read_capacity = 5;
+ optional int64 disk_write_capacity = 6;
+}
+
+// Describes the current state of a chunk location.
+message TChunkLocationStatistics
+{
+ optional NYT.NProto.TGuid location_uuid = 12;
+ optional string disk_family = 13 [default = "UNKNOWN"];
+ required int32 medium_index = 7;
+ required int64 available_space = 1;
+ required int64 used_space = 2;
+ required int64 low_watermark_space = 8;
+ required int32 chunk_count = 3;
+ required int32 session_count = 4;
+ required bool enabled = 5;
+ required bool full = 6;
+
+ optional bool throttling_reads = 9 [default = false];
+ optional bool throttling_writes = 10 [default = false];
+ optional bool sick = 11 [default = false];
+
+ optional TIOStatistics io_statistics = 14;
+}
+
+// Describes the current state of a slot location.
+message TSlotLocationStatistics
+{
+ required int32 medium_index = 1;
+ optional int64 available_space = 2;
+ optional int64 used_space = 3;
+ repeated int64 slot_space_usages = 4;
+
+ //! If slot location is disabled, this error contains the reason.
+ optional NYT.NProto.TError error = 5;
+}
+
+// Provides detailed information on memory consumption.
+message TMemoryStatistics
+{
+ message TCategory
+ {
+ required int32 type = 1; // EMemoryCategory
+ required int64 used = 2;
+ optional int64 limit = 3;
+ }
+
+ required int64 total_limit = 1;
+ required int64 total_used = 2;
+ repeated TCategory categories = 3;
+}
+
+// Provides detailed information on cpu distribution.
+message TCpuStatistics
+{
+ required double total_used = 1;
+ optional double total_limit = 2;
+ optional double total_guarantee = 6;
+ required double tablet_slots = 3;
+ required double dedicated = 4;
+ required double jobs = 5;
+}
+
+// Describes the current state of a storage medium.
+message TMediumStatistics
+{
+ required int32 medium_index = 1;
+ required double io_weight = 3;
+}
+
+message TNetworkStatistics
+{
+ required string network = 1;
+ required bool throttling_reads = 2 [default = false];
+}
+
+// Describes the current state of the whole node.
+message TNodeStatistics
+{
+ required int64 total_available_space = 1;
+ required int64 total_used_space = 2;
+ required int32 total_stored_chunk_count = 3;
+ required int32 total_cached_chunk_count = 16;
+ required int32 total_user_session_count = 4;
+ required int32 total_replication_session_count = 7;
+ required int32 total_repair_session_count = 8;
+ required int64 total_low_watermark_space = 14;
+ required bool full = 5;
+ repeated TChunkLocationStatistics chunk_locations = 6;
+ required int32 available_tablet_slots = 11;
+ required int32 used_tablet_slots = 12;
+ required TMemoryStatistics memory = 15;
+ repeated TMediumStatistics media = 17;
+ repeated TNetworkStatistics network = 18;
+ repeated TSlotLocationStatistics slot_locations = 19;
+ optional TCpuStatistics cpu = 20;
+}
+
+// Measures limits and utilization of various resources.
+message TNodeResources
+{
+ optional int32 user_slots = 1;
+ optional double cpu = 2;
+ optional int64 user_memory = 3;
+ optional int32 network = 4;
+ optional int32 replication_slots = 5;
+ optional int32 removal_slots = 6;
+ optional int32 repair_slots = 7;
+ optional int32 seal_slots = 8;
+ optional int32 merge_slots = 13;
+ optional int32 autotomy_slots = 15;
+ optional int32 reincarnation_slots = 17;
+ optional int64 replication_data_size = 9;
+ optional int64 repair_data_size = 10;
+ optional int64 merge_data_size = 14;
+ optional int64 system_memory = 11;
+ optional int32 gpu = 12;
+ optional double vcpu = 16;
+}
+
+message TDiskLocationResources
+{
+ required int64 usage = 1;
+ required int64 limit = 2;
+ optional int32 medium_index = 3;
+}
+
+message TDiskResources
+{
+ repeated TDiskLocationResources disk_location_resources = 1;
+ // Default medium that should be used for jobs without specified disk requests.
+ optional int32 default_medium_index = 2;
+}
+
+// Limits overrides pushed from master to node.
+message TNodeResourceLimitsOverrides
+{
+ optional double cpu = 1;
+ optional int32 network = 2;
+ optional int32 replication_slots = 3;
+ optional int64 replication_data_size = 4;
+ optional int64 merge_data_size = 13;
+ optional int32 removal_slots = 5;
+ optional int32 repair_slots = 6;
+ optional int64 repair_data_size = 7;
+ optional int32 seal_slots = 8;
+ optional int32 merge_slots = 12;
+ optional int32 autotomy_slots = 14;
+ optional int32 reincarnation_slots = 15;
+ optional int64 user_memory = 9;
+ optional int64 system_memory = 10;
+ optional int32 gpu = 11;
+}
+
+// A collection of network name to address mappings.
+message TAddressMap
+{
+ message TAddressEntry
+ {
+ required string network = 1;
+ required string address = 2;
+ }
+
+ repeated TAddressEntry entries = 3;
+}
+
+message TNodeAddressMap
+{
+ message TAddressEntry
+ {
+ required int32 address_type = 1;
+ required TAddressMap addresses = 2;
+ }
+
+ repeated TAddressEntry entries = 3;
+}
+
+message TNodeDescriptor
+{
+ required TAddressMap addresses = 1;
+ optional string host = 5;
+ optional string rack = 2;
+ optional string data_center = 3;
+ repeated string tags = 4;
+ optional int64 last_seen_time = 6;
+}
+
+message TClusterNodeStatistics
+{
+ required TMemoryStatistics memory = 1;
+ repeated TNetworkStatistics network = 2;
+ optional TCpuStatistics cpu = 3;
+}
+
+message TDataNodeStatistics
+{
+ required int64 total_available_space = 1;
+ required int64 total_used_space = 2;
+ required int32 total_stored_chunk_count = 3;
+ required int32 total_cached_chunk_count = 4;
+ required int32 total_user_session_count = 5;
+ required int32 total_replication_session_count = 6;
+ required int32 total_repair_session_count = 7;
+ required int64 total_low_watermark_space = 8;
+ required bool full = 9;
+ repeated TChunkLocationStatistics chunk_locations = 10;
+ repeated TMediumStatistics media = 11;
+}
+
+message TExecNodeStatistics
+{
+ repeated TSlotLocationStatistics slot_locations = 1;
+}
+
+message TCellarNodeStatistics
+{
+ required int32 available_cell_slots = 1;
+ required int32 used_cell_slots = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.proto b/yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.proto
new file mode 100644
index 0000000000..2c383c96c5
--- /dev/null
+++ b/yt/yt_proto/yt/client/node_tracker_client/proto/node_directory.proto
@@ -0,0 +1,20 @@
+package NYT.NNodeTrackerClient.NProto;
+
+import "yt_proto/yt/client/node_tracker_client/proto/node.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Provides a mapping from node ids to descriptors.
+// Used for unpacking replica information.
+message TNodeDirectory
+{
+ message TItem
+ {
+ required int32 node_id = 1;
+ required TNodeDescriptor node_descriptor = 2;
+ }
+
+ repeated TItem items = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/query_client/proto/query_statistics.proto b/yt/yt_proto/yt/client/query_client/proto/query_statistics.proto
new file mode 100644
index 0000000000..acb02c2c8a
--- /dev/null
+++ b/yt/yt_proto/yt/client/query_client/proto/query_statistics.proto
@@ -0,0 +1,25 @@
+package NYT.NQueryClient.NProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TQueryStatistics
+{
+ required int64 rows_read = 1;
+ optional int64 data_weight_read = 11;
+ required int64 rows_written = 2;
+ required uint64 sync_time = 3;
+ required uint64 async_time = 4;
+ required uint64 execute_time = 5;
+ required uint64 read_time = 6;
+ required uint64 write_time = 7;
+ required uint64 codegen_time = 10;
+ optional uint64 wait_on_ready_event_time = 14 [ default = 0 ];
+
+ required bool incomplete_input = 8;
+ required bool incomplete_output = 9;
+ optional uint64 memory_usage = 13 [ default = 0 ];
+ repeated TQueryStatistics inner_statistics = 12;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
diff --git a/yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.proto b/yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.proto
new file mode 100644
index 0000000000..314f8a91c6
--- /dev/null
+++ b/yt/yt_proto/yt/client/table_chunk_format/proto/chunk_meta.proto
@@ -0,0 +1,327 @@
+package NYT.NTableClient.NProto;
+
+import "yt_proto/yt/client/table_chunk_format/proto/column_meta.proto";
+import "yt_proto/yt/core/misc/proto/guid.proto";
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TLogicalType
+{
+ message TStructField
+ {
+ optional string name = 1;
+ optional TLogicalType type = 2;
+ }
+
+ message TStructLogicalType
+ {
+ repeated TStructField fields = 1;
+ }
+
+ message TTupleLogicalType
+ {
+ repeated TLogicalType elements = 1;
+ }
+
+ message TVariantTupleLogicalType
+ {
+ repeated TLogicalType elements = 1;
+ }
+
+ message TVariantStructLogicalType
+ {
+ repeated TStructField fields = 1;
+ }
+
+ message TDictLogicalType
+ {
+ optional TLogicalType key = 1;
+ optional TLogicalType value = 2;
+ }
+
+ message TTaggedLogicalType
+ {
+ optional string tag = 1;
+ optional TLogicalType element = 2;
+ }
+
+ message TDecimalLogicalType
+ {
+ optional int32 precision = 1;
+ optional int32 scale = 2;
+ }
+
+ oneof type {
+ int32 simple = 1;
+ TLogicalType optional = 2;
+ TLogicalType list = 3;
+ TStructLogicalType struct_ = 4;
+ TTupleLogicalType tuple = 5;
+ TVariantTupleLogicalType variant_tuple = 6;
+ TVariantStructLogicalType variant_struct = 7;
+ TDictLogicalType dict = 8;
+ TTaggedLogicalType tagged = 9;
+ TDecimalLogicalType decimal = 10;
+ }
+}
+
+message TColumnSchema
+{
+ required string name = 1;
+ optional string stable_name = 12;
+
+ // `type', `simple_logical_type` `logical_type' describe type of the column.
+ //
+ // `type' contains value of enumeration NTableClient::EValueType and represents physical type.
+ required int32 type = 2;
+
+ // `simple_logical_type' contains value of enumeration NTableClient::ESimpleLogicalValueType.
+ // It describes logical type and was used before introduction of complex logical types.
+ // We might miss it for very old chunks.
+ optional int32 simple_logical_type = 8;
+ optional bool required = 9 [default = false];
+
+ // `logical_type` contains description of logical type that might be complex
+ optional TLogicalType logical_type = 10;
+
+ optional string lock = 3;
+ optional string expression = 4;
+ optional string aggregate = 5;
+ optional int32 sort_order = 6;
+ optional string group = 7;
+ optional int64 max_inline_hunk_size = 11;
+}
+
+message TDeletedColumn
+{
+ required string stable_name = 1;
+}
+
+message TTableSchemaExt
+{
+ repeated TColumnSchema columns = 1;
+ optional bool strict = 2 [default = true];
+ optional bool unique_keys = 3 [default = false];
+ optional int32 schema_modification = 4 [default = 0]; // ETableSchemaModification
+ repeated TDeletedColumn deleted_columns = 5;
+}
+
+message TSchemaDictionary
+{
+ repeated TColumnSchema columns = 1;
+ repeated TDeletedColumn deleted_columns = 3;
+
+ message TTableSchemaInternal
+ {
+ repeated int32 columns = 1;
+ optional bool strict = 2 [default = true];
+ optional bool unique_keys = 3 [default = false];
+ repeated int32 deleted_columns = 4;
+ }
+
+ repeated TTableSchemaInternal tables = 2;
+}
+
+message TColumnNameFilter
+{
+ repeated string admitted_names = 1;
+}
+
+message TColumnFilterDictionary
+{
+ repeated TColumnNameFilter column_filters = 1;
+}
+
+message TNameTableExt
+{
+ repeated string names = 1;
+}
+
+message TDataBlockMeta
+{
+ required int32 row_count = 1;
+ required int64 uncompressed_size = 2;
+ required int64 chunk_row_count = 3;
+
+ required int32 block_index = 7;
+
+ optional int32 partition_index = 8 [default = -1];
+ optional bytes last_key = 9;
+
+ extensions 100 to max;
+}
+
+message TSimpleVersionedBlockMeta
+{
+ extend TDataBlockMeta
+ {
+ optional TSimpleVersionedBlockMeta block_meta_ext = 100;
+ }
+
+ required int32 value_count = 1;
+ required int32 timestamp_count = 2;
+}
+
+message TDataBlockMetaExt
+{
+ repeated TDataBlockMeta data_blocks = 1;
+
+ reserved 2;
+}
+
+message THashTableChunkIndexSystemBlockMeta
+{
+ extend TSystemBlockMeta
+ {
+ optional THashTableChunkIndexSystemBlockMeta hash_table_chunk_index_system_block_meta_ext = 100;
+ }
+
+ optional uint64 seed = 1;
+ optional int32 slot_count = 2;
+
+ optional bytes last_key = 3;
+}
+
+message TXorFilterSystemBlockMeta
+{
+ extend TSystemBlockMeta
+ {
+ optional TXorFilterSystemBlockMeta xor_filter_system_block_meta_ext = 101;
+ }
+
+ optional bytes last_key = 1;
+}
+
+message TSystemBlockMeta
+{
+ extensions 100 to 199;
+}
+
+message TSystemBlockMetaExt
+{
+ repeated TSystemBlockMeta system_blocks = 1;
+}
+
+// Chunk meta extension.
+message TBoundaryKeysExt
+{
+ required bytes min = 1;
+ required bytes max = 2;
+}
+
+message TSamplesExt
+{
+ repeated bytes entries = 1;
+ repeated int32 weights = 2;
+}
+
+message TPartitionsExt
+{
+ repeated int64 row_counts = 1;
+ repeated int64 uncompressed_data_sizes = 2;
+}
+
+message TKeyColumnsExt
+{
+ repeated string names = 1;
+}
+
+message TSortColumnsExt
+{
+ repeated string names = 1;
+ repeated int32 sort_orders = 2;
+}
+
+message TColumnMetaExt
+{
+ repeated NTableChunkFormat.NProto.TColumnMeta columns = 1;
+}
+
+message TColumnarStatisticsExt
+{
+ repeated int64 column_data_weights = 1;
+ // Total weight of all write and delete timestamps.
+ optional int64 timestamp_total_weight = 2;
+
+ // This field is reserved for consistency with TColumnarStatistics message. It has never been used.
+ reserved 3;
+
+ // Per-column approximate minimum values. For more information check comments in NYT::NTableClient::TColumnarStatistics.
+ optional bytes column_min_values = 4;
+ // Per-column approximate maximum values. For more information check comments in NYT::NTableClient::TColumnarStatistics.
+ optional bytes column_max_values = 5;
+ // Number of non-null values in each column.
+ repeated int64 column_non_null_value_counts = 6;
+
+ // Total number of rows in a chunk.
+ optional int64 chunk_row_count = 7;
+ // This field is reserved for consistency with TColumnarStatistics message. It has never been used.
+ reserved 8;
+}
+
+message TColumnRenameDescriptor
+{
+ required string original_name = 1;
+ required string new_name = 2;
+}
+
+message THeavyColumnStatisticsExt
+{
+ required int32 version = 1;
+
+ // NB(gritukan): fixed32 is used instead of uint32 here intentionally.
+ // It makes weights of two equal chunks equal and more space-efficient on average.
+ optional fixed32 salt = 2;
+
+ optional int64 data_weight_unit = 3;
+
+ repeated fixed32 column_name_hashes = 4;
+
+ optional bytes column_data_weights = 5;
+}
+
+message THunkChunkRef
+{
+ required NYT.NProto.TGuid chunk_id = 1;
+ optional int64 hunk_count = 2;
+ optional int64 total_hunk_length = 3;
+ optional int32 erasure_codec = 4; // NErasure::ECodec
+}
+
+message THunkChunkRefsExt
+{
+ repeated THunkChunkRef refs = 1;
+}
+
+// Bits of hunk chunk metas that are stored into meta of the chunk
+// referencing them. This extension is not stored at master however
+// try to keep it small.
+message THunkChunkMeta
+{
+ required NYT.NProto.TGuid chunk_id = 1;
+
+ // NB: Block sizes equal zero for regular chunks. Also,
+ // block sizes may be equal zero for blocks without relevant hunks.
+ repeated int64 block_sizes = 2;
+}
+
+message THunkChunkMetasExt
+{
+ repeated THunkChunkMeta metas = 1;
+}
+
+message THunkChunkMiscExt
+{
+ optional int64 hunk_count = 1;
+ optional int64 total_hunk_length = 2;
+}
+
+message TVersionedRowDigestExt
+{
+ repeated int64 timestamp_count_log_histogram = 1;
+ repeated uint64 earliest_nth_timestamp = 2;
+ optional bytes last_timestamp_digest = 3;
+ optional bytes all_but_last_timestamp_digest = 4;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/table_chunk_format/proto/column_meta.proto b/yt/yt_proto/yt/client/table_chunk_format/proto/column_meta.proto
new file mode 100644
index 0000000000..97f480102b
--- /dev/null
+++ b/yt/yt_proto/yt/client/table_chunk_format/proto/column_meta.proto
@@ -0,0 +1,77 @@
+package NYT.NTableChunkFormat.NProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TSegmentMeta
+{
+ required int32 version = 1;
+ required int32 type = 2;
+ required int64 row_count = 3;
+ required int32 block_index = 4;
+ required int64 offset = 5;
+ required int64 chunk_row_count = 6;
+ required int64 size = 7;
+
+ extensions 100 to max;
+}
+
+message TColumnMeta
+{
+ repeated TSegmentMeta segments = 1;
+}
+
+message TTimestampSegmentMeta
+{
+ extend TSegmentMeta
+ {
+ optional TTimestampSegmentMeta timestamp_segment_meta = 100;
+ }
+
+ required uint64 min_timestamp = 1;
+
+ required uint32 expected_writes_per_row = 2;
+
+ required uint32 expected_deletes_per_row = 3;
+}
+
+message TIntegerSegmentMeta
+{
+ extend TSegmentMeta
+ {
+ optional TIntegerSegmentMeta integer_segment_meta = 101;
+ }
+
+ required uint64 min_value = 1;
+}
+
+message TStringSegmentMeta
+{
+ extend TSegmentMeta
+ {
+ optional TStringSegmentMeta string_segment_meta = 122;
+ }
+
+ required uint32 expected_length = 1;
+}
+
+message TDenseVersionedSegmentMeta
+{
+ extend TSegmentMeta
+ {
+ optional TDenseVersionedSegmentMeta dense_versioned_segment_meta = 123;
+ }
+
+ required uint32 expected_values_per_row = 1;
+}
+
+message TSchemalessSegmentMeta
+{
+ extend TSegmentMeta
+ {
+ optional TSchemalessSegmentMeta schemaless_segment_meta = 124;
+ }
+
+ required uint32 expected_bytes_per_row = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.proto b/yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.proto
new file mode 100644
index 0000000000..04a1af9eb1
--- /dev/null
+++ b/yt/yt_proto/yt/client/table_chunk_format/proto/wire_protocol.proto
@@ -0,0 +1,20 @@
+package NYT.NTableClient.NProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TColumnFilter
+{
+ repeated int32 indexes = 1;
+}
+
+message TReqLookupRows
+{
+ optional TColumnFilter column_filter = 1;
+}
+
+message TReqVersionedLookupRows
+{
+ optional TColumnFilter column_filter = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt_proto/yt/client/transaction_client/proto/timestamp_service.proto b/yt/yt_proto/yt/client/transaction_client/proto/timestamp_service.proto
new file mode 100644
index 0000000000..792e964de3
--- /dev/null
+++ b/yt/yt_proto/yt/client/transaction_client/proto/timestamp_service.proto
@@ -0,0 +1,16 @@
+package NYT.NTransactionClient.NProto;
+
+////////////////////////////////////////////////////////////////////////////////
+
+message TReqGenerateTimestamps
+{
+ optional int32 count = 1 [default = 1];
+}
+
+message TRspGenerateTimestamps
+{
+ required int64 timestamp = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
diff --git a/yt/yt_proto/yt/client/ya.make b/yt/yt_proto/yt/client/ya.make
new file mode 100644
index 0000000000..0ac05be114
--- /dev/null
+++ b/yt/yt_proto/yt/client/ya.make
@@ -0,0 +1,48 @@
+PROTO_LIBRARY()
+
+PROTO_NAMESPACE(yt)
+
+PY_NAMESPACE(yt_proto.yt.client)
+
+PEERDIR(
+ yt/yt_proto/yt/core
+)
+
+INCLUDE(${ARCADIA_ROOT}/yt/gradle.inc)
+
+SRCS(
+ api/rpc_proxy/proto/api_service.proto
+ api/rpc_proxy/proto/discovery_service.proto
+
+ cell_master/proto/cell_directory.proto
+
+ chaos_client/proto/replication_card.proto
+
+ chunk_client/proto/data_statistics.proto
+ chunk_client/proto/chunk_meta.proto
+ chunk_client/proto/read_limit.proto
+ chunk_client/proto/chunk_spec.proto
+ chunk_client/proto/confirm_chunk_replica_info.proto
+
+ discovery_client/proto/discovery_client_service.proto
+
+ hive/proto/timestamp_map.proto
+ hive/proto/cluster_directory.proto
+
+ node_tracker_client/proto/node.proto
+ node_tracker_client/proto/node_directory.proto
+
+ table_chunk_format/proto/chunk_meta.proto
+ table_chunk_format/proto/column_meta.proto
+ table_chunk_format/proto/wire_protocol.proto
+
+ transaction_client/proto/timestamp_service.proto
+
+ query_client/proto/query_statistics.proto
+
+ misc/proto/workload.proto
+)
+
+EXCLUDE_TAGS(GO_PROTO)
+
+END()