diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_move_sequence.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_move_sequence.cpp new file mode 100644 index 000000000000..a54e1c4a4f65 --- /dev/null +++ b/ydb/core/tx/schemeshard/schemeshard__operation_move_sequence.cpp @@ -0,0 +1,849 @@ +#include "schemeshard__operation_part.h" +#include "schemeshard__operation_common.h" +#include "schemeshard_impl.h" + +#include +#include +#include + +namespace { + +using namespace NKikimr; +using namespace NSchemeShard; + +void MarkSrcDropped(NIceDb::TNiceDb& db, + TOperationContext& context, + TOperationId operationId, + const TTxState& txState, + TPath& srcPath) +{ + Y_ABORT_UNLESS(!srcPath->Dropped()); + Y_ABORT_UNLESS(txState.PlanStep); + srcPath->SetDropped(txState.PlanStep, operationId.GetTxId()); + context.SS->PersistDropStep(db, srcPath->PathId, txState.PlanStep, operationId); + + auto path = context.SS->PathsById.at(srcPath->PathId); + auto parentDir = context.SS->PathsById.at(srcPath->ParentPathId); + + context.SS->TabletCounters->Simple()[COUNTER_USER_ATTRIBUTES_COUNT].Add(-srcPath->UserAttrs->Size()); + context.SS->PersistUserAttributes(db, srcPath->PathId, srcPath->UserAttrs, nullptr); + + ++parentDir->DirAlterVersion; + + context.SS->PersistPathDirAlterVersion(db, parentDir); + context.SS->ClearDescribePathCaches(parentDir); + context.SS->ClearDescribePathCaches(path); + + parentDir->DecAliveChildren(); + srcPath.DomainInfo()->DecPathsInside(); + + if (!context.SS->DisablePublicationsOfDropping) { + context.OnComplete.PublishToSchemeBoard(operationId, parentDir->PathId); + context.OnComplete.PublishToSchemeBoard(operationId, path->PathId); + } +} + +class TConfigureParts : public TSubOperationState { +private: + TOperationId OperationId; + + TString DebugHint() const override { + return TStringBuilder() + << "TMoveSequence TConfigureParts" + << " operationId#" << OperationId; + } + +public: + TConfigureParts(TOperationId id) + : OperationId(id) + { + IgnoreMessages(DebugHint(), { + TEvHive::TEvCreateTabletReply::EventType, + }); + } + + bool HandleReply(NSequenceShard::TEvSequenceShard::TEvCreateSequenceResult::TPtr& ev, TOperationContext& context) override { + auto ssId = context.SS->SelfTabletId(); + auto tabletId = TTabletId(ev->Get()->Record.GetOrigin()); + auto status = ev->Get()->Record.GetStatus(); + + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TConfigureParts HandleReply TEvCreateSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + + switch (status) { + case NKikimrTxSequenceShard::TEvCreateSequenceResult::SUCCESS: + case NKikimrTxSequenceShard::TEvCreateSequenceResult::SEQUENCE_ALREADY_EXISTS: + // Treat expected status as success + break; + + default: + // Treat all other replies as unexpected and spurious + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TConfigureParts HandleReply ignoring unexpected TEvCreateSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + Y_ABORT_UNLESS(txState->State == TTxState::ConfigureParts); + + auto shardIdx = context.SS->MustGetShardIdx(tabletId); + if (!txState->ShardsInProgress.erase(shardIdx)) { + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TCreateSequence TConfigureParts HandleReply ignoring duplicate TEvCreateSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + + context.OnComplete.UnbindMsgFromPipe(OperationId, tabletId, txState->TargetPathId); + + if (txState->ShardsInProgress.empty()) { + NIceDb::TNiceDb db(context.GetDB()); + context.SS->ChangeTxState(db, OperationId, TTxState::Propose); + context.OnComplete.ActivateTx(OperationId); + return true; + } + + return false; + } + + bool ProgressState(TOperationContext& context) override { + auto ssId = context.SS->SelfTabletId(); + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TConfigureParts ProgressState" + << " operationId# " << OperationId + << " at tablet " << ssId); + + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + Y_ABORT_UNLESS(!txState->Shards.empty()); + + txState->ClearShardsInProgress(); + + Y_ABORT_UNLESS(txState->Shards.size() == 1); + for (auto shard : txState->Shards) { + auto shardIdx = shard.Idx; + auto tabletId = context.SS->ShardInfos.at(shardIdx).TabletID; + Y_ABORT_UNLESS(shard.TabletType == ETabletType::SequenceShard); + Y_ABORT_UNLESS(tabletId != InvalidTabletId); + + auto event = MakeHolder(txState->TargetPathId); + event->Record.SetTxId(ui64(OperationId.GetTxId())); + event->Record.SetTxPartId(OperationId.GetSubTxId()); + event->Record.SetFrozen(true); + + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TCoptSequence TConfigureParts ProgressState" + << " sending TEvCreateSequence to tablet " << tabletId + << " operationId# " << OperationId + << " at tablet " << ssId); + + context.OnComplete.BindMsgToPipe(OperationId, tabletId, txState->TargetPathId, event.Release()); + + // Wait for results from this shard + txState->ShardsInProgress.insert(shardIdx); + } + + return false; + } +}; + +class TPropose: public TSubOperationState { +private: + TOperationId OperationId; + + TString DebugHint() const override { + return TStringBuilder() + << "TMoveSequence TPropose" + << " operationId#" << OperationId; + } + +public: + TPropose(TOperationId id) + : OperationId(id) + { + IgnoreMessages(DebugHint(), { + NSequenceShard::TEvSequenceShard::TEvCreateSequenceResult::EventType, + }); + } + + bool HandleReply(TEvPrivate::TEvOperationPlan::TPtr& ev, TOperationContext& context) override { + auto step = TStepId(ev->Get()->StepId); + auto ssId = context.SS->SelfTabletId(); + + LOG_INFO_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + DebugHint() << " HandleReply TEvOperationPlan" + << ", at schemeshard: " << ssId); + + TTxState* txState = context.SS->FindTx(OperationId); + if (!txState) { + return false; + } + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + + TPathId pathId = txState->TargetPathId; + TPathElement::TPtr path = context.SS->PathsById.at(pathId); + + Y_VERIFY_S(context.SS->Sequences.contains(pathId), "Sequence not found. PathId: " << pathId); + TSequenceInfo::TPtr sequenceInfo = context.SS->Sequences.at(pathId); + Y_ABORT_UNLESS(sequenceInfo); + TSequenceInfo::TPtr alterData = sequenceInfo->AlterData; + Y_ABORT_UNLESS(alterData); + + NIceDb::TNiceDb db(context.GetDB()); + + path->StepCreated = step; + context.SS->PersistCreateStep(db, pathId, step); + txState->PlanStep = step; + + context.SS->Sequences[pathId] = alterData; + context.SS->PersistSequenceAlterRemove(db, pathId); + context.SS->PersistSequence(db, pathId, *alterData); + + auto parentDir = context.SS->PathsById.at(path->ParentPathId); + if (parentDir->IsLikeDirectory()) { + ++parentDir->DirAlterVersion; + context.SS->PersistPathDirAlterVersion(db, parentDir); + } + context.SS->ClearDescribePathCaches(parentDir); + context.OnComplete.PublishToSchemeBoard(OperationId, parentDir->PathId); + + context.SS->ClearDescribePathCaches(path); + context.OnComplete.PublishToSchemeBoard(OperationId, pathId); + + context.SS->ChangeTxState(db, OperationId, TTxState::DeletePathBarrier); + return true; + } + + bool ProgressState(TOperationContext& context) override { + TTabletId ssId = context.SS->SelfTabletId(); + + LOG_INFO_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + DebugHint() << " ProgressState" + << ", at schemeshard: " << ssId); + + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + + context.OnComplete.ProposeToCoordinator(OperationId, txState->TargetPathId, TStepId(0)); + return false; + } +}; + + +class TDeleteTableBarrier: public TSubOperationState { +private: + TOperationId OperationId; + + TString DebugHint() const override { + return TStringBuilder() + << "TMoveTableIndex TDeleteTableBarrier" + << " operationId: " << OperationId; + } + +public: + TDeleteTableBarrier(TOperationId id) + : OperationId(id) + { + IgnoreMessages(DebugHint(), {TEvPrivate::TEvOperationPlan::EventType, + NSequenceShard::TEvSequenceShard::TEvCreateSequenceResult::EventType,}); + } + + bool HandleReply(TEvPrivate::TEvCompleteBarrier::TPtr& ev, TOperationContext& context) override { + TTabletId ssId = context.SS->SelfTabletId(); + + LOG_INFO_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + DebugHint() << " HandleReply TEvPrivate::TEvCompleteBarrier" + << ", msg: " << ev->Get()->ToString() + << ", at tablet" << ssId); + + NIceDb::TNiceDb db(context.GetDB()); + + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + + context.SS->ChangeTxState(db, OperationId, TTxState::ProposedMoveSequence); + return true; + } + bool ProgressState(TOperationContext& context) override { + TTabletId ssId = context.SS->SelfTabletId(); + context.OnComplete.RouteByTabletsFromOperation(OperationId); + + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + + Y_ABORT_UNLESS(txState->PlanStep); + + LOG_INFO_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + DebugHint() << " ProgressState" + << ", operation type: " << TTxState::TypeName(txState->TxType) + << ", at tablet" << ssId); + + context.OnComplete.Barrier(OperationId, "RenamePathBarrier"); + + return false; + } +}; + +class TProposedMoveSequence : public TSubOperationState { +private: + TOperationId OperationId; + NKikimrTxSequenceShard::TEvGetSequenceResult GetSequenceResult; + TString DebugHint() const override { + return TStringBuilder() + << "TMoveSequence TProposedMoveSequence" + << " operationId#" << OperationId; + } +public: + TProposedMoveSequence(TOperationId id) + : OperationId(id) + { + IgnoreMessages(DebugHint(), { + TEvPrivate::TEvOperationPlan::EventType, + TEvPrivate::TEvCompleteBarrier::EventType, + NSequenceShard::TEvSequenceShard::TEvCreateSequenceResult::EventType, + }); + } + bool HandleReply(NSequenceShard::TEvSequenceShard::TEvDropSequenceResult::TPtr& ev, TOperationContext& context) override { + auto ssId = context.SS->SelfTabletId(); + auto tabletId = TTabletId(ev->Get()->Record.GetOrigin()); + auto status = ev->Get()->Record.GetStatus(); + + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply TEvDropSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + + switch (status) { + case NKikimrTxSequenceShard::TEvDropSequenceResult::SUCCESS: + case NKikimrTxSequenceShard::TEvDropSequenceResult::SEQUENCE_NOT_FOUND: + // Treat expected status as success + break; + + default: + // Treat all other replies as unexpected and spurious + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply ignoring unexpected TEvDropSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + Y_ABORT_UNLESS(txState->State == TTxState::ProposedMoveSequence); + + auto shardIdx = context.SS->MustGetShardIdx(tabletId); + if (!txState->ShardsInProgress.erase(shardIdx)) { + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply ignoring duplicate TEvDropSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + + context.OnComplete.UnbindMsgFromPipe(OperationId, tabletId, txState->TargetPathId); + + if (txState->ShardsInProgress.empty()) { + NIceDb::TNiceDb db(context.GetDB()); + TPath srcPath = TPath::Init(txState->SourcePathId, context.SS); + + MarkSrcDropped(db, context, OperationId, *txState, srcPath); + + context.SS->ChangeTxState(db, OperationId, TTxState::Done); + context.OnComplete.ActivateTx(OperationId); + return true; + } + + return false; + } + bool HandleReply(NSequenceShard::TEvSequenceShard::TEvRestoreSequenceResult::TPtr& ev, TOperationContext& context) override { + auto ssId = context.SS->SelfTabletId(); + auto tabletId = TTabletId(ev->Get()->Record.GetOrigin()); + auto status = ev->Get()->Record.GetStatus(); + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply TEvRestoreSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + switch (status) { + case NKikimrTxSequenceShard::TEvRestoreSequenceResult::SUCCESS: + case NKikimrTxSequenceShard::TEvRestoreSequenceResult::SEQUENCE_ALREADY_ACTIVE: break; + default: + // Treat all other replies as unexpected and spurious + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply ignoring unexpected TEvRestoreSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + TTxState* txState = context.SS->FindTx(OperationId); + context.OnComplete.UnbindMsgFromPipe(OperationId, tabletId, txState->TargetPathId); + Y_ABORT_UNLESS(txState); + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + Y_ABORT_UNLESS(txState->State == TTxState::ProposedMoveSequence); + auto shardIdx = context.SS->MustGetShardIdx(tabletId); + if (!txState->ShardsInProgress.erase(shardIdx)) { + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply ignoring duplicate TEvRestoreSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + if (!txState->ShardsInProgress.empty()) { + return false; + } + for (auto& shard : txState->Shards) { + auto shardIdx = shard.Idx; + auto tabletId = context.SS->ShardInfos[shard.Idx].TabletID; + + Y_ABORT_UNLESS(shard.TabletType == ETabletType::SequenceShard); + + auto event = MakeHolder(txState->SourcePathId); + event->Record.SetTxId(ui64(OperationId.GetTxId())); + event->Record.SetTxPartId(OperationId.GetSubTxId()); + + context.OnComplete.BindMsgToPipe(OperationId, tabletId, txState->SourcePathId, event.Release()); + + // Wait for results from this shard + txState->ShardsInProgress.insert(shardIdx); + + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + DebugHint() << " ProgressState" + << " Propose drop at sequence shard" + << " tabletId# " << tabletId + << " pathId# " << txState->SourcePathId); + } + return false; + } + bool HandleReply(NSequenceShard::TEvSequenceShard::TEvGetSequenceResult::TPtr& ev, TOperationContext& context) override { + auto ssId = context.SS->SelfTabletId(); + auto tabletId = TTabletId(ev->Get()->Record.GetOrigin()); + auto status = ev->Get()->Record.GetStatus(); + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply TEvGetSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + switch (status) { + case NKikimrTxSequenceShard::TEvGetSequenceResult::SUCCESS: break; + default: + // Treat all other replies as unexpected and spurious + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply ignoring unexpected TEvGetSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + Y_ABORT_UNLESS(txState->State == TTxState::ProposedMoveSequence); + auto shardIdx = context.SS->MustGetShardIdx(tabletId); + context.OnComplete.UnbindMsgFromPipe(OperationId, tabletId, txState->SourcePathId); + if (!txState->ShardsInProgress.erase(shardIdx)) { + LOG_WARN_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence HandleReply ignoring duplicate TEvGetSequenceResult" + << " shardId# " << tabletId + << " status# " << status + << " operationId# " << OperationId + << " at tablet " << ssId); + return false; + } + if (!txState->ShardsInProgress.empty()) { + return false; + } + auto getSequenceResult = ev->Get()->Record; + Y_ABORT_UNLESS(txState->Shards.size() == 1); + for (auto shard : txState->Shards) { + auto shardIdx = shard.Idx; + auto currentTabletId = context.SS->ShardInfos.at(shardIdx).TabletID; + Y_ABORT_UNLESS(currentTabletId != InvalidTabletId); + auto event = MakeHolder( + txState->TargetPathId, getSequenceResult); + event->Record.SetTxId(ui64(OperationId.GetTxId())); + event->Record.SetTxPartId(OperationId.GetSubTxId()); + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence ProgressState" + << " sending TEvRestoreSequence to tablet " << currentTabletId + << " operationId# " << OperationId + << " at tablet " << ssId); + context.OnComplete.BindMsgToPipe(OperationId, currentTabletId, txState->TargetPathId, event.Release()); + // Wait for results from this shard + txState->ShardsInProgress.insert(shardIdx); + } + return false; + } + bool ProgressState(TOperationContext& context) override { + auto ssId = context.SS->SelfTabletId(); + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence ProgressState" + << " operationId# " << OperationId + << " at tablet " << ssId); + TTxState* txState = context.SS->FindTx(OperationId); + Y_ABORT_UNLESS(txState); + Y_ABORT_UNLESS(txState->TxType == TTxState::TxMoveSequence); + Y_ABORT_UNLESS(!txState->Shards.empty()); + Y_ABORT_UNLESS(txState->SourcePathId != InvalidPathId); + Y_ABORT_UNLESS(txState->Shards.size() == 1); + for (auto shard : txState->Shards) { + auto shardIdx = shard.Idx; + auto tabletId = context.SS->ShardInfos.at(shardIdx).TabletID; + auto event = MakeHolder(txState->SourcePathId); + event->Record.SetTxId(ui64(OperationId.GetTxId())); + event->Record.SetTxPartId(OperationId.GetSubTxId()); + LOG_DEBUG_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence TProposedMoveSequence ProgressState" + << " sending TEvGetSequence to tablet " << tabletId + << " operationId# " << OperationId + << " at tablet " << ssId); + context.OnComplete.BindMsgToPipe(OperationId, tabletId, txState->SourcePathId, event.Release()); + txState->ShardsInProgress.insert(shardIdx); + } + return false; + } +}; + +class TMoveSequence: public TSubOperation { + + static TTxState::ETxState NextState() { + return TTxState::Propose; + } + + TTxState::ETxState NextState(TTxState::ETxState state) const override { + switch (state) { + case TTxState::Waiting: + case TTxState::CreateParts: + return TTxState::ConfigureParts; + case TTxState::ConfigureParts: + return TTxState::Propose; + case TTxState::Propose: + return TTxState::DeletePathBarrier; + case TTxState::DeletePathBarrier: + return TTxState::ProposedMoveSequence; + case TTxState::ProposedMoveSequence: + return TTxState::Done; + default: + return TTxState::Invalid; + } + } + + TSubOperationState::TPtr SelectStateFunc(TTxState::ETxState state) override { + using TPtr = TSubOperationState::TPtr; + + switch (state) { + case TTxState::Waiting: + case TTxState::CreateParts: + return TPtr(new TCreateParts(OperationId)); + case TTxState::ConfigureParts: + return TPtr(new TConfigureParts(OperationId)); + case TTxState::Propose: + return TPtr(new TPropose(OperationId)); + case TTxState::DeletePathBarrier: + return MakeHolder(OperationId); + case TTxState::ProposedMoveSequence: + return TPtr(new TProposedMoveSequence(OperationId)); + case TTxState::Done: + return TPtr(new TDone(OperationId)); + default: + return nullptr; + } + } + +public: + using TSubOperation::TSubOperation; + + THolder Propose(const TString&, TOperationContext& context) override { + const TTabletId ssId = context.SS->SelfTabletId(); + + const auto acceptExisted = !Transaction.GetFailOnExist(); + const auto& moveSequence = Transaction.GetMoveSequence(); + + const TString& srcPathStr = moveSequence.GetSrcPath(); + const TString& dstPathStr = moveSequence.GetDstPath(); + + LOG_NOTICE_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveTableIndex Propose" + << ", from: "<< srcPathStr + << ", to: " << dstPathStr + << ", opId: " << OperationId + << ", at schemeshard: " << ssId); + + THolder result; + result.Reset(new TEvSchemeShard::TEvModifySchemeTransactionResult( + NKikimrScheme::StatusAccepted, ui64(OperationId.GetTxId()), ui64(ssId))); + + TString errStr; + + TPath srcPath = TPath::Resolve(srcPathStr, context.SS); + { + TPath::TChecker checks = srcPath.Check(); + checks + .NotEmpty() + .NotUnderDomainUpgrade() + .IsAtLocalSchemeShard() + .IsResolved() + .NotDeleted() + .NotUnderDeleting() + .IsSequence() + .NotUnderOperation(); + + if (!checks) { + result->SetError(checks.GetStatus(), checks.GetError()); + return result; + } + } + + { + TPath srcParentPath = srcPath.Parent(); + + TPath::TChecker checks = srcParentPath.Check(); + checks + .NotUnderDomainUpgrade() + .IsAtLocalSchemeShard() + .IsResolved() + .NotDeleted() + .NotUnderDeleting() + .IsCommonSensePath() + .NotAsyncReplicaTable(); + + if (checks) { + if (srcParentPath->IsTable()) { + // allow immediately inside a normal table + if (srcParentPath.IsUnderOperation()) { + checks.IsUnderTheSameOperation(OperationId.GetTxId()); // allowed only as part of consistent operations + } + } else { + // otherwise don't allow unexpected object types + checks.IsLikeDirectory(); + } + } + + if (!checks) { + result->SetError(checks.GetStatus(), checks.GetError()); + return result; + } + } + + TPath dstPath = TPath::Resolve(dstPathStr, context.SS); + TPath dstParentPath = dstPath.Parent(); + + { + TPath::TChecker checks = dstParentPath.Check(); + checks + .NotUnderDomainUpgrade() + .IsAtLocalSchemeShard() + .IsResolved(); + + if (dstParentPath.IsUnderOperation()) { + checks + .IsUnderTheSameOperation(OperationId.GetTxId()); + } else { + checks + .NotUnderOperation(); + } + + if (!checks) { + result->SetError(checks.GetStatus(), checks.GetError()); + return result; + } + } + + auto domainPathId = dstParentPath.GetPathIdForDomain(); + auto domainInfo = dstParentPath.DomainInfo(); + + Y_ABORT_UNLESS(context.SS->Sequences.contains(srcPath.Base()->PathId)); + TSequenceInfo::TPtr srcSequence = context.SS->Sequences.at(srcPath.Base()->PathId); + Y_ABORT_UNLESS(!srcSequence->Sharding.GetSequenceShards().empty()); + + const auto& protoSequenceShard = *srcSequence->Sharding.GetSequenceShards().rbegin(); + TShardIdx sequenceShard = FromProto(protoSequenceShard); + + const TString acl = Transaction.GetModifyACL().GetDiffACL(); + + { + TPath::TChecker checks = dstPath.Check(); + checks.IsAtLocalSchemeShard(); + if (dstPath.IsResolved()) { + checks + .IsResolved(); + + if (dstPath.IsUnderDeleting()) { + checks + .IsUnderDeleting() + .IsUnderTheSameOperation(OperationId.GetTxId()); + } else if (dstPath.IsUnderMoving()) { + // it means that dstPath is free enough to be the move destination + checks + .IsUnderMoving() + .IsUnderTheSameOperation(OperationId.GetTxId()); + } else { + checks + .IsDeleted() + .NotUnderOperation() + .FailOnExist(TPathElement::EPathType::EPathTypeTable, acceptExisted); + } + } else { + checks + .NotEmpty() + .NotResolved(); + } + + if (checks) { + checks + .DepthLimit() + .IsValidLeafName() + .IsTheSameDomain(srcPath) + .DirChildrenLimit() + .IsValidACL(acl); + } + + if (!checks) { + result->SetError(checks.GetStatus(), checks.GetError()); + return result; + } + } + + if (!context.SS->CheckApplyIf(Transaction, errStr)) { + result->SetError(NKikimrScheme::StatusPreconditionFailed, errStr); + return result; + } + + dstPath.MaterializeLeaf(srcPath.Base()->Owner); + result->SetPathId(dstPath->PathId.LocalPathId); + + srcPath.Base()->PathState = TPathElement::EPathState::EPathStateMoving; + srcPath.Base()->LastTxId = OperationId.GetTxId(); + + TPathId pathId = dstPath->PathId; + dstPath->CreateTxId = OperationId.GetTxId(); + dstPath->LastTxId = OperationId.GetTxId(); + dstPath->PathState = TPathElement::EPathState::EPathStateCreate; + dstPath->PathType = TPathElement::EPathType::EPathTypeSequence; + + if (dstParentPath->HasActiveChanges()) { + TTxId parentTxId = dstParentPath->PlannedToCreate() ? dstParentPath->CreateTxId : dstParentPath->LastTxId; + context.OnComplete.Dependence(parentTxId, OperationId.GetTxId()); + } + + TTxState& txState = + context.SS->CreateTx(OperationId, TTxState::TxMoveSequence, pathId, srcPath.Base()->PathId); + txState.State = TTxState::Propose; + + TSequenceInfo::TPtr sequenceInfo = new TSequenceInfo(0); + sequenceInfo->AlterData = srcSequence->CreateNextVersion(); + + txState.Shards.emplace_back(sequenceShard, ETabletType::SequenceShard, TTxState::ConfigureParts); + auto& shardInfo = context.SS->ShardInfos.at(sequenceShard); + if (shardInfo.CurrentTxId != OperationId.GetTxId()) { + context.OnComplete.Dependence(shardInfo.CurrentTxId, OperationId.GetTxId()); + } + + { + auto* p = sequenceInfo->AlterData->Sharding.AddSequenceShards(); + p->SetOwnerId(sequenceShard.GetOwnerId()); + p->SetLocalId(ui64(sequenceShard.GetLocalId())); + } + + NIceDb::TNiceDb db(context.GetDB()); + + context.SS->ChangeTxState(db, OperationId, txState.State); + context.OnComplete.ActivateTx(OperationId); + + context.SS->PersistPath(db, dstPath->PathId); + if (!acl.empty()) { + dstPath->ApplyACL(acl); + context.SS->PersistACL(db, dstPath.Base()); + } + + context.SS->Sequences[pathId] = sequenceInfo; + context.SS->PersistSequence(db, pathId, *sequenceInfo); + context.SS->PersistSequenceAlter(db, pathId, *sequenceInfo->AlterData); + context.SS->IncrementPathDbRefCount(pathId); + + context.SS->PersistTxState(db, OperationId); + context.SS->PersistUpdateNextPathId(db); + + for (auto shard : txState.Shards) { + if (shard.Operation == TTxState::CreateParts) { + context.SS->PersistChannelsBinding(db, shard.Idx, context.SS->ShardInfos.at(shard.Idx).BindedChannels); + context.SS->PersistShardMapping(db, shard.Idx, InvalidTabletId, domainPathId, OperationId.GetTxId(), shard.TabletType); + } + } + + ++dstParentPath->DirAlterVersion; + context.SS->PersistPathDirAlterVersion(db, dstParentPath.Base()); + context.SS->ClearDescribePathCaches(dstParentPath.Base()); + context.OnComplete.PublishToSchemeBoard(OperationId, dstParentPath->PathId); + + context.SS->ClearDescribePathCaches(dstPath.Base()); + context.OnComplete.PublishToSchemeBoard(OperationId, dstPath->PathId); + + domainInfo->IncPathsInside(); + dstParentPath->IncAliveChildren(); + + SetState(NextState()); + return result; + } + + void AbortPropose(TOperationContext& context) override { + LOG_NOTICE_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence AbortPropose" + << ", opId: " << OperationId + << ", at schemeshard: " << context.SS->TabletID()); + } + + void AbortUnsafe(TTxId forceDropTxId, TOperationContext& context) override { + LOG_NOTICE_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TMoveSequence AbortUnsafe" + << ", opId: " << OperationId + << ", forceDropId: " << forceDropTxId + << ", at schemeshard: " << context.SS->TabletID()); + + context.OnComplete.DoneOperation(OperationId); + } +}; + +} + +namespace NKikimr::NSchemeShard { + +ISubOperation::TPtr CreateMoveSequence(TOperationId id, const TTxTransaction& tx) { + return MakeSubOperation(id, tx); +} + +ISubOperation::TPtr CreateMoveSequence(TOperationId id, TTxState::ETxState state) { + Y_ABORT_UNLESS(state != TTxState::Invalid); + return MakeSubOperation(id, state); +} + +}