All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.ytsaurus.client.ApiServiceClientImpl Maven / Gradle / Ivy

The newest version!
package tech.ytsaurus.client;

import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.protobuf.MessageLite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.ytsaurus.client.operations.Operation;
import tech.ytsaurus.client.operations.OperationImpl;
import tech.ytsaurus.client.operations.Spec;
import tech.ytsaurus.client.operations.SpecPreparationContext;
import tech.ytsaurus.client.request.AbortJob;
import tech.ytsaurus.client.request.AbortOperation;
import tech.ytsaurus.client.request.AbortQuery;
import tech.ytsaurus.client.request.AbortTransaction;
import tech.ytsaurus.client.request.AbstractLookupRowsRequest;
import tech.ytsaurus.client.request.AbstractModifyRowsRequest;
import tech.ytsaurus.client.request.AdvanceConsumer;
import tech.ytsaurus.client.request.AlterQuery;
import tech.ytsaurus.client.request.AlterTable;
import tech.ytsaurus.client.request.AlterTableReplica;
import tech.ytsaurus.client.request.Atomicity;
import tech.ytsaurus.client.request.BaseOperation;
import tech.ytsaurus.client.request.BuildSnapshot;
import tech.ytsaurus.client.request.CheckClusterLiveness;
import tech.ytsaurus.client.request.CheckPermission;
import tech.ytsaurus.client.request.CommitTransaction;
import tech.ytsaurus.client.request.CompleteOperation;
import tech.ytsaurus.client.request.ConcatenateNodes;
import tech.ytsaurus.client.request.CopyNode;
import tech.ytsaurus.client.request.CreateNode;
import tech.ytsaurus.client.request.CreateObject;
import tech.ytsaurus.client.request.ExistsNode;
import tech.ytsaurus.client.request.FreezeTable;
import tech.ytsaurus.client.request.GcCollect;
import tech.ytsaurus.client.request.GenerateTimestamps;
import tech.ytsaurus.client.request.GetFileFromCache;
import tech.ytsaurus.client.request.GetFileFromCacheResult;
import tech.ytsaurus.client.request.GetInSyncReplicas;
import tech.ytsaurus.client.request.GetJob;
import tech.ytsaurus.client.request.GetJobStderr;
import tech.ytsaurus.client.request.GetJobStderrResult;
import tech.ytsaurus.client.request.GetNode;
import tech.ytsaurus.client.request.GetOperation;
import tech.ytsaurus.client.request.GetQuery;
import tech.ytsaurus.client.request.GetQueryResult;
import tech.ytsaurus.client.request.GetTablePivotKeys;
import tech.ytsaurus.client.request.GetTabletInfos;
import tech.ytsaurus.client.request.HighLevelRequest;
import tech.ytsaurus.client.request.LinkNode;
import tech.ytsaurus.client.request.ListJobs;
import tech.ytsaurus.client.request.ListJobsResult;
import tech.ytsaurus.client.request.ListNode;
import tech.ytsaurus.client.request.ListQueries;
import tech.ytsaurus.client.request.ListQueriesResult;
import tech.ytsaurus.client.request.LockNode;
import tech.ytsaurus.client.request.LockNodeResult;
import tech.ytsaurus.client.request.MapOperation;
import tech.ytsaurus.client.request.MapReduceOperation;
import tech.ytsaurus.client.request.MergeOperation;
import tech.ytsaurus.client.request.MountTable;
import tech.ytsaurus.client.request.MoveNode;
import tech.ytsaurus.client.request.MultiLookupRowsRequest;
import tech.ytsaurus.client.request.MultiTablePartition;
import tech.ytsaurus.client.request.MutateNode;
import tech.ytsaurus.client.request.MutatingOptions;
import tech.ytsaurus.client.request.PartitionTables;
import tech.ytsaurus.client.request.PingTransaction;
import tech.ytsaurus.client.request.PullConsumer;
import tech.ytsaurus.client.request.PutFileToCache;
import tech.ytsaurus.client.request.PutFileToCacheResult;
import tech.ytsaurus.client.request.Query;
import tech.ytsaurus.client.request.QueryResult;
import tech.ytsaurus.client.request.ReadFile;
import tech.ytsaurus.client.request.ReadQueryResult;
import tech.ytsaurus.client.request.ReadTable;
import tech.ytsaurus.client.request.ReduceOperation;
import tech.ytsaurus.client.request.RegisterQueueConsumer;
import tech.ytsaurus.client.request.RemoteCopyOperation;
import tech.ytsaurus.client.request.RemountTable;
import tech.ytsaurus.client.request.RemoveNode;
import tech.ytsaurus.client.request.RequestBase;
import tech.ytsaurus.client.request.ReshardTable;
import tech.ytsaurus.client.request.ResumeOperation;
import tech.ytsaurus.client.request.SelectRowsRequest;
import tech.ytsaurus.client.request.SetNode;
import tech.ytsaurus.client.request.SortOperation;
import tech.ytsaurus.client.request.StartOperation;
import tech.ytsaurus.client.request.StartQuery;
import tech.ytsaurus.client.request.StartTransaction;
import tech.ytsaurus.client.request.SuspendOperation;
import tech.ytsaurus.client.request.TableReplicaMode;
import tech.ytsaurus.client.request.TableReq;
import tech.ytsaurus.client.request.TabletInfo;
import tech.ytsaurus.client.request.TabletInfoReplica;
import tech.ytsaurus.client.request.TransactionalOptions;
import tech.ytsaurus.client.request.TrimTable;
import tech.ytsaurus.client.request.UnfreezeTable;
import tech.ytsaurus.client.request.UnmountTable;
import tech.ytsaurus.client.request.UpdateOperationParameters;
import tech.ytsaurus.client.request.VanillaOperation;
import tech.ytsaurus.client.request.WriteFile;
import tech.ytsaurus.client.request.WriteTable;
import tech.ytsaurus.client.rows.ConsumerSource;
import tech.ytsaurus.client.rows.ConsumerSourceRet;
import tech.ytsaurus.client.rows.EntitySkiffSerializer;
import tech.ytsaurus.client.rows.QueueRowset;
import tech.ytsaurus.client.rows.UnversionedRowset;
import tech.ytsaurus.client.rows.VersionedRowset;
import tech.ytsaurus.client.rpc.RpcClient;
import tech.ytsaurus.client.rpc.RpcClientRequestBuilder;
import tech.ytsaurus.client.rpc.RpcClientResponse;
import tech.ytsaurus.client.rpc.RpcClientStreamControl;
import tech.ytsaurus.client.rpc.RpcOptions;
import tech.ytsaurus.client.rpc.RpcRequestsTestingController;
import tech.ytsaurus.client.rpc.RpcStreamConsumer;
import tech.ytsaurus.client.rpc.RpcUtil;
import tech.ytsaurus.core.GUID;
import tech.ytsaurus.core.YtTimestamp;
import tech.ytsaurus.core.cypress.RichYPathParser;
import tech.ytsaurus.core.cypress.YPath;
import tech.ytsaurus.core.request.LockMode;
import tech.ytsaurus.core.rows.YTreeRowSerializer;
import tech.ytsaurus.core.rows.YTreeSerializer;
import tech.ytsaurus.core.tables.TableSchema;
import tech.ytsaurus.lang.NonNullApi;
import tech.ytsaurus.lang.NonNullFields;
import tech.ytsaurus.rpc.TRequestHeader;
import tech.ytsaurus.rpcproxy.EAtomicity;
import tech.ytsaurus.rpcproxy.EOperationType;
import tech.ytsaurus.rpcproxy.ETableReplicaMode;
import tech.ytsaurus.rpcproxy.TCheckPermissionResult;
import tech.ytsaurus.rpcproxy.TReqGetInSyncReplicas;
import tech.ytsaurus.rpcproxy.TReqModifyRows;
import tech.ytsaurus.rpcproxy.TReqReadFile;
import tech.ytsaurus.rpcproxy.TReqStartTransaction;
import tech.ytsaurus.rpcproxy.TReqWriteFile;
import tech.ytsaurus.rpcproxy.TRowsetDescriptor;
import tech.ytsaurus.rpcproxy.TRspLookupRows;
import tech.ytsaurus.rpcproxy.TRspMultiLookup;
import tech.ytsaurus.rpcproxy.TRspReadFile;
import tech.ytsaurus.rpcproxy.TRspSelectRows;
import tech.ytsaurus.rpcproxy.TRspStartTransaction;
import tech.ytsaurus.rpcproxy.TRspVersionedLookupRows;
import tech.ytsaurus.rpcproxy.TRspWriteFile;
import tech.ytsaurus.ysontree.YTree;
import tech.ytsaurus.ysontree.YTreeBinarySerializer;
import tech.ytsaurus.ysontree.YTreeBuilder;
import tech.ytsaurus.ysontree.YTreeMapNode;
import tech.ytsaurus.ysontree.YTreeNode;
import tech.ytsaurus.ysontree.YTreeNodeUtils;

/**
 * Implementation of ApiServiceClient
 *
 * @see ApiServiceClient
 */
@NonNullFields
public class ApiServiceClientImpl implements ApiServiceClient, Closeable {
    private static final Logger logger = LoggerFactory.getLogger(ApiServiceClientImpl.class);
    private static final List JOB_TYPES = Arrays.asList("mapper", "reducer", "reduce_combiner");

    private final ScheduledExecutorService executorService;
    private final Executor heavyExecutor;
    private final ExecutorService prepareSpecExecutor = Executors.newSingleThreadExecutor();
    @Nullable
    private final RpcClient rpcClient;
    private final YTsaurusClientConfig config;
    protected final RpcOptions rpcOptions;
    protected final SerializationResolver serializationResolver;

    public ApiServiceClientImpl(
            @Nullable RpcClient client,
            @Nonnull YTsaurusClientConfig config,
            @Nonnull Executor heavyExecutor,
            @Nonnull ScheduledExecutorService executorService,
            SerializationResolver serializationResolver
    ) {
        OutageController outageController = config.getRpcOptions().getTestingOptions().getOutageController();
        if (client != null && outageController != null) {
            this.rpcClient = new OutageRpcClient(client, outageController);
        } else {
            this.rpcClient = client;
        }
        this.heavyExecutor = Objects.requireNonNull(heavyExecutor);
        this.config = config;
        this.rpcOptions = config.getRpcOptions();
        this.executorService = executorService;
        this.serializationResolver = serializationResolver;
    }

    public ApiServiceClientImpl(
            @Nullable RpcClient client,
            @Nonnull RpcOptions options,
            @Nonnull Executor heavyExecutor,
            @Nonnull ScheduledExecutorService executorService,
            SerializationResolver serializationResolver
    ) {
        this(
                client,
                YTsaurusClientConfig.builder().withPorto().setRpcOptions(options).build(),
                heavyExecutor,
                executorService,
                serializationResolver
        );
    }

    @Override
    public void close() {
        prepareSpecExecutor.shutdown();
    }

    @Override
    public TransactionalClient getRootClient() {
        return this;
    }

    YTsaurusClientConfig getConfig() {
        return config;
    }

    ExecutorService getPrepareSpecExecutor() {
        return prepareSpecExecutor;
    }

    /**
     * Start new master or tablet transaction.
     *
     * @see StartTransaction
     */
    @Override
    public CompletableFuture startTransaction(StartTransaction startTransaction) {
        RpcClientRequestBuilder builder =
                ApiServiceMethodTable.START_TRANSACTION.createRequestBuilder(rpcOptions);
        return RpcUtil.apply(sendRequest(startTransaction, builder), response -> {
            GUID id = RpcUtil.fromProto(response.body().getId());
            YtTimestamp startTimestamp = YtTimestamp.valueOf(response.body().getStartTimestamp());
            RpcClient sender = response.sender();
            ApiServiceTransaction result;

            if (startTransaction.getSticky() && (rpcClient == null || !rpcClient.equals(sender))) {
                logger.trace("Create sticky transaction with new client to proxy {}", sender.getAddressString());
                result = new ApiServiceTransaction(
                        new ApiServiceClientImpl(
                                Objects.requireNonNull(sender), config, heavyExecutor,
                                executorService, serializationResolver),
                        id,
                        startTimestamp,
                        startTransaction.getPing(),
                        startTransaction.getPingAncestors(),
                        startTransaction.getSticky(),
                        startTransaction.getPingPeriod().orElse(null),
                        startTransaction.getFailedPingRetryPeriod().orElse(null),
                        sender.executor(),
                        startTransaction.getOnPingFailed().orElse(null));
            } else {
                if (startTransaction.getSticky()) {
                    logger.trace("Create sticky transaction with client {} to proxy {}", this,
                            sender.getAddressString());
                } else {
                    logger.trace("Create non-sticky transaction with client {}", this);
                }

                result = new ApiServiceTransaction(
                        this,
                        id,
                        startTimestamp,
                        startTransaction.getPing(),
                        startTransaction.getPingAncestors(),
                        startTransaction.getSticky(),
                        startTransaction.getPingPeriod().orElse(null),
                        startTransaction.getFailedPingRetryPeriod().orElse(null),
                        sender.executor(),
                        startTransaction.getOnPingFailed().orElse(null));
            }

            sender.ref();
            result.getTransactionCompleteFuture().whenComplete((ignored, ex) -> sender.unref());

            RpcRequestsTestingController rpcRequestsTestingController =
                    rpcOptions.getTestingOptions().getRpcRequestsTestingController();
            if (rpcRequestsTestingController != null) {
                rpcRequestsTestingController.addStartedTransaction(id);
            }

            logger.debug("New transaction {} has started by {}", id, builder);
            return result;
        });
    }

    /**
     * Ping existing transaction
     *
     * @see PingTransaction
     */
    @Override
    public CompletableFuture pingTransaction(PingTransaction req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.PING_TRANSACTION.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    /**
     * Commit existing transaction
     *
     * @see CommitTransaction
     */
    @Override
    public CompletableFuture commitTransaction(CommitTransaction req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.COMMIT_TRANSACTION.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    /**
     * Abort existing transaction
     *
     * @see AbortTransaction
     */
    @Override
    public CompletableFuture abortTransaction(AbortTransaction req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ABORT_TRANSACTION.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    /* nodes */

    /**
     * Get cypress node
     *
     * @see GetNode
     */
    @Override
    public CompletableFuture getNode(GetNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_NODE.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.parseByteString(response.body().getValue()));
    }

    /**
     * List cypress node
     *
     * @see ListNode
     */
    @Override
    public CompletableFuture listNode(ListNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.LIST_NODE.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.parseByteString(response.body().getValue()));
    }

    /**
     * Set cypress node
     *
     * @see SetNode
     */
    @Override
    public CompletableFuture setNode(SetNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.SET_NODE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    /**
     * Check if cypress node exists
     *
     * @see ExistsNode
     */
    @Override
    public CompletableFuture existsNode(ExistsNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.EXISTS_NODE.createRequestBuilder(rpcOptions)),
                response -> response.body().getExists());
    }

    /**
     * Get table pivot keys.
     *
     * @see GetTablePivotKeys
     */
    @Override
    public CompletableFuture> getTablePivotKeys(GetTablePivotKeys req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_TABLE_PIVOT_KEYS.createRequestBuilder(rpcOptions)),
                response -> YTreeBinarySerializer.deserialize(
                        new ByteArrayInputStream(response.body().getValue().toByteArray())
                ).asList());
    }

    /**
     * Create new master object.
     *
     * @see CreateObject
     */
    public CompletableFuture createObject(CreateObject req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.CREATE_OBJECT.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.fromProto(response.body().getObjectId()));
    }

    /**
     * Check cluster's liveness
     *
     * @see CheckClusterLiveness
     */
    public CompletableFuture checkClusterLiveness(CheckClusterLiveness req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.CHECK_CLUSTER_LIVENESS.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    /**
     * Create cypress node
     *
     * @see CreateNode
     */
    @Override
    public CompletableFuture createNode(CreateNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.CREATE_NODE.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.fromProto(response.body().getNodeId()));
    }

    /**
     * Remove cypress node
     *
     * @see RemoveNode
     */
    @Override
    public CompletableFuture removeNode(RemoveNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.REMOVE_NODE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    /**
     * Lock cypress node
     *
     * @see LockNode
     */
    @Override
    public CompletableFuture lockNode(LockNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.LOCK_NODE.createRequestBuilder(rpcOptions)),
                response -> new LockNodeResult(
                        RpcUtil.fromProto(response.body().getNodeId()),
                        RpcUtil.fromProto(response.body().getLockId())));
    }

    /**
     * Copy cypress node
     *
     * @see CopyNode
     */
    @Override
    public CompletableFuture copyNode(CopyNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.COPY_NODE.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.fromProto(response.body().getNodeId()));
    }

    /**
     * Move cypress node
     *
     * @see MoveNode
     */
    @Override
    public CompletableFuture moveNode(MoveNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.MOVE_NODE.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.fromProto(response.body().getNodeId()));
    }

    /**
     * Link cypress node
     *
     * @see LinkNode
     */
    @Override
    public CompletableFuture linkNode(LinkNode req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.LINK_NODE.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.fromProto(response.body().getNodeId()));
    }

    /**
     * Concatenate nodes
     *
     * @see ConcatenateNodes
     */
    @Override
    public CompletableFuture concatenateNodes(ConcatenateNodes req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.CONCATENATE_NODES.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture> partitionTables(PartitionTables req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.PARTITION_TABLES.createRequestBuilder(rpcOptions)),
                response -> response.body().getPartitionsList().stream()
                        .map(p -> {
                            List tableRanges = p.getTableRangesList().stream()
                                    .map(RichYPathParser::parse)
                                    .collect(Collectors.toList());
                            var statistics = new MultiTablePartition.AggregateStatistics(
                                    p.getAggregateStatistics().getChunkCount(),
                                    p.getAggregateStatistics().getDataWeight(),
                                    p.getAggregateStatistics().getRowCount());
                            return new MultiTablePartition(tableRanges, statistics);
                        })
                        .collect(Collectors.toList()));
    }

    // TODO: TReqAttachTransaction

    /* */

    @Override
    public CompletableFuture lookupRows(AbstractLookupRowsRequest request) {
        return lookupRowsImpl(request, response ->
                ApiServiceUtil.deserializeUnversionedRowset(response.body().getRowsetDescriptor(),
                        response.attachments()));
    }

    @Override
    public  CompletableFuture> lookupRows(
            AbstractLookupRowsRequest request,
            YTreeRowSerializer serializer
    ) {
        return lookupRowsImpl(request, response -> {
            final ConsumerSourceRet result = ConsumerSource.list();
            ApiServiceUtil.deserializeUnversionedRowset(response.body().getRowsetDescriptor(),
                    response.attachments(), serializer, result, serializationResolver);
            return result.get();
        });
    }

    @Override
    public  CompletableFuture lookupRows(
            AbstractLookupRowsRequest request,
            YTreeRowSerializer serializer,
            ConsumerSource consumer
    ) {
        return lookupRowsImpl(request, response -> {
            ApiServiceUtil.deserializeUnversionedRowset(response.body().getRowsetDescriptor(),
                    response.attachments(), serializer, consumer, serializationResolver);
            return null;
        });
    }

    private  CompletableFuture lookupRowsImpl(
            AbstractLookupRowsRequest request,
            Function, T> responseReader
    ) {
        request.convertValues(serializationResolver);
        return handleHeavyResponse(
                sendRequest(
                        request.asLookupRowsWritable(),
                        ApiServiceMethodTable.LOOKUP_ROWS.createRequestBuilder(rpcOptions)
                ),
                response -> {
                    logger.trace("LookupRows incoming rowset descriptor: {}", response.body().getRowsetDescriptor());
                    return responseReader.apply(response);
                });
    }

    @Override
    public CompletableFuture> multiLookupRows(MultiLookupRowsRequest request) {
        return multiLookupImpl(request, response -> multiLookupResponseReader(
                response,
                ApiServiceUtil::deserializeUnversionedRowset
        ));
    }

    @Override
    public  CompletableFuture>> multiLookupRows(
            MultiLookupRowsRequest request,
            YTreeRowSerializer serializer
    ) {
        return multiLookupImpl(request, response ->
                multiLookupResponseReader(
                        response,
                        (rowsetDescriptor, attachments) -> {
                            final ConsumerSourceRet result = ConsumerSource.list();
                            ApiServiceUtil.deserializeUnversionedRowset(
                                    rowsetDescriptor,
                                    attachments,
                                    serializer,
                                    result,
                                    serializationResolver
                            );
                            return result.get();
                        }
                )
        );
    }

    private  CompletableFuture multiLookupImpl(
            MultiLookupRowsRequest request,
            Function, T> responseReader
    ) {
        for (var subrequest : request.getSubrequests()) {
            subrequest.convertValues(serializationResolver);
        }
        return handleHeavyResponse(
                sendRequest(
                        request.asMultiLookupWritable(),
                        ApiServiceMethodTable.MULTI_LOOKUP.createRequestBuilder(rpcOptions)
                ),
                response -> {
                    logger.trace("MultiLookupRows incoming – number of rowset descriptors: {}",
                            response.body().getSubresponsesCount());
                    return responseReader.apply(response);
                });
    }

    private  List multiLookupResponseReader(
            RpcClientResponse response,
            BiFunction, T> rowsetDeserializer
    ) {
        List result = new ArrayList<>(response.body().getSubresponsesCount());
        int beginAttachmentIndex = 0;
        for (var subresponse : response.body().getSubresponsesList()) {
            int endAttachmentIndex = beginAttachmentIndex + subresponse.getAttachmentCount();
            result.add(
                    rowsetDeserializer.apply(
                            subresponse.getRowsetDescriptor(),
                            response.attachments().subList(beginAttachmentIndex, endAttachmentIndex)
                    )
            );
            beginAttachmentIndex = endAttachmentIndex;
        }
        return result;
    }


    @Override
    public CompletableFuture versionedLookupRows(AbstractLookupRowsRequest request) {
        return versionedLookupRowsImpl(request, response -> ApiServiceUtil
                .deserializeVersionedRowset(response.body().getRowsetDescriptor(), response.attachments()));
    }

    private  CompletableFuture versionedLookupRowsImpl(
            AbstractLookupRowsRequest request,
            Function, T> responseReader
    ) {
        request.convertValues(serializationResolver);
        return handleHeavyResponse(
                sendRequest(
                        request.asVersionedLookupRowsWritable(),
                        ApiServiceMethodTable.VERSIONED_LOOKUP_ROWS.createRequestBuilder(rpcOptions)
                ),
                response -> {
                    logger.trace("VersionedLookupRows incoming rowset descriptor: {}",
                            response.body().getRowsetDescriptor());
                    return responseReader.apply(response);
                });
    }

    @Override
    public CompletableFuture selectRowsV2(SelectRowsRequest request) {
        return sendRequest(request, ApiServiceMethodTable.SELECT_ROWS.createRequestBuilder(rpcOptions))
                .thenApply(
                        response -> new SelectRowsResult(response, heavyExecutor, serializationResolver)
                );
    }

    @Override
    public CompletableFuture selectRows(SelectRowsRequest request) {
        return selectRowsImpl(request, response ->
                ApiServiceUtil.deserializeUnversionedRowset(response.body().getRowsetDescriptor(),
                        response.attachments()));
    }

    @Override
    public  CompletableFuture> selectRows(SelectRowsRequest request, YTreeRowSerializer serializer) {
        return selectRowsImpl(request, response -> {
            final ConsumerSourceRet result = ConsumerSource.list();
            ApiServiceUtil.deserializeUnversionedRowset(response.body().getRowsetDescriptor(),
                    response.attachments(), serializer, result, serializationResolver);
            return result.get();
        });
    }

    @Override
    public  CompletableFuture selectRows(SelectRowsRequest request, YTreeRowSerializer serializer,
                                                  ConsumerSource consumer) {
        return selectRowsImpl(request, response -> {
            ApiServiceUtil.deserializeUnversionedRowset(response.body().getRowsetDescriptor(),
                    response.attachments(), serializer, consumer, serializationResolver);
            return null;
        });
    }

    private  CompletableFuture selectRowsImpl(SelectRowsRequest request,
                                                    Function, T> responseReader) {
        return handleHeavyResponse(
                sendRequest(request, ApiServiceMethodTable.SELECT_ROWS.createRequestBuilder(rpcOptions)),
                response -> {
                    logger.trace("SelectRows incoming rowset descriptor: {}", response.body().getRowsetDescriptor());
                    return responseReader.apply(response);
                });
    }


    @Override
    public CompletableFuture modifyRows(GUID transactionId, AbstractModifyRowsRequest request) {
        request.convertValues(serializationResolver);
        return RpcUtil.apply(
                sendRequest(
                        new ModifyRowsWrapper(transactionId, request),
                        ApiServiceMethodTable.MODIFY_ROWS.createRequestBuilder(rpcOptions)
                ),
                response -> null);
    }

    // TODO: TReqBatchModifyRows

    @Override
    public CompletableFuture buildSnapshot(BuildSnapshot req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.BUILD_SNAPSHOT.createRequestBuilder(rpcOptions)),
                response -> response.body().getSnapshotId());
    }

    @Override
    public CompletableFuture gcCollect(GcCollect req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GC_COLLECT.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture mountTable(MountTable req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.MOUNT_TABLE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture unmountTable(UnmountTable req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.UNMOUNT_TABLE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture remountTable(RemountTable req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.REMOUNT_TABLE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture freezeTable(FreezeTable req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.FREEZE_TABLE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture unfreezeTable(UnfreezeTable req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.UNFREEZE_TABLE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture> getInSyncReplicas(GetInSyncReplicas request, YtTimestamp timestamp) {
        request.convertValues(serializationResolver);
        return RpcUtil.apply(
                sendRequest(
                        new GetInSyncReplicasWrapper(timestamp, request),
                        ApiServiceMethodTable.GET_IN_SYNC_REPLICAS.createRequestBuilder(rpcOptions)
                ),
                response -> response.body().getReplicaIdsList()
                        .stream().map(RpcUtil::fromProto).collect(Collectors.toList()));
    }

    @Override
    public CompletableFuture> getTabletInfos(GetTabletInfos req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_TABLET_INFOS.createRequestBuilder(rpcOptions)),
                response ->
                        response.body().getTabletsList()
                                .stream()
                                .map(x -> {
                                    List replicas = x.getReplicasList().stream()
                                            .map(o -> new TabletInfoReplica(
                                                    RpcUtil.fromProto(o.getReplicaId()),
                                                    o.getLastReplicationTimestamp(),
                                                    ETableReplicaMode.forNumber(o.getMode()))
                                            )
                                            .collect(Collectors.toList());
                                    return new TabletInfo(x.getTotalRowCount(), x.getTrimmedRowCount(),
                                            x.getLastWriteTimestamp(), replicas);
                                })
                                .collect(Collectors.toList()));
    }

    @Override
    public CompletableFuture generateTimestamps(GenerateTimestamps req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GENERATE_TIMESTAMPS.createRequestBuilder(rpcOptions)),
                response -> YtTimestamp.valueOf(response.body().getTimestamp()));
    }

    /* tables */
    @Override
    public CompletableFuture reshardTable(ReshardTable req) {
        req.convertValues(serializationResolver);
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.RESHARD_TABLE.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture trimTable(TrimTable req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.TRIM_TABLE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture alterTable(AlterTable req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ALTER_TABLE.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture alterTableReplica(
            GUID replicaId,
            boolean enabled,
            ETableReplicaMode mode,
            boolean preserveTimestamp,
            EAtomicity atomicity
    ) {
        TableReplicaMode convertedMode;
        switch (mode) {
            case TRM_ASYNC:
                convertedMode = TableReplicaMode.Async;
                break;
            case TRM_SYNC:
                convertedMode = TableReplicaMode.Sync;
                break;
            default:
                throw new IllegalArgumentException();
        }
        Atomicity convertedAtomicity;
        switch (atomicity) {
            case A_FULL:
                convertedAtomicity = Atomicity.Full;
                break;
            case A_NONE:
                convertedAtomicity = Atomicity.None;
                break;
            default:
                throw new IllegalArgumentException();
        }

        return alterTableReplica(
                AlterTableReplica.builder()
                        .setReplicaId(replicaId)
                        .setEnabled(enabled)
                        .setMode(convertedMode)
                        .setPreserveTimestamps(preserveTimestamp)
                        .setAtomicity(convertedAtomicity)
                        .build()
        );
    }

    @Override
    public CompletableFuture alterTableReplica(AlterTableReplica req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ALTER_TABLE_REPLICA.createRequestBuilder(rpcOptions)),
                response -> null);
    }

    @Override
    public CompletableFuture startOperation(StartOperation req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.START_OPERATION.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.fromProto(response.body().getOperationId()));
    }

    YTreeMapNode patchSpec(YTreeMapNode spec) {
        YTreeMapNode resultingSpec = spec;

        for (String op : JOB_TYPES) {
            if (resultingSpec.containsKey(op) && config.getJobSpecPatch().isPresent()) {
                YTreeNode patch = YTree.builder()
                        .beginMap()
                        .key(op).value(config.getJobSpecPatch().get())
                        .endMap()
                        .build();
                YTreeBuilder b = YTree.builder();
                YTreeNodeUtils.merge(resultingSpec, patch, b, true);
                resultingSpec = b.build().mapNode();
            }
        }

        if (resultingSpec.containsKey("tasks") && config.getJobSpecPatch().isPresent()) {
            for (Map.Entry entry
                    : resultingSpec.getOrThrow("tasks").asMap().entrySet()
            ) {
                YTreeNode patch = YTree.builder()
                        .beginMap()
                        .key("tasks")
                        .beginMap()
                        .key(entry.getKey()).value(config.getJobSpecPatch().get())
                        .endMap()
                        .endMap()
                        .build();

                YTreeBuilder b = YTree.builder();
                YTreeNodeUtils.merge(resultingSpec, patch, b, true);
                resultingSpec = b.build().mapNode();
            }
        }

        if (config.getSpecPatch().isPresent()) {
            YTreeBuilder b = YTree.builder();
            YTreeNodeUtils.merge(resultingSpec, config.getSpecPatch().get(), b, true);
            resultingSpec = b.build().mapNode();
        }
        return resultingSpec;
    }

    private CompletableFuture prepareSpec(Spec spec, @Nullable TransactionalOptions transactionalOptions) {
        return CompletableFuture.supplyAsync(
                () -> {
                    YTreeBuilder builder = YTree.builder();
                    spec.prepare(builder, this, new SpecPreparationContext(config, transactionalOptions));
                    return patchSpec(builder.build().mapNode());
                },
                prepareSpecExecutor);
    }

     CompletableFuture startPreparedOperation(
            YTreeNode preparedSpec,
            BaseOperation req,
            EOperationType type
    ) {
        return startOperation(
                StartOperation.builder()
                        .setType(type)
                        .setSpec(preparedSpec)
                        .setTransactionalOptions(req.getTransactionalOptions().orElse(null))
                        .setMutatingOptions(req.getMutatingOptions())
                        .build()
        ).thenApply(operationId ->
                new OperationImpl(operationId, this, executorService, config.getOperationPingPeriod()));
    }

    private  CompletableFuture startOperationImpl(
            BaseOperation req,
            EOperationType type
    ) {
        return prepareSpec(req.getSpec(), req.getTransactionalOptions().orElse(null)).thenCompose(
                preparedSpec -> startPreparedOperation(preparedSpec, req, type)
        );
    }

    @Override
    public CompletableFuture startMap(MapOperation req) {
        return startOperationImpl(req, EOperationType.OT_MAP);
    }

    @Override
    public CompletableFuture startReduce(ReduceOperation req) {
        return startOperationImpl(req, EOperationType.OT_REDUCE);
    }

    @Override
    public CompletableFuture startSort(SortOperation req) {
        return startOperationImpl(req, EOperationType.OT_SORT);
    }

    @Override
    public CompletableFuture startMapReduce(MapReduceOperation req) {
        return startOperationImpl(req, EOperationType.OT_MAP_REDUCE);
    }

    @Override
    public CompletableFuture startMerge(MergeOperation req) {
        return startOperationImpl(req, EOperationType.OT_MERGE);
    }

    @Override
    public CompletableFuture startRemoteCopy(RemoteCopyOperation req) {
        return startOperationImpl(req, EOperationType.OT_REMOTE_COPY);
    }

    @Override
    public CompletableFuture startVanilla(VanillaOperation req) {
        return startOperationImpl(req, EOperationType.OT_VANILLA);
    }

    @Override
    public CompletableFuture getOperation(GetOperation req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_OPERATION.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.parseByteString(response.body().getMeta())
        );
    }

    @Override
    public Operation attachOperation(GUID operationId) {
        return new OperationImpl(operationId, this, executorService, config.getOperationPingPeriod());
    }

    @Override
    public CompletableFuture abortOperation(AbortOperation req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ABORT_OPERATION.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture completeOperation(CompleteOperation req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.COMPLETE_OPERATION.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture suspendOperation(SuspendOperation req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.SUSPEND_OPERATION.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture resumeOperation(ResumeOperation req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.RESUME_OPERATION.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture getJob(GetJob req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_JOB.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.parseByteString(response.body().getInfo())
        );
    }

    @Override
    public CompletableFuture listJobs(ListJobs req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.LIST_JOBS.createRequestBuilder(rpcOptions)),
                response -> new ListJobsResult(response.body())
        );
    }

    @Override
    public CompletableFuture getJobStderr(GetJobStderr req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_JOB_STDERR.createRequestBuilder(rpcOptions)),
                response -> new GetJobStderrResult(response.attachments()));
    }

    @Override
    public CompletableFuture abortJob(AbortJob req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ABORT_JOB.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture updateOperationParameters(UpdateOperationParameters req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.UPDATE_OPERATION_PARAMETERS.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture checkPermission(CheckPermission req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.CHECK_PERMISSION.createRequestBuilder(rpcOptions)),
                response -> response.body().getResult());
    }

    @Override
    public CompletableFuture getFileFromCache(GetFileFromCache req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_FILE_FROM_CACHE.createRequestBuilder(rpcOptions)),
                response -> {
                    if (!response.body().getResult().getPath().isEmpty()) {
                        return new GetFileFromCacheResult(YPath.simple(response.body().getResult().getPath()));
                    }
                    return new GetFileFromCacheResult(null);
                });
    }

    @Override
    public CompletableFuture putFileToCache(PutFileToCache req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.PUT_FILE_TO_CACHE.createRequestBuilder(rpcOptions)),
                response -> new PutFileToCacheResult(YPath.simple(response.body().getResult().getPath())));
    }

    @Override
    public CompletableFuture pullConsumer(PullConsumer req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.PULL_CONSUMER.createRequestBuilder(rpcOptions)),
                response -> new QueueRowset(
                        ApiServiceUtil.deserializeUnversionedRowset(
                                response.body().getRowsetDescriptor(),
                                response.attachments()
                        ),
                        response.body().getStartOffset()
                )
        );
    }

    @Override
    public CompletableFuture advanceConsumer(AdvanceConsumer req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ADVANCE_CONSUMER.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture registerQueueConsumer(RegisterQueueConsumer req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.REGISTER_QUEUE_CONSUMER.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture startQuery(StartQuery req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.START_QUERY.createRequestBuilder(rpcOptions)),
                response -> RpcUtil.fromProto(response.body().getQueryId())
        );
    }

    @Override
    public CompletableFuture abortQuery(AbortQuery req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ABORT_QUERY.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public CompletableFuture getQueryResult(GetQueryResult req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_QUERY_RESULT.createRequestBuilder(rpcOptions)),
                response -> new QueryResult(response.body())
        );
    }

    @Override
    public CompletableFuture readQueryResult(ReadQueryResult req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.READ_QUERY_RESULT.createRequestBuilder(rpcOptions)),
                response -> ApiServiceUtil.deserializeUnversionedRowset(
                        response.body().getRowsetDescriptor(), response.attachments()
                )
        );
    }

    @Override
    public CompletableFuture getQuery(GetQuery req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.GET_QUERY.createRequestBuilder(rpcOptions)),
                response -> new Query(response.body().getQuery())
        );
    }

    @Override
    public CompletableFuture listQueries(ListQueries req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.LIST_QUERIES.createRequestBuilder(rpcOptions)),
                response -> new ListQueriesResult(response.body())
        );
    }

    @Override
    public CompletableFuture alterQuery(AlterQuery req) {
        return RpcUtil.apply(
                sendRequest(req, ApiServiceMethodTable.ALTER_QUERY.createRequestBuilder(rpcOptions)),
                response -> null
        );
    }

    @Override
    public  CompletableFuture> readTable(ReadTable req) {
        Optional> attachmentReader = req.getSerializationContext().getAttachmentReader();
        if (attachmentReader.isEmpty()) {
            Optional> serializer = req.getSerializationContext().getYtreeSerializer();
            if (serializer.isPresent()) {
                attachmentReader = Optional.of(new TableAttachmentWireProtocolReader<>(
                        serializationResolver.createWireRowDeserializer(serializer.get())));
            }
        }

        TableReaderImpl tableReader;
        if (attachmentReader.isPresent()) {
            tableReader = new TableReaderImpl<>(attachmentReader.get());
        } else {
            if (req.getSerializationContext().getObjectClass().isEmpty()) {
                throw new IllegalArgumentException("No object clazz");
            }
            tableReader = new TableReaderImpl<>(req, req.getSerializationContext().getObjectClass().get());
        }

        return setTableSchemaInSerializer(req)
                .thenCompose(transactionAndLockResult -> {
                    var readReq = getConfiguredReadReq(req, transactionAndLockResult);
                    if (transactionAndLockResult != null) {
                        tableReader.setTransaction(transactionAndLockResult.getKey());
                    }

                    var builder = ApiServiceMethodTable.READ_TABLE.createRequestBuilder(rpcOptions);
                    readReq.writeHeaderTo(builder.header());
                    readReq.writeTo(builder.body());
                    CompletableFuture streamControlFuture = startStream(builder, tableReader);
                    CompletableFuture> result = streamControlFuture.thenCompose(
                            control -> tableReader.waitMetadata(serializationResolver));
                    RpcUtil.relayCancel(result, streamControlFuture);
                    return result;
                });
    }

    @Override
    public  CompletableFuture> readTableV2(ReadTable req) {
        Optional> attachmentReader = req.getSerializationContext().getAttachmentReader();
        if (attachmentReader.isEmpty()) {
            Optional> serializer = req.getSerializationContext().getYtreeSerializer();
            if (serializer.isPresent()) {
                attachmentReader = Optional.of(new TableAttachmentWireProtocolReader<>(
                        serializationResolver.createWireRowDeserializer(serializer.get())));
            }
        }

        AsyncTableReaderImpl tableReader;
        if (attachmentReader.isPresent()) {
            tableReader = new AsyncTableReaderImpl<>(attachmentReader.get());
        } else {
            if (req.getSerializationContext().getObjectClass().isEmpty()) {
                throw new IllegalArgumentException("No object clazz");
            }
            tableReader = new AsyncTableReaderImpl<>(req,
                    req.getSerializationContext().getObjectClass().get());
        }

        return setTableSchemaInSerializer(req)
                .thenCompose(transactionAndLockResult -> {
                    var readReq = getConfiguredReadReq(req, transactionAndLockResult);
                    if (transactionAndLockResult != null) {
                        tableReader.setTransaction(transactionAndLockResult.getKey());
                    }

                    var builder = ApiServiceMethodTable.READ_TABLE.createRequestBuilder(rpcOptions);
                    readReq.writeHeaderTo(builder.header());
                    readReq.writeTo(builder.body());
                    CompletableFuture streamControlFuture = startStream(builder, tableReader);
                    CompletableFuture> result = streamControlFuture.thenCompose(
                            control -> tableReader.waitMetadata(serializationResolver));
                    RpcUtil.relayCancel(result, streamControlFuture);
                    return result;
                });
    }

    @Override
    public  CompletableFuture> writeTable(WriteTable req) {
        if (req.getNeedRetries()) {
            throw new IllegalStateException("Cannot write table with retries in ApiServiceClient");
        }

        return setTableSchemaInSerializer(req)
                .thenCompose(transactionAndLockResult -> {
                    var writeReq = getWriteReqByTransactionAndLockResult(req, transactionAndLockResult);
                    var tableWriter = new TableWriterImpl<>(writeReq, serializationResolver);
                    if (transactionAndLockResult != null) {
                        tableWriter.setTransaction(transactionAndLockResult.getKey());
                    }

                    var builder = ApiServiceMethodTable.WRITE_TABLE.createRequestBuilder(rpcOptions);
                    writeReq.writeHeaderTo(builder.header());
                    writeReq.writeTo(builder.body());
                    CompletableFuture streamControlFuture = startStream(builder, tableWriter);
                    CompletableFuture> result = streamControlFuture
                            .thenCompose(control -> tableWriter.startUpload());
                    RpcUtil.relayCancel(result, streamControlFuture);
                    return result;
                });
    }

    @Override
    public  CompletableFuture> writeTableV2(WriteTable req) {
        if (req.getNeedRetries()) {
            throw new IllegalStateException("Cannot write table with retries in ApiServiceClient");
        }

        return setTableSchemaInSerializer(req)
                .thenCompose(transactionAndLockResult -> {
                    var writeReq = getWriteReqByTransactionAndLockResult(req, transactionAndLockResult);
                    var tableWriter = new AsyncTableWriterImpl<>(writeReq, serializationResolver);
                    if (transactionAndLockResult != null) {
                        tableWriter.setTransaction(transactionAndLockResult.getKey());
                    }

                    var builder = ApiServiceMethodTable.WRITE_TABLE.createRequestBuilder(rpcOptions);
                    writeReq.writeHeaderTo(builder.header());
                    writeReq.writeTo(builder.body());
                    CompletableFuture streamControlFuture = startStream(builder, tableWriter);
                    CompletableFuture> result = streamControlFuture
                            .thenCompose(control -> tableWriter.startUpload());
                    RpcUtil.relayCancel(result, streamControlFuture);
                    return result;
                });
    }

    @Override
    public CompletableFuture readFile(ReadFile req) {
        RpcClientRequestBuilder
                builder = ApiServiceMethodTable.READ_FILE.createRequestBuilder(rpcOptions);

        req.writeHeaderTo(builder.header());
        req.writeTo(builder.body());

        FileReaderImpl fileReader = new FileReaderImpl();
        CompletableFuture streamControlFuture = startStream(builder, fileReader);
        CompletableFuture result = streamControlFuture.thenCompose(
                control -> fileReader.waitMetadata());
        RpcUtil.relayCancel(result, streamControlFuture);
        return result;
    }

    @Override
    public CompletableFuture writeFile(WriteFile req) {
        RpcClientRequestBuilder
                builder = ApiServiceMethodTable.WRITE_FILE.createRequestBuilder(rpcOptions);

        req.writeHeaderTo(builder.header());
        req.writeTo(builder.body());

        FileWriterImpl fileWriter = new FileWriterImpl(
                req.getWindowSize(),
                req.getPacketSize());
        CompletableFuture streamControlFuture = startStream(builder, fileWriter);
        CompletableFuture result = streamControlFuture.thenCompose(control -> fileWriter.startUpload());
        RpcUtil.relayCancel(result, streamControlFuture);
        return result;
    }

    private  CompletableFuture> setTableSchemaInSerializer(
            WriteTable req
    ) {
        var skiffSerializer = req.getSerializationContext().getSkiffSerializer();
        if (skiffSerializer.isEmpty()) {
            return CompletableFuture.completedFuture(null);
        }
        if (req.getTableSchema().isPresent()) {
            skiffSerializer.get()
                    .setTableSchema(req.getTableSchema().get());
            return CompletableFuture.completedFuture(null);
        }
        return startTransactionAndSetSchema(req.getTransactionId().orElse(null),
                req.getYPath(),
                skiffSerializer.get(),
                req.getYPath().getAppend().orElse(false) ? LockMode.Shared : LockMode.Exclusive);
    }

    private  CompletableFuture> setTableSchemaInSerializer(
            ReadTable req
    ) {
        var skiffSerializer = req.getSerializationContext().getSkiffSerializer();
        if (skiffSerializer.isEmpty()) {
            return CompletableFuture.completedFuture(null);
        }
        if (req.getTableSchema().isPresent()) {
            skiffSerializer.get()
                    .setTableSchema(req.getTableSchema().get());
            return CompletableFuture.completedFuture(null);
        }
        return startTransactionAndSetSchema(req.getTransactionId().orElse(null),
                req.getYPath(),
                skiffSerializer.get(),
                LockMode.Snapshot);
    }

    private  CompletableFuture> startTransactionAndSetSchema(
            GUID transactionId,
            YPath path,
            EntitySkiffSerializer serializer,
            LockMode lockMode) {
        return startTransaction(StartTransaction.master().toBuilder()
                .setParentId(transactionId)
                .build()
        )
                .thenCompose(transaction -> transaction.lockNode(new LockNode(path, lockMode))
                        .thenCompose(lockNodeResult -> transaction.getNode(GetNode.builder()
                                                .setPath(YPath.objectRoot(lockNodeResult.nodeId))
                                                .setAttributes(List.of("schema"))
                                                .build()
                                        )
                                        .thenApply(node ->
                                                TableSchema.fromYTree(node.getAttributeOrThrow("schema"))
                                        )
                                        .thenAccept(serializer::setTableSchema)
                                        .thenApply(unused -> lockNodeResult)
                        )
                        .handle((lockNodeResult, ex) -> {
                            if (ex == null) {
                                return CompletableFuture.completedFuture(lockNodeResult);
                            }
                            return transaction.abort()
                                    .thenAccept(voidRes -> {
                                        throw new RuntimeException(ex);
                                    })
                                    .thenApply(unused -> lockNodeResult);
                        })
                        .thenCompose(future -> future)
                        .thenApply(lockNodeResult -> new AbstractMap.SimpleEntry<>(transaction, lockNodeResult))
                );
    }

    private static  ReadTable getConfiguredReadReq(
            ReadTable req,
            Map.Entry transactionAndLockResult
    ) {
        var readReq = transactionAndLockResult != null ?
                req.toBuilder()
                        .setTransactionalOptions(transactionAndLockResult.getKey().getTransactionalOptions())
                        .setPath(req.getYPath().withObjectRoot(transactionAndLockResult.getValue().nodeId))
                        .build() :
                req;
        if (readReq.getSerializationContext().getSkiffSerializer().isEmpty()) {
            return readReq;
        }
        return readReq.toBuilder()
                .setPath(
                        readReq.getYPath().withColumns(
                                readReq.getSerializationContext().getSkiffSerializer().get()
                                        .getEntityTableSchema().orElseThrow(IllegalStateException::new)
                                        .getColumnNames()
                        )
                )
                .build();
    }

    private static  WriteTable getWriteReqByTransactionAndLockResult(
            WriteTable req,
            Map.Entry transactionAndLockResult
    ) {
        return transactionAndLockResult != null ?
                req.toBuilder()
                        .setTransactionalOptions(transactionAndLockResult.getKey().getTransactionalOptions())
                        .setPath(req.getYPath().withObjectRoot(transactionAndLockResult.getValue().nodeId))
                        .build() :
                req;
    }

    /* */
    private  CompletableFuture handleHeavyResponse(CompletableFuture future,
                                                                   Function fn) {
        return RpcUtil.applyAsync(future, fn, heavyExecutor);
    }

    protected 
    CompletableFuture>
    invoke(RpcClientRequestBuilder builder) {
        return builder.invoke(rpcClient);
    }

    protected 
    CompletableFuture
    startStream(RpcClientRequestBuilder builder, RpcStreamConsumer consumer) {
        RpcClientStreamControl control = rpcClient.startStream(
                rpcClient,
                builder.getRpcRequest(),
                consumer,
                builder.getOptions()
        );

        return CompletableFuture.completedFuture(control);
    }

    private >
    CompletableFuture>
    sendRequest(RequestType req, RpcClientRequestBuilder builder) {
        /*
         * If several mutating requests was done with the same RequestType instance,
         * it must have different mutation ids.
         * So we reset mutationId for every request.
         */
        if (req instanceof TableReq) {
            req = (RequestType) ((TableReq) req)
                    .toBuilder()
                    .setMutatingOptions(new MutatingOptions().setMutationId(GUID.create()))
                    .build();
        } else if (req instanceof MutateNode) {
            req = (RequestType) ((MutateNode) req)
                    .toBuilder()
                    .setMutatingOptions(new MutatingOptions().setMutationId(GUID.create()))
                    .build();
        } else if (req instanceof MutateNode.Builder) {
            ((MutateNode.Builder) req).setMutatingOptions(new MutatingOptions().setMutationId(GUID.create()));
        }

        if (req instanceof RequestBase) {
            req = (RequestType) ((RequestBase) req)
                    .toBuilder()
                    .setUserAgent(config.getVersion())
                    .build();
        }

        logger.debug("Starting request {}; {}; User-Agent: {}",
                builder,
                req.getArgumentsLogString(),
                config.getVersion());
        req.writeHeaderTo(builder.header());
        req.writeTo(builder);
        return invoke(builder);
    }

    @Override
    public String toString() {
        return rpcClient != null ? rpcClient.toString() : super.toString();
    }

    @Nullable
    String getRpcProxyAddress() {
        if (rpcClient == null) {
            return null;
        }
        return rpcClient.getAddressString();
    }
}

@NonNullApi
@NonNullFields
class ModifyRowsWrapper implements HighLevelRequest {
    private final GUID transactionId;
    private final AbstractModifyRowsRequest request;

    ModifyRowsWrapper(GUID transactionId, AbstractModifyRowsRequest request) {
        this.transactionId = transactionId;
        this.request = request;
    }

    @Override
    public String getArgumentsLogString() {
        return "TransactionId: " + transactionId + "; ";
    }

    @Override
    public void writeHeaderTo(TRequestHeader.Builder header) {
        request.writeHeaderTo(header);
    }

    @Override
    public void writeTo(RpcClientRequestBuilder builder) {
        builder.body().setTransactionId(RpcUtil.toProto(transactionId));
        builder.body().setPath(request.getPath());
        if (request.getRequireSyncReplica().isPresent()) {
            builder.body().setRequireSyncReplica(request.getRequireSyncReplica().get());
        }
        builder.body().addAllRowModificationTypes(request.getRowModificationTypes());
        builder.body().setRowsetDescriptor(ApiServiceUtil.makeRowsetDescriptor(request.getSchema()));
        request.serializeRowsetTo(builder);
    }
}

@NonNullApi
@NonNullFields
class GetInSyncReplicasWrapper implements HighLevelRequest {
    private final YtTimestamp timestamp;
    private final GetInSyncReplicas request;

    GetInSyncReplicasWrapper(YtTimestamp timestamp, GetInSyncReplicas request) {
        this.timestamp = timestamp;
        this.request = request;
    }

    @Override
    public String getArgumentsLogString() {
        return "Path: " + request.getPath() +
                "; Timestamp: " + timestamp + "; ";
    }

    @Override
    public void writeHeaderTo(TRequestHeader.Builder header) {
        request.writeHeaderTo(header);
    }

    /**
     * Internal method: prepare request to send over network.
     */
    @Override
    public void writeTo(RpcClientRequestBuilder builder) {
        builder.body().setPath(request.getPath());
        builder.body().setTimestamp(timestamp.getValue());
        builder.body().setRowsetDescriptor(ApiServiceUtil.makeRowsetDescriptor(request.getSchema()));

        request.serializeRowsetTo(builder.attachments());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy