aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVasily Gerasimov <UgnineSirdis@ydb.tech>2025-05-27 17:46:05 +0300
committerGitHub <noreply@github.com>2025-05-27 14:46:05 +0000
commit8fa236de315950d6bf971e9f32618a5cda4b58e8 (patch)
tree8192e7abd0eb2b6b42a4cfa9968f60fb47eeac57
parent5136f56cdaa29001a68823d190648f5f91cea1a2 (diff)
downloadydb-8fa236de315950d6bf971e9f32618a5cda4b58e8.tar.gz
Disable write to unique index under construction (#18668)
-rw-r--r--ydb/core/kqp/gateway/kqp_metadata_loader.cpp21
-rw-r--r--ydb/core/kqp/opt/kqp_opt_kql.cpp24
-rw-r--r--ydb/core/kqp/provider/yql_kikimr_exec.cpp2
-rw-r--r--ydb/core/kqp/provider/yql_kikimr_gateway.h4
-rw-r--r--ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp303
-rw-r--r--ydb/core/protos/feature_flags.proto1
-rw-r--r--ydb/core/tx/schemeshard/schemeshard_build_index__create.cpp10
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