diff --git a/ydb/core/kqp/provider/yql_kikimr_datasource.cpp b/ydb/core/kqp/provider/yql_kikimr_datasource.cpp index 4e45f0a21853..af95adf40d5a 100644 --- a/ydb/core/kqp/provider/yql_kikimr_datasource.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_datasource.cpp @@ -351,6 +351,18 @@ class TKiSourceLoadTableMetadataTransformer : public TGraphTransformerBase { if (!AddCluster(table, res, input, ctx)) { return TStatus::Error; } + + if (const auto& preparingQuery = SessionCtx->Query().PreparingQuery; + preparingQuery + && res.Metadata->Kind == EKikimrTableKind::View + ) { + const auto& viewMetadata = *res.Metadata; + auto* viewInfo = preparingQuery->MutablePhysicalQuery()->MutableViewInfos()->Add(); + auto* pathId = viewInfo->MutableTableId(); + pathId->SetOwnerId(viewMetadata.PathId.OwnerId()); + pathId->SetTableId(viewMetadata.PathId.TableId()); + viewInfo->SetSchemaVersion(viewMetadata.SchemaVersion); + } } else { TIssueScopeGuard issueScope(ctx.IssueManager, [input, &table, &ctx]() { return MakeIntrusive(TIssue(ctx.GetPosition(input->Pos()), TStringBuilder() diff --git a/ydb/core/kqp/session_actor/kqp_query_state.cpp b/ydb/core/kqp/session_actor/kqp_query_state.cpp index 7ea7dfa77bfe..eebe3c33a468 100644 --- a/ydb/core/kqp/session_actor/kqp_query_state.cpp +++ b/ydb/core/kqp/session_actor/kqp_query_state.cpp @@ -71,6 +71,16 @@ bool TKqpQueryState::EnsureTableVersions(const TEvTxProxySchemeCache::TEvNavigat return true; } +void TKqpQueryState::FillViews(const google::protobuf::RepeatedPtrField< ::NKqpProto::TKqpTableInfo>& views) { + for (const auto& view : views) { + const auto& pathId = view.GetTableId(); + const auto schemaVersion = view.GetSchemaVersion(); + auto [it, isInserted] = TableVersions.emplace(TTableId(pathId.GetOwnerId(), pathId.GetTableId()), schemaVersion); + if (!isInserted) { + Y_ENSURE(it->second == schemaVersion); + } + } +} std::unique_ptr TKqpQueryState::BuildNavigateKeySet() { TableVersions.clear(); @@ -78,6 +88,7 @@ std::unique_ptr TKqpQueryState::BuildN for (const auto& tx : PreparedQuery->GetPhysicalQuery().GetTransactions()) { FillTables(tx); } + FillViews(PreparedQuery->GetPhysicalQuery().GetViewInfos()); auto navigate = MakeHolder(); navigate->DatabaseName = Database; diff --git a/ydb/core/kqp/session_actor/kqp_query_state.h b/ydb/core/kqp/session_actor/kqp_query_state.h index 7766ddd70116..48f32a7342a2 100644 --- a/ydb/core/kqp/session_actor/kqp_query_state.h +++ b/ydb/core/kqp/session_actor/kqp_query_state.h @@ -234,6 +234,8 @@ class TKqpQueryState : public TNonCopyable { } } + void FillViews(const google::protobuf::RepeatedPtrField< ::NKqpProto::TKqpTableInfo>& views); + bool NeedCheckTableVersions() const { return CompileStats.FromCache; } diff --git a/ydb/core/kqp/ut/view/view_ut.cpp b/ydb/core/kqp/ut/view/view_ut.cpp index fed6b336e75b..cfd6adf5c654 100644 --- a/ydb/core/kqp/ut/view/view_ut.cpp +++ b/ydb/core/kqp/ut/view/view_ut.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -8,6 +9,7 @@ using namespace NKikimr; using namespace NKikimr::NKqp; +using namespace NYdb; using namespace NYdb::NTable; namespace { @@ -60,10 +62,14 @@ void ExecuteDataDefinitionQuery(TSession& session, const TString& script) { << script << "\nThe issues:\n" << result.GetIssues().ToString()); } -TDataQueryResult ExecuteDataModificationQuery(TSession& session, const TString& script) { +TDataQueryResult ExecuteDataModificationQuery(TSession& session, + const TString& script, + const TExecDataQuerySettings& settings = {} +) { const auto result = session.ExecuteDataQuery( script, - TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx() + TTxControl::BeginTx(TTxSettings::SerializableRW()).CommitTx(), + settings ).ExtractValueSync(); UNIT_ASSERT_C(result.IsSuccess(), "Failed to execute the following DML script:\n" << script << "\nThe issues:\n" << result.GetIssues().ToString()); @@ -71,6 +77,31 @@ TDataQueryResult ExecuteDataModificationQuery(TSession& session, const TString& return result; } +TValue GetSingleResult(const TDataQueryResult& rawResults) { + auto resultSetParser = rawResults.GetResultSetParser(0); + UNIT_ASSERT(resultSetParser.TryNextRow()); + return resultSetParser.GetValue(0); +} + +int GetInteger(const TValue& value) { + return TValueParser(value).GetInt32(); +} + +TMaybe GetFromCacheStat(const TQueryStats& stats) { + const auto& proto = TProtoAccessor::GetProto(stats); + if (!proto.Hascompilation()) { + return Nothing(); + } + return proto.Getcompilation().Getfrom_cache(); +} + +void AssertFromCache(const TMaybe& stats, bool expectedValue) { + UNIT_ASSERT(stats.Defined()); + const auto isFromCache = GetFromCacheStat(*stats); + UNIT_ASSERT_C(isFromCache.Defined(), stats->ToString()); + UNIT_ASSERT_VALUES_EQUAL_C(*isFromCache, expectedValue, stats->ToString()); +} + void CompareResults(const TDataQueryResult& first, const TDataQueryResult& second) { const auto& firstResults = first.GetResultSets(); const auto& secondResults = second.GetResultSets(); @@ -376,4 +407,51 @@ Y_UNIT_TEST_SUITE(TSelectFromViewTest) { ExecuteDataDefinitionQuery(session, ReadWholeFile(pathPrefix + "drop_view.sql")); } } + + Y_UNIT_TEST(QueryCacheIsUpdated) { + TKikimrRunner kikimr(TKikimrSettings().SetWithSampleTables(false)); + EnableViewsFeatureFlag(kikimr); + auto session = kikimr.GetTableClient().CreateSession().GetValueSync().GetSession(); + + constexpr const char* viewName = "TheView"; + + const auto getCreationQuery = [&viewName](const char* innerQuery) -> TString { + return std::format(R"( + CREATE VIEW {} WITH (security_invoker = TRUE) AS {}; + )", + viewName, + innerQuery + ); + }; + constexpr const char* firstInnerQuery = "SELECT 1"; + ExecuteDataDefinitionQuery(session, getCreationQuery(firstInnerQuery)); + + const TString selectFromViewQuery = std::format(R"( + SELECT * FROM {}; + )", + viewName + ); + TExecDataQuerySettings queryExecutionSettings; + queryExecutionSettings.KeepInQueryCache(true); + queryExecutionSettings.CollectQueryStats(ECollectQueryStatsMode::Basic); + ExecuteDataModificationQuery(session, selectFromViewQuery, queryExecutionSettings); + // make sure the server side cache is working by calling the same query twice + const auto cachedQueryRawResult = ExecuteDataModificationQuery(session, selectFromViewQuery, queryExecutionSettings); + AssertFromCache(cachedQueryRawResult.GetStats(), true); + UNIT_ASSERT_VALUES_EQUAL(GetInteger(GetSingleResult(cachedQueryRawResult)), 1); + + // recreate the view with a different query inside + ExecuteDataDefinitionQuery(session, std::format(R"( + DROP VIEW {}; + )", + viewName + ) + ); + constexpr const char* secondInnerQuery = "SELECT 2"; + ExecuteDataDefinitionQuery(session, getCreationQuery(secondInnerQuery)); + + const auto secondCallRawResult = ExecuteDataModificationQuery(session, selectFromViewQuery, queryExecutionSettings); + AssertFromCache(secondCallRawResult.GetStats(), false); + UNIT_ASSERT_VALUES_EQUAL(GetInteger(GetSingleResult(secondCallRawResult)), 2); + } } diff --git a/ydb/core/protos/kqp_physical.proto b/ydb/core/protos/kqp_physical.proto index 9ee7b069ed04..98351bfe1f1e 100644 --- a/ydb/core/protos/kqp_physical.proto +++ b/ydb/core/protos/kqp_physical.proto @@ -507,4 +507,6 @@ message TKqpPhyQuery { bool HasUncommittedChangesRead = 9; string QueryDiagnostics = 10; + + repeated TKqpTableInfo ViewInfos = 11; }