diff options
author | Vasily Gerasimov <UgnineSirdis@ydb.tech> | 2025-05-27 17:46:05 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-27 14:46:05 +0000 |
commit | 8fa236de315950d6bf971e9f32618a5cda4b58e8 (patch) | |
tree | 8192e7abd0eb2b6b42a4cfa9968f60fb47eeac57 | |
parent | 5136f56cdaa29001a68823d190648f5f91cea1a2 (diff) | |
download | ydb-8fa236de315950d6bf971e9f32618a5cda4b58e8.tar.gz |
Disable write to unique index under construction (#18668)
-rw-r--r-- | ydb/core/kqp/gateway/kqp_metadata_loader.cpp | 21 | ||||
-rw-r--r-- | ydb/core/kqp/opt/kqp_opt_kql.cpp | 24 | ||||
-rw-r--r-- | ydb/core/kqp/provider/yql_kikimr_exec.cpp | 2 | ||||
-rw-r--r-- | ydb/core/kqp/provider/yql_kikimr_gateway.h | 4 | ||||
-rw-r--r-- | ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp | 303 | ||||
-rw-r--r-- | ydb/core/protos/feature_flags.proto | 1 | ||||
-rw-r--r-- | ydb/core/tx/schemeshard/schemeshard_build_index__create.cpp | 10 |
7 files changed, 351 insertions, 14 deletions
diff --git a/ydb/core/kqp/gateway/kqp_metadata_loader.cpp b/ydb/core/kqp/gateway/kqp_metadata_loader.cpp index 4bf6cec1029..8113a7e3ea0 100644 --- a/ydb/core/kqp/gateway/kqp_metadata_loader.cpp +++ b/ydb/core/kqp/gateway/kqp_metadata_loader.cpp @@ -122,6 +122,24 @@ void IndexProtoToMetadata(const TIndexProto& indexes, NYql::TKikimrTableMetadata } } +template<typename TIndexProto> +void CheckWritesAreDisabled(const TIndexProto& indexes, NYql::TKikimrTableMetadataPtr tableMeta) { + TStringBuilder disableReason; + for (const NKikimrSchemeOp::TIndexDescription& index : indexes) { + if (index.GetType() == NKikimrSchemeOp::EIndexType::EIndexTypeGlobalUnique && index.GetState() != NKikimrSchemeOp::EIndexState::EIndexStateReady) { + if (disableReason) { + disableReason << ", "; + } + disableReason << "Unique index " << index.GetName() << " is under construction"; + } + } + + if (disableReason) { + tableMeta->WritesToTableAreDisabled = true; + tableMeta->DisableWritesReason = disableReason; + } +} + TString GetTypeName(const NScheme::TTypeInfoMod& typeInfoMod) { return NScheme::TypeName(typeInfoMod.TypeInfo, typeInfoMod.TypeMod); } @@ -235,6 +253,9 @@ TTableMetadataResult GetTableMetadataResult(const NSchemeCache::TSchemeCacheNavi IndexProtoToMetadata(entry.Indexes, tableMeta); + // Check if we have unique indexes that are not built + CheckWritesAreDisabled(entry.Indexes, tableMeta); + return result; } diff --git a/ydb/core/kqp/opt/kqp_opt_kql.cpp b/ydb/core/kqp/opt/kqp_opt_kql.cpp index ba37ec84114..b9f274a2ec6 100644 --- a/ydb/core/kqp/opt/kqp_opt_kql.cpp +++ b/ydb/core/kqp/opt/kqp_opt_kql.cpp @@ -1069,7 +1069,13 @@ TMaybe<TKqlQueryList> BuildKqlQuery(TKiDataQueryBlocks dataQueryBlocks, const TK TVector<TExprBase> kqlEffects; TNodeOnNodeOwnedMap effectsMap; for (const auto& effect : block.Effects()) { + TString cluster; + TString table; + if (auto maybeWrite = effect.Maybe<TKiWriteTable>()) { + cluster = maybeWrite.Cast().DataSink().Cluster(); + table = maybeWrite.Cast().Table().Value(); + auto writeOp = HandleWriteTable(maybeWrite.Cast(), ctx, *kqpCtx, tablesData); if (!writeOp) { return {}; @@ -1079,6 +1085,9 @@ TMaybe<TKqlQueryList> BuildKqlQuery(TKiDataQueryBlocks dataQueryBlocks, const TK } if (auto maybeUpdate = effect.Maybe<TKiUpdateTable>()) { + cluster = maybeUpdate.Cast().DataSink().Cluster(); + table = maybeUpdate.Cast().Table().Value(); + auto updateOp = HandleUpdateTable(maybeUpdate.Cast(), ctx, *kqpCtx, tablesData, withSystemColumns); if (!updateOp) { return {}; @@ -1087,6 +1096,9 @@ TMaybe<TKqlQueryList> BuildKqlQuery(TKiDataQueryBlocks dataQueryBlocks, const TK } if (auto maybeDelete = effect.Maybe<TKiDeleteTable>()) { + cluster = maybeDelete.Cast().DataSink().Cluster(); + table = maybeDelete.Cast().Table().Value(); + auto deleteOp = HandleDeleteTable(maybeDelete.Cast(), ctx, *kqpCtx, tablesData, withSystemColumns); if (!deleteOp) { return {}; @@ -1094,6 +1106,18 @@ TMaybe<TKqlQueryList> BuildKqlQuery(TKiDataQueryBlocks dataQueryBlocks, const TK kqlEffects.push_back(TExprBase(deleteOp)); } + if (cluster && table) { + auto& tableData = GetTableData(tablesData, cluster, table); + if (tableData.Metadata->WritesToTableAreDisabled) { + NYql::TIssues issues; + issues.AddIssue(NYql::TIssue(ctx.GetPosition(effect.Pos()), + TStringBuilder() << "Table `" << tableData.Metadata->Name << "` modification is disabled: " + << tableData.Metadata->DisableWritesReason)); + ctx.IssueManager.AddIssues(ctx.GetPosition(effect.Pos()), issues); + return {}; + } + } + if (TExprNode::TPtr result = HandleExternalWrite(effect, ctx, typesCtx)) { kqlEffects.emplace_back(result); } diff --git a/ydb/core/kqp/provider/yql_kikimr_exec.cpp b/ydb/core/kqp/provider/yql_kikimr_exec.cpp index b83060db711..c5a1902a295 100644 --- a/ydb/core/kqp/provider/yql_kikimr_exec.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_exec.cpp @@ -1842,6 +1842,8 @@ public: const auto type = TString(columnTuple.Item(1).Cast<TCoAtom>().Value()); if (type == "syncGlobal") { add_index->mutable_global_index(); + } else if (type == "syncGlobalUnique") { + add_index->mutable_global_unique_index(); } else if (type == "asyncGlobal") { add_index->mutable_global_async_index(); } else if (type == "globalVectorKmeansTree") { diff --git a/ydb/core/kqp/provider/yql_kikimr_gateway.h b/ydb/core/kqp/provider/yql_kikimr_gateway.h index b3c5b7b619a..d8c6156984b 100644 --- a/ydb/core/kqp/provider/yql_kikimr_gateway.h +++ b/ydb/core/kqp/provider/yql_kikimr_gateway.h @@ -490,6 +490,10 @@ struct TKikimrTableMetadata : public TThrRefBase { TMaybe<NKikimrSysView::ESysViewType> SysViewType; bool IsIndexImplTable = false; + // If writes are disabled, query that writes to table must finish with error. + bool WritesToTableAreDisabled = false; + TString DisableWritesReason; + ui64 RecordsCount = 0; ui64 DataSize = 0; ui64 MemorySize = 0; diff --git a/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp b/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp index 39458f291ac..115616dbf30 100644 --- a/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp +++ b/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp @@ -8,13 +8,16 @@ #include <ydb/core/formats/arrow/arrow_helpers.h> #include <ydb/core/tx/tx_proxy/proxy.h> #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/draft/ydb_replication.h> +#include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/operation/operation.h> #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/proto/accessor.h> #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/scheme/scheme.h> #include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/topic/client.h> #include <ydb/core/testlib/cs_helper.h> #include <ydb/core/testlib/common_helper.h> +#include <ydb/library/yql/utils/actor_log/log.h> #include <yql/essentials/types/uuid/uuid.h> #include <yql/essentials/types/binary_json/write.h> +#include <ydb/core/tx/datashard/datashard.h> #include <library/cpp/threading/local_executor/local_executor.h> @@ -2956,7 +2959,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { // a user which does not have any implicit permissions auto userSession = kikimr.GetTableClient(NYdb::NTable::TClientSettings() - .AuthToken("user@builtin")).CreateSession().GetValueSync().GetSession(); + .AuthToken("user@builtin")).CreateSession().GetValueSync().GetSession(); constexpr int minPartitionsCount = 10; auto setPartitioningQuery = [&]() { @@ -2969,7 +2972,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { return userSession.ExecuteSchemeQuery(Sprintf(R"( ALTER TABLE `%s` SET READ_REPLICAS_SETTINGS "PER_AZ:%d"; )", implTablePath, replicasCount)).ExtractValueSync(); - }; + }; auto setForbiddenSettingsQuery = [&]() { return userSession.ExecuteSchemeQuery(Sprintf(R"( ALTER TABLE `%s` SET KEY_BLOOM_FILTER ENABLED; @@ -2998,7 +3001,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { result.GetIssues().ToString() ); } - + // grant necessary permission Grant(adminSession, "DESCRIBE SCHEMA", tablePath, "user@builtin"); Grant(adminSession, "ALTER SCHEMA", tablePath, "user@builtin"); @@ -3011,7 +3014,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { { auto result = setReplicasQuery(); UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); - } + } // check result { auto describe = userSession.DescribeTable(implTablePath).ExtractValueSync(); @@ -3019,13 +3022,13 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto tableDesc = describe.GetTableDescription(); UNIT_ASSERT_VALUES_EQUAL(tableDesc.GetPartitioningSettings().GetMinPartitionsCount(), minPartitionsCount); - + const auto readReplicasSettings = tableDesc.GetReadReplicasSettings(); UNIT_ASSERT(readReplicasSettings); UNIT_ASSERT(readReplicasSettings->GetMode() == NYdb::NTable::TReadReplicasSettings::EMode::PerAz); UNIT_ASSERT_VALUES_EQUAL(readReplicasSettings->GetReadReplicasCount(), replicasCount); } - + // try altering non-partitioning setting of indexImplTable as non-superuser { @@ -3048,7 +3051,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { // become superuser kikimr.GetTestServer().GetRuntime()->GetAppData().AdministrationAllowedSIDs.emplace_back("user@builtin"); - + // alter non-partitioning setting of indexImplTable as superuser { auto result = setForbiddenSettingsQuery(); @@ -3252,6 +3255,282 @@ Y_UNIT_TEST_SUITE(KqpScheme) { } } + class TKikimrRunnerWithPauseIndexBuild : public TKikimrRunner { + public: + static TKikimrSettings MakeSettings() { + NKikimrConfig::TFeatureFlags featureFlags; + featureFlags.SetEnableAddUniqueIndex(true); + + TKikimrSettings settings; + settings + .SetFeatureFlags(featureFlags) + .SetUseRealThreads(false); + + return settings; + } + + TKikimrRunnerWithPauseIndexBuild(bool yqlDetailedLogging = false) + : TKikimrRunner(MakeSettings()) + , BuildIndexRequestPromise(NThreading::NewPromise<void>()) + , BuildIndexRequest(BuildIndexRequestPromise.GetFuture()) + { + GetTestServer().GetRuntime()->SetLogPriority(NKikimrServices::BUILD_INDEX, NActors::NLog::PRI_TRACE); + if (yqlDetailedLogging) { + GetTestServer().GetRuntime()->SetLogPriority(NKikimrServices::KQP_YQL, NActors::NLog::PRI_TRACE); + GetTestServer().GetRuntime()->SetLogPriority(NKikimrServices::KQP_EXECUTER, NActors::NLog::PRI_TRACE); + NYql::NDq::SetYqlLogLevels(NActors::NLog::PRI_TRACE); + } + + GetTestServer().GetRuntime()->SetObserverFunc([this](TAutoPtr<IEventHandle>& event) -> NActors::TTestActorRuntimeBase::EEventAction { + if (!BuildIndexEventIsIntercepted && event->Type == static_cast<ui32>(NKikimr::TEvDataShard::EvBuildIndexCreateRequest)) { + Cerr << "NKikimr::TEvDataShard::TEvBuildIndexCreateRequest is intercepted\n"; + BuildIndexEventIsIntercepted = true; + BuildIndexRequestPromise.SetValue(); + BuildIndexRequestEvent.Swap(event); + return NActors::TTestActorRuntimeBase::EEventAction::DROP; + } + return NActors::TTestActorRuntimeBase::EEventAction::PROCESS; + }); + } + + void WaitBuildIndex() { + BuildIndexRequest.Wait(); + } + + void ContinueBuildIndex() { + GetTestServer().GetRuntime()->Send(BuildIndexRequestEvent.Release()); + } + + private: + NThreading::TPromise<void> BuildIndexRequestPromise; + NThreading::TFuture<void> BuildIndexRequest; + TAutoPtr<IEventHandle> BuildIndexRequestEvent; + bool BuildIndexEventIsIntercepted = false; + }; + + void BuildingUniqIndexDeniesTableModifications(bool sqlInterface) { + TKikimrRunnerWithPauseIndexBuild kikimr(false /* yqlDetailedLogging */); + kikimr.RunCall([&] + { + auto db = kikimr.GetTableClient(); + auto queryClient = kikimr.GetQueryClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + + NYdb::TOperation::TOperationId alterOpId; // grpc + NYdb::NQuery::TAsyncExecuteQueryResult alterTableResultFuture; // sql + + if (sqlInterface) { + TString createQuery = R"sql( + CREATE TABLE `/Root/TestTable` ( + Key Uint64, + Value String, + PRIMARY KEY (Key) + ); + )sql"; + auto result = queryClient.ExecuteQuery(createQuery, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + if (!result.IsSuccess()) { + ythrow yexception() << "Unexpected status create table: " << result.GetStatus() << ": " << result.GetIssues().ToString(); + } + + TString alterQuery = R"sql( + ALTER TABLE `/Root/TestTable` + ADD INDEX uniq_value_idx GLOBAL UNIQUE ON (`Value`); + )sql"; + alterTableResultFuture = queryClient.ExecuteQuery(alterQuery, NYdb::NQuery::TTxControl::NoTx()); + } else { + auto builder = TTableBuilder() + .AddNullableColumn("Key", EPrimitiveType::Uint64) + .AddNullableColumn("Value", EPrimitiveType::String) + .SetPrimaryKeyColumn("Key"); + + auto result = session.CreateTable("/Root/TestTable", builder.Build()).ExtractValueSync(); + if (!result.IsSuccess()) { + ythrow yexception() << "Unexpected status create table: " << result.GetStatus() << ": " << result.GetIssues().ToString(); + } + + TAlterTableSettings settings; + settings.AppendAddIndexes(TIndexDescription( + "uniq_value_idx", + EIndexType::GlobalUnique, + {"Value"} + )); + auto op = session.AlterTableLong("/Root/TestTable", settings).ExtractValueSync(); + alterOpId = op.Id(); + } + + kikimr.WaitBuildIndex(); + + TString upsertQuery = R"sql( + UPSERT INTO `/Root/TestTable` (Key, Value) VALUES (1, "1"); + )sql"; + + TString insertQuery = R"sql( + INSERT INTO `/Root/TestTable` (Key, Value) VALUES (2, "2"); + )sql"; + + TString updateQuery = R"sql( + UPDATE `/Root/TestTable` SET Value = "11" WHERE Key = 1; + )sql"; + + TString deleteQuery = R"sql( + DELETE FROM `/Root/TestTable` WHERE Key = 2; + )sql"; + + TString returningQuery = R"sql( + REPLACE INTO `/Root/TestTable` (Key, Value) VALUES (1, "1") RETURNING Key, Value; + )sql"; + + auto modificationQueries = { + upsertQuery, + insertQuery, + updateQuery, + deleteQuery, + returningQuery, + }; + + for (const TString& query : modificationQueries) { + Cerr << "Running query:\n" << query << Endl; + auto result = queryClient.ExecuteQuery(query, NYdb::NQuery::TTxControl::BeginTx().CommitTx()).ExtractValueSync(); + if (result.GetStatus() != EStatus::GENERIC_ERROR + || result.GetIssues().ToString().find("Table `/Root/TestTable` modification is disabled: Unique index uniq_value_idx is under construction") == TString::npos) + { + Cerr << "Execute query issues. Query: " << query << Endl; + Cerr << "Execute issues:\n" << result.GetIssues().ToString() << Endl; + ythrow yexception() << "Unexpected status of modification query: " << result.GetStatus() << ": " << result.GetIssues().ToString(); + } + } + + auto checkBulkUpsert = [&]{ + NYdb::TValueBuilder rows; + rows.BeginList(); + rows.AddListItem() + .BeginStruct() + .AddMember("Key").Uint64(42) + .AddMember("Value").String("42") + .EndStruct(); + rows.EndList(); + + auto bulkUpsertResult = db.BulkUpsert("/Root/TestTable", rows.Build()).GetValueSync(); + if (bulkUpsertResult.IsSuccess() + || bulkUpsertResult.GetIssues().ToString().find("Only async-indexed tables are supported by BulkUpsert") == TString::npos) + { + ythrow yexception() << "Unexpected status of bulk upsert: " << bulkUpsertResult.GetStatus() << ": " << bulkUpsertResult.GetIssues().ToString(); + } + }; + + checkBulkUpsert(); + + kikimr.ContinueBuildIndex(); + + // Wait for index to be built + if (sqlInterface) { + auto result = alterTableResultFuture.GetValueSync(); + if (!result.IsSuccess()) { + ythrow yexception() << "Unexpected status of index build: " << result.GetStatus() << ": " << result.GetIssues().ToString(); + } + } else { + bool ready = false; + TMaybe<NYdb::NTable::TBuildIndexOperation> buildOp; + while (!ready) { + Sleep(TDuration::MilliSeconds(100)); + NYdb::NOperation::TOperationClient opClient(kikimr.GetDriver()); + buildOp = opClient.Get<NYdb::NTable::TBuildIndexOperation>(alterOpId).GetValueSync(); + ready = buildOp->Ready(); + } + if (!buildOp->Status().IsSuccess()) { + ythrow yexception() << "Unexpected status of index build: " << buildOp->Status().GetStatus() << ": " << buildOp->Status().GetIssues().ToString(); + } + } + + for (const TString& query : modificationQueries) { + auto result = queryClient.ExecuteQuery(query, NYdb::NQuery::TTxControl::BeginTx().CommitTx()).ExtractValueSync(); + if (result.GetStatus() != EStatus::SUCCESS) { + Cerr << "Execute query issues. Query: " << query << Endl; + Cerr << "Execute issues:\n" << result.GetIssues().ToString() << Endl; + ythrow yexception() << "Unexpected status of upsert query: " << result.GetStatus() << ": " << result.GetIssues().ToString(); + } + } + + // Bulk upsert must be denied even after index is built + checkBulkUpsert(); + }); + } + + Y_UNIT_TEST(BuildingUniqIndexDeniesTableModificationsPublicApi) { + BuildingUniqIndexDeniesTableModifications(false); + } + + Y_UNIT_TEST(BuildingUniqIndexDeniesTableModificationsSql) { + BuildingUniqIndexDeniesTableModifications(true); + } + + Y_UNIT_TEST(AlterTableAddUniqIndexSqlFeatureOff) { + NKikimrConfig::TFeatureFlags featureFlags; + featureFlags.SetEnableAddUniqueIndex(false); + auto settings = TKikimrSettings().SetFeatureFlags(featureFlags); + TKikimrRunner kikimr(settings); + kikimr.GetTestServer().GetRuntime()->SetLogPriority(NKikimrServices::KQP_YQL, NActors::NLog::PRI_TRACE); + kikimr.GetTestServer().GetRuntime()->SetLogPriority(NKikimrServices::KQP_EXECUTER, NActors::NLog::PRI_TRACE); + NYql::NDq::SetYqlLogLevels(NActors::NLog::PRI_TRACE); + + auto db = kikimr.GetQueryClient(); + + { + TString createQuery = R"sql( + CREATE TABLE `/Root/TestTable` ( + Key Uint64, + Value String, + PRIMARY KEY (Key) + ); + )sql"; + auto result = db.ExecuteQuery(createQuery, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + + { + TString alterQuery = R"sql( + ALTER TABLE `/Root/TestTable` + ADD INDEX uniq_value_idx GLOBAL UNIQUE ON (`Value`); + )sql"; + auto result = db.ExecuteQuery(alterQuery, NYdb::NQuery::TTxControl::NoTx()).ExtractValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::BAD_REQUEST, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "unsupported index type to build"); + } + } + + Y_UNIT_TEST(AlterTableAddUniqIndexPublicApiFeatureOff) { + NKikimrConfig::TFeatureFlags featureFlags; + featureFlags.SetEnableAddUniqueIndex(false); + auto settings = TKikimrSettings().SetFeatureFlags(featureFlags); + TKikimrRunner kikimr(settings); + + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + { + auto builder = TTableBuilder() + .AddNullableColumn("Key", EPrimitiveType::Uint64) + .AddNullableColumn("Value", EPrimitiveType::String) + .SetPrimaryKeyColumn("Key"); + + auto result = session.CreateTable("/Root/TestTable", builder.Build()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + + { + TAlterTableSettings settings; + settings.AppendAddIndexes(TIndexDescription( + "uniq_value_idx", + EIndexType::GlobalUnique, + {"Value"} + )); + auto op = session.AlterTableLong("/Root/TestTable", settings).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(op.Status().GetStatus(), EStatus::BAD_REQUEST, op.Status().GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(op.Status().GetIssues().ToString(), "unsupported index type to build"); + } + } + Y_UNIT_TEST(CreateTableWithVectorIndex) { NKikimrConfig::TFeatureFlags featureFlags; featureFlags.SetEnableVectorIndex(true); @@ -3491,7 +3770,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto describePostingTable = session.DescribeTable("/Root/TestTable/RenamedIndex/indexImplPostingTable").GetValueSync(); UNIT_ASSERT_C(describePostingTable.IsSuccess(), describePostingTable.GetIssues().ToString()); } - } + } Y_UNIT_TEST(RenameTableWithVectorIndex) { NKikimrConfig::TFeatureFlags featureFlags; @@ -3524,7 +3803,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto describePostingTable = session.DescribeTable("/Root/TestTableRenamed/vector_idx/indexImplPostingTable").GetValueSync(); UNIT_ASSERT_C(describePostingTable.IsSuccess(), describePostingTable.GetIssues().ToString()); } - } + } Y_UNIT_TEST(AlterTableWithDecimalColumn) { TKikimrRunner kikimr; @@ -4027,7 +4306,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { } } } - + void CheckOwner(TSession& session, const TString& path, const TString& name) { TDescribeTableResult describe = session.DescribeTable(path).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL(describe.GetStatus(), EStatus::SUCCESS); @@ -9883,7 +10162,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { CONCURRENT_QUERY_LIMIT=)" << NResourcePool::POOL_MAX_CONCURRENT_QUERY_LIMIT + 1 << R"( );)").GetValueSync(); UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::SCHEME_ERROR); - UNIT_ASSERT_STRING_CONTAINS_C(result.GetIssues().ToString(), + UNIT_ASSERT_STRING_CONTAINS_C(result.GetIssues().ToString(), TStringBuilder() << "Invalid resource pool configuration, concurrent_query_limit is " << NResourcePool::POOL_MAX_CONCURRENT_QUERY_LIMIT + 1 << ", that exceeds limit in " << NResourcePool::POOL_MAX_CONCURRENT_QUERY_LIMIT, result.GetIssues().ToString() ); @@ -10237,7 +10516,7 @@ Y_UNIT_TEST_SUITE(KqpScheme) { MEMBER_NAME=")" << BUILTIN_ACL_METADATA << R"(" );)").GetValueSync(); UNIT_ASSERT_VALUES_EQUAL(result.GetStatus(), EStatus::GENERIC_ERROR); - UNIT_ASSERT_STRING_CONTAINS_C(result.GetIssues().ToString(), + UNIT_ASSERT_STRING_CONTAINS_C(result.GetIssues().ToString(), TStringBuilder() << "Invalid resource pool classifier configuration, cannot create classifier for system user " << BUILTIN_ACL_METADATA, result.GetIssues().ToString() ); diff --git a/ydb/core/protos/feature_flags.proto b/ydb/core/protos/feature_flags.proto index 9f07d571df5..29a41bc5c60 100644 --- a/ydb/core/protos/feature_flags.proto +++ b/ydb/core/protos/feature_flags.proto @@ -210,4 +210,5 @@ message TFeatureFlags { optional bool EnableThrottlingReport = 184 [default = true]; optional bool EnableNodeBrokerDeltaProtocol = 185 [default = false]; optional bool EnableAccessToIndexImplTables = 186 [default = false]; + optional bool EnableAddUniqueIndex = 187 [default = false]; } diff --git a/ydb/core/tx/schemeshard/schemeshard_build_index__create.cpp b/ydb/core/tx/schemeshard/schemeshard_build_index__create.cpp index 66a5d237efc..85a7d215678 100644 --- a/ydb/core/tx/schemeshard/schemeshard_build_index__create.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_build_index__create.cpp @@ -227,8 +227,14 @@ private: buildInfo.IndexType = NKikimrSchemeOp::EIndexType::EIndexTypeGlobalAsync; break; case Ydb::Table::TableIndex::TypeCase::kGlobalUniqueIndex: - explain = "unsupported index type to build"; - return false; + if (AppData()->FeatureFlags.GetEnableAddUniqueIndex()) { + buildInfo.BuildKind = TIndexBuildInfo::EBuildKind::BuildSecondaryIndex; + buildInfo.IndexType = NKikimrSchemeOp::EIndexType::EIndexTypeGlobalUnique; + break; + } else { + explain = "unsupported index type to build"; + return false; + } case Ydb::Table::TableIndex::TypeCase::kGlobalVectorKmeansTreeIndex: { buildInfo.BuildKind = index.index_columns().size() == 1 ? TIndexBuildInfo::EBuildKind::BuildVectorIndex |