summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-06-02 16:34:29 +0300
committerrobot-piglet <[email protected]>2025-06-02 16:48:28 +0300
commita94f4f389851dfc9ac0a7f8e7bb7a0d12ffadf82 (patch)
treea4041ded4fdc5746f5c1002ede7f3fe583131fa0
parent23b5653e9af4ef22f764a842524a69815ec0459c (diff)
Intermediate changes
commit_hash:6476b453be16b7db003e75789e3b630c1168f14f
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/byte_size.cpp1
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/byte_size.h46
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/cache.cpp1
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/cache.h42
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/cached.cpp1
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/cached.h37
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/cached_ut.cpp30
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/local/cache.cpp1
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/local/cache.h113
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/local/cache_ut.cpp216
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/local/ut/ya.make7
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/local/ya.make17
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/ut/ya.make11
-rw-r--r--yql/essentials/sql/v1/complete/name/cache/ya.make21
-rw-r--r--yql/essentials/sql/v1/complete/name/object/simple/cached/key.h48
-rw-r--r--yql/essentials/sql/v1/complete/name/object/simple/cached/schema.cpp49
-rw-r--r--yql/essentials/sql/v1/complete/name/object/simple/cached/schema.h16
-rw-r--r--yql/essentials/sql/v1/complete/name/object/simple/cached/ya.make12
-rw-r--r--yql/essentials/sql/v1/complete/name/ya.make1
-rw-r--r--yql/essentials/sql/v1/complete/sql_complete_ut.cpp37
-rw-r--r--yql/essentials/sql/v1/complete/ut/ya.make2
21 files changed, 709 insertions, 0 deletions
diff --git a/yql/essentials/sql/v1/complete/name/cache/byte_size.cpp b/yql/essentials/sql/v1/complete/name/cache/byte_size.cpp
new file mode 100644
index 00000000000..304a865aab1
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/byte_size.cpp
@@ -0,0 +1 @@
+#include "byte_size.h"
diff --git a/yql/essentials/sql/v1/complete/name/cache/byte_size.h b/yql/essentials/sql/v1/complete/name/cache/byte_size.h
new file mode 100644
index 00000000000..f838d364750
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/byte_size.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+
+#include <util/generic/vector.h>
+#include <util/generic/string.h>
+
+namespace NSQLComplete {
+
+ template <class T>
+ struct TByteSize;
+
+ template <class T>
+ requires std::is_fundamental_v<T>
+ struct TByteSize<T> {
+ size_t operator()(const T& x) const noexcept {
+ return sizeof(x);
+ }
+ };
+
+ template <class T>
+ struct TByteSize<TVector<T>> {
+ size_t operator()(const TVector<T>& x) const noexcept {
+ size_t bytes = sizeof(x);
+ bytes = Accumulate(x, bytes, [](size_t acc, const T& x) {
+ return acc + TByteSize<T>()(x);
+ });
+ bytes += x.capacity() * sizeof(T);
+ return bytes;
+ }
+ };
+
+ template <>
+ struct TByteSize<TString> {
+ size_t operator()(const TString& x) const noexcept {
+ return std::max(sizeof(x), sizeof(x) + x.capacity());
+ }
+ };
+
+ template <class T>
+ concept CByteSized = requires(const T& x) {
+ { TByteSize<T>()(x) } -> std::convertible_to<std::size_t>;
+ };
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/cache/cache.cpp b/yql/essentials/sql/v1/complete/name/cache/cache.cpp
new file mode 100644
index 00000000000..05b26b0cf80
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/cache.cpp
@@ -0,0 +1 @@
+#include "cache.h"
diff --git a/yql/essentials/sql/v1/complete/name/cache/cache.h b/yql/essentials/sql/v1/complete/name/cache/cache.h
new file mode 100644
index 00000000000..d65b41d5636
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/cache.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "byte_size.h"
+
+#include <library/cpp/threading/future/future.h>
+
+#include <util/generic/ptr.h>
+#include <util/datetime/base.h>
+
+namespace NSQLComplete {
+
+ namespace NPrivate {
+
+ template <class T>
+ concept CHashable = requires(const T& x) {
+ { THash<T>()(x) } -> std::convertible_to<std::size_t>;
+ };
+
+ template <class T>
+ concept CCacheKey = std::regular<T> && CHashable<T> && CByteSized<T>;
+
+ template <class T>
+ concept CCacheValue = std::copyable<T> && CByteSized<T>;
+
+ }; // namespace NPrivate
+
+ template <NPrivate::CCacheKey TKey, NPrivate::CCacheValue TValue>
+ class ICache: public TThrRefBase {
+ public:
+ using TPtr = TIntrusivePtr<ICache>;
+
+ struct TEntry {
+ TValue Value = {};
+ bool IsExpired = true;
+ };
+
+ virtual ~ICache() = default;
+ virtual NThreading::TFuture<TEntry> Get(const TKey& key) const = 0;
+ virtual NThreading::TFuture<void> Update(const TKey& key, TValue value) const = 0;
+ };
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/cache/cached.cpp b/yql/essentials/sql/v1/complete/name/cache/cached.cpp
new file mode 100644
index 00000000000..7a155ac0999
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/cached.cpp
@@ -0,0 +1 @@
+#include "cached.h"
diff --git a/yql/essentials/sql/v1/complete/name/cache/cached.h b/yql/essentials/sql/v1/complete/name/cache/cached.h
new file mode 100644
index 00000000000..b265f6fde13
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/cached.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "cache.h"
+
+namespace NSQLComplete {
+
+ template <NPrivate::CCacheKey TKey, NPrivate::CCacheValue TValue>
+ class TCachedQuery {
+ public:
+ using TFunc = std::function<NThreading::TFuture<TValue>(const TKey& key)>;
+
+ TCachedQuery(ICache<TKey, TValue>::TPtr cache, TFunc query)
+ : Cache_(std::move(cache))
+ , Query_(std::move(query))
+ {
+ }
+
+ NThreading::TFuture<TValue> operator()(TKey key) const {
+ return Cache_->Get(key).Apply([cache = Cache_,
+ query = Query_,
+ key = std::move(key)](auto f) {
+ typename ICache<TKey, TValue>::TEntry entry = f.ExtractValue();
+ if (entry.IsExpired) {
+ query(key).Apply([cache, key = std::move(key)](auto f) {
+ cache->Update(key, f.ExtractValue());
+ });
+ }
+ return std::move(entry.Value);
+ });
+ }
+
+ private:
+ ICache<TKey, TValue>::TPtr Cache_;
+ TFunc Query_;
+ };
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/cache/cached_ut.cpp b/yql/essentials/sql/v1/complete/name/cache/cached_ut.cpp
new file mode 100644
index 00000000000..18607dea438
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/cached_ut.cpp
@@ -0,0 +1,30 @@
+#include "cached.h"
+
+#include <yql/essentials/sql/v1/complete/name/cache/local/cache.h>
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <library/cpp/time_provider/monotonic_provider.h>
+
+using namespace NSQLComplete;
+
+Y_UNIT_TEST_SUITE(CachedQueryTests) {
+
+ Y_UNIT_TEST(OnExpired_WhenApplied_ThenDefferedUpdateAndReturnOld) {
+ size_t queried = 0;
+ auto cache = MakeLocalCache<TString, TString>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {.TTL = TDuration::Zero()});
+ auto cached = TCachedQuery<TString, TString>(cache, [&](const TString& key) {
+ queried += 1;
+ return NThreading::MakeFuture<TString>(key);
+ });
+ cache->Update("1", "2");
+
+ TString value = cached("1").GetValueSync();
+
+ UNIT_ASSERT_VALUES_EQUAL(value, "2");
+ UNIT_ASSERT_VALUES_EQUAL(queried, 1);
+ UNIT_ASSERT_VALUES_EQUAL(cached("1").GetValueSync(), "1");
+ }
+
+} // Y_UNIT_TEST_SUITE(CachedQueryTests)
diff --git a/yql/essentials/sql/v1/complete/name/cache/local/cache.cpp b/yql/essentials/sql/v1/complete/name/cache/local/cache.cpp
new file mode 100644
index 00000000000..05b26b0cf80
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/local/cache.cpp
@@ -0,0 +1 @@
+#include "cache.h"
diff --git a/yql/essentials/sql/v1/complete/name/cache/local/cache.h b/yql/essentials/sql/v1/complete/name/cache/local/cache.h
new file mode 100644
index 00000000000..9e4438f0022
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/local/cache.h
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <yql/essentials/sql/v1/complete/name/cache/cache.h>
+
+#include <library/cpp/cache/cache.h>
+#include <library/cpp/time_provider/monotonic_provider.h>
+
+#include <util/system/mutex.h>
+
+namespace NSQLComplete {
+
+ struct TLocalCacheConfig {
+ size_t ByteCapacity = 1 * 1024 * 1024;
+ TDuration TTL = TDuration::Seconds(8);
+ };
+
+ namespace NPrivate {
+
+ template <CCacheValue TValue>
+ struct TLocalCacheCell {
+ TValue Value;
+ NMonotonic::TMonotonic Deadline;
+ size_t KeyByteSize = 0;
+ size_t CellByteSize = 0;
+ };
+
+ template <CCacheKey TKey, CCacheValue TValue>
+ class TLocalCache: public ICache<TKey, TValue> {
+ private:
+ using TEntry = ICache<TKey, TValue>::TEntry;
+ using TCell = TLocalCacheCell<TValue>;
+
+ struct TLRUSizeProvider {
+ size_t operator()(const TCell& x) noexcept {
+ const size_t listItemContent = x.CellByteSize;
+ const size_t listItemPtrs = sizeof(TIntrusiveListItem<void>);
+ const size_t listItem = listItemContent + listItemPtrs;
+
+ const size_t cacheIndexKey = x.KeyByteSize;
+ const size_t cacheIndexListItemPtr = sizeof(void*);
+ const size_t cacheIndexEntry = cacheIndexKey + cacheIndexListItemPtr;
+
+ return listItem + cacheIndexEntry;
+ }
+ };
+
+ using TStorage = TLRUCache<TKey, TCell, TNoopDelete, TLRUSizeProvider>;
+
+ public:
+ TLocalCache(TIntrusivePtr<NMonotonic::IMonotonicTimeProvider> clock, TLocalCacheConfig config)
+ : Clock_(std::move(clock))
+ , Config_(std::move(config))
+ , Origin_(/* maxSize = */ Config_.ByteCapacity)
+ {
+ }
+
+ NThreading::TFuture<TEntry> Get(const TKey& key) const override {
+ TEntry entry;
+
+ with_lock (Mutex_) {
+ if (auto it = Origin_.Find(key); it != Origin_.End()) {
+ entry.Value = it->Value;
+ entry.IsExpired = (it->Deadline < Clock_->Now());
+ }
+ }
+
+ return NThreading::MakeFuture(std::move(entry));
+ }
+
+ NThreading::TFuture<void> Update(const TKey& key, TValue value) const override {
+ TCell cell = {
+ .Value = std::move(value),
+ .Deadline = Clock_->Now() + Config_.TTL,
+ .KeyByteSize = TByteSize<TKey>()(key),
+ };
+
+ cell.CellByteSize =
+ TByteSize<TValue>()(cell.Value) +
+ sizeof(cell.Deadline) +
+ cell.KeyByteSize;
+
+ with_lock (Mutex_) {
+ Origin_.Update(key, std::move(cell));
+ }
+
+ return NThreading::MakeFuture();
+ }
+
+ private:
+ TIntrusivePtr<NMonotonic::IMonotonicTimeProvider> Clock_;
+ TLocalCacheConfig Config_;
+
+ TMutex Mutex_;
+ mutable TStorage Origin_;
+ };
+
+ } // namespace NPrivate
+
+ template <NPrivate::CCacheKey TKey, NPrivate::CCacheValue TValue>
+ ICache<TKey, TValue>::TPtr MakeLocalCache(
+ TIntrusivePtr<NMonotonic::IMonotonicTimeProvider> clock,
+ TLocalCacheConfig config) {
+ return new NPrivate::TLocalCache<TKey, TValue>(std::move(clock), std::move(config));
+ }
+
+ template <NPrivate::CCacheValue TValue>
+ struct TByteSize<NPrivate::TLocalCacheCell<TValue>> {
+ size_t operator()(const NPrivate::TLocalCacheCell<TValue>& x) const noexcept {
+ return x.CellByteSize;
+ }
+ };
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/cache/local/cache_ut.cpp b/yql/essentials/sql/v1/complete/name/cache/local/cache_ut.cpp
new file mode 100644
index 00000000000..0823991e2d4
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/local/cache_ut.cpp
@@ -0,0 +1,216 @@
+#include "cache.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <library/cpp/time_provider/monotonic_provider.h>
+
+#include <util/random/random.h>
+#include <util/thread/pool.h>
+
+using namespace NSQLComplete;
+
+class TPausedClock: public NMonotonic::IMonotonicTimeProvider {
+public:
+ NMonotonic::TMonotonic Now() override {
+ return Now_;
+ }
+
+ void Skip(TDuration duration) {
+ Now_ += duration;
+ }
+
+private:
+ NMonotonic::TMonotonic Now_ = NMonotonic::CreateDefaultMonotonicTimeProvider()->Now();
+};
+
+struct TFat {
+ size_t Id = 0;
+
+ friend bool operator==(const TFat& lhs, const TFat& rhs) = default;
+};
+
+namespace NSQLComplete {
+
+ template <>
+ struct TByteSize<TFat> {
+ size_t operator()(const TFat&) const noexcept {
+ return 10'000;
+ }
+ };
+
+} // namespace NSQLComplete
+
+template <>
+struct THash<TFat> {
+ size_t operator()(const TFat& x) const noexcept {
+ return x.Id;
+ }
+};
+
+struct TAction {
+ bool IsGet = false;
+ TString Key = "";
+ TString Value = "";
+};
+
+TVector<TAction> GenerateRandomActions(size_t size) {
+ constexpr double GetFrequency = 0.75;
+ constexpr ui32 MaxKey = 100;
+ constexpr ui32 MinValue = 1;
+ constexpr ui32 MaxValue = 10;
+
+ TVector<TAction> actions(size);
+ for (auto& action : actions) {
+ action.IsGet = RandomNumber<double>() < GetFrequency;
+ action.Key = ToString(RandomNumber(MaxKey));
+ action.Value = ToString(MinValue + RandomNumber(MaxValue - MinValue));
+ }
+ return actions;
+}
+
+TIntrusivePtr<TPausedClock> MakePausedClock() {
+ return new TPausedClock();
+}
+
+Y_UNIT_TEST_SUITE(LocalCacheTests) {
+
+ Y_UNIT_TEST(OnEmpty_WhenGet_ThenReturnedExpiredDefault) {
+ auto cache = MakeLocalCache<TString, TString>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {});
+
+ auto entry = cache->Get("1").GetValueSync();
+
+ UNIT_ASSERT_VALUES_EQUAL(entry.Value, "");
+ UNIT_ASSERT_VALUES_EQUAL(entry.IsExpired, true);
+ }
+
+ Y_UNIT_TEST(OnEmpty_WhenUpdate_ThenReturnedNew) {
+ auto cache = MakeLocalCache<TString, TString>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {});
+
+ cache->Update("1", "1").GetValueSync();
+
+ auto entry = cache->Get("1").GetValueSync();
+ UNIT_ASSERT_VALUES_EQUAL(entry.Value, "1");
+ UNIT_ASSERT_VALUES_EQUAL(entry.IsExpired, false);
+ }
+
+ Y_UNIT_TEST(OnExistingKey_WhenUpdate_ThenReturnedNew) {
+ auto cache = MakeLocalCache<TString, TString>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {});
+ cache->Update("1", "1");
+
+ cache->Update("1", "2").GetValueSync();
+
+ auto entry = cache->Get("1").GetValueSync();
+ UNIT_ASSERT_VALUES_EQUAL(entry.Value, "2");
+ UNIT_ASSERT_VALUES_EQUAL(entry.IsExpired, false);
+ }
+
+ Y_UNIT_TEST(OnExistingKey_WhenExpires_ThenReturnedOld) {
+ auto clock = MakePausedClock();
+ auto cache = MakeLocalCache<TString, TString>(clock, {.TTL = TDuration::Minutes(2)});
+ cache->Update("1", "1");
+
+ clock->Skip(TDuration::Minutes(2) + TDuration::Seconds(1));
+
+ auto entry = cache->Get("1").GetValueSync();
+ UNIT_ASSERT_VALUES_EQUAL(entry.Value, "1");
+ UNIT_ASSERT_VALUES_EQUAL(entry.IsExpired, true);
+ }
+
+ Y_UNIT_TEST(OnExistingKey_WhenGetResultExtracted_ThenItIsCopied) {
+ auto cache = MakeLocalCache<TString, TString>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {});
+ cache->Update("1", TString(128, '1'));
+
+ cache->Get("1").ExtractValueSync();
+
+ UNIT_ASSERT_VALUES_EQUAL(cache->Get("1").ExtractValueSync().Value, TString(128, '1'));
+ UNIT_ASSERT_VALUES_EQUAL(cache->Get("1").ExtractValueSync().Value, TString(128, '1'));
+ }
+
+ Y_UNIT_TEST(OnFull_WhenFatAdded_ThenSomeKeyIsEvicted) {
+ const size_t Overhead = TByteSize<TFat>()({}) / 10;
+
+ auto cache = MakeLocalCache<int, TFat>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(),
+ {.ByteCapacity = 4 * TByteSize<TFat>()({}) + Overhead});
+ cache->Update(1, {});
+ cache->Update(2, {});
+ cache->Update(3, {});
+ cache->Update(4, {});
+
+ cache->Update(5, {});
+
+ size_t evicted = 0;
+ for (auto x : {1, 2, 3, 4, 5}) {
+ if (cache->Get(x).GetValueSync().IsExpired) {
+ evicted += 1;
+ }
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(evicted, 1);
+ }
+
+ Y_UNIT_TEST(OnFull_WhenFatAdded_ThenKeyAndOverheadAreAccounted) {
+ const size_t Overhead = TByteSize<TFat>()({}) / 10;
+
+ auto cache = MakeLocalCache<TFat, TFat>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(),
+ {.ByteCapacity = 3 * 4 * TByteSize<TFat>()({}) + Overhead});
+ cache->Update(TFat{1}, {});
+ cache->Update(TFat{2}, {});
+ cache->Update(TFat{3}, {});
+ cache->Update(TFat{4}, {});
+
+ cache->Update(TFat{5}, {});
+
+ size_t evicted = 0;
+ for (auto x : {TFat{1}, TFat{2}, TFat{3}, TFat{4}, TFat{5}}) {
+ if (cache->Get(x).GetValueSync().IsExpired) {
+ evicted += 1;
+ }
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(evicted, 1);
+ }
+
+ Y_UNIT_TEST(WhenRandomlyAccessed_ThenDoesNotDie) {
+ constexpr size_t Iterations = 1024 * 1024;
+ SetRandomSeed(1);
+
+ auto cache = MakeLocalCache<TString, TString>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {.ByteCapacity = 64, .TTL = TDuration::MilliSeconds(1)});
+
+ for (auto&& a : GenerateRandomActions(Iterations)) {
+ if (a.IsGet) {
+ Y_DO_NOT_OPTIMIZE_AWAY(cache->Get(a.Key));
+ } else {
+ Y_DO_NOT_OPTIMIZE_AWAY(cache->Update(a.Key, std::move(a.Value)));
+ }
+ }
+ }
+
+ Y_UNIT_TEST(WhenConcurrentlyAccessed_ThenDoesNotDie) {
+ constexpr size_t Threads = 8;
+ constexpr size_t Iterations = Threads * 16 * 1024;
+ SetRandomSeed(1);
+
+ auto cache = MakeLocalCache<TString, TString>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {.ByteCapacity = 64, .TTL = TDuration::MilliSeconds(1)});
+
+ auto pool = CreateThreadPool(Threads);
+ for (auto&& a : GenerateRandomActions(Iterations)) {
+ Y_ENSURE(pool->AddFunc([cache, a = std::move(a)] {
+ if (a.IsGet) {
+ Y_DO_NOT_OPTIMIZE_AWAY(cache->Get(a.Key));
+ } else {
+ Y_DO_NOT_OPTIMIZE_AWAY(cache->Update(a.Key, std::move(a.Value)));
+ }
+ }));
+ }
+ pool->Stop();
+ }
+
+} // Y_UNIT_TEST_SUITE(LocalCacheTests)
diff --git a/yql/essentials/sql/v1/complete/name/cache/local/ut/ya.make b/yql/essentials/sql/v1/complete/name/cache/local/ut/ya.make
new file mode 100644
index 00000000000..85db0e61f88
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/local/ut/ya.make
@@ -0,0 +1,7 @@
+UNITTEST_FOR(yql/essentials/sql/v1/complete/name/cache/local)
+
+SRCS(
+ cache_ut.cpp
+)
+
+END()
diff --git a/yql/essentials/sql/v1/complete/name/cache/local/ya.make b/yql/essentials/sql/v1/complete/name/cache/local/ya.make
new file mode 100644
index 00000000000..433f40f4704
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/local/ya.make
@@ -0,0 +1,17 @@
+LIBRARY()
+
+SRCS(
+ cache.cpp
+)
+
+PEERDIR(
+ yql/essentials/sql/v1/complete/name/cache
+ library/cpp/cache
+ library/cpp/time_provider
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ ut
+)
diff --git a/yql/essentials/sql/v1/complete/name/cache/ut/ya.make b/yql/essentials/sql/v1/complete/name/cache/ut/ya.make
new file mode 100644
index 00000000000..c40d146dc28
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/ut/ya.make
@@ -0,0 +1,11 @@
+UNITTEST_FOR(yql/essentials/sql/v1/complete/name/cache)
+
+SRCS(
+ cached_ut.cpp
+)
+
+PEERDIR(
+ yql/essentials/sql/v1/complete/name/cache/local
+)
+
+END()
diff --git a/yql/essentials/sql/v1/complete/name/cache/ya.make b/yql/essentials/sql/v1/complete/name/cache/ya.make
new file mode 100644
index 00000000000..9d8c18417a4
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/cache/ya.make
@@ -0,0 +1,21 @@
+LIBRARY()
+
+SRCS(
+ byte_size.cpp
+ cache.cpp
+ cached.cpp
+)
+
+PEERDIR(
+ library/cpp/threading/future
+)
+
+END()
+
+RECURSE(
+ local
+)
+
+RECURSE_FOR_TESTS(
+ ut
+)
diff --git a/yql/essentials/sql/v1/complete/name/object/simple/cached/key.h b/yql/essentials/sql/v1/complete/name/object/simple/cached/key.h
new file mode 100644
index 00000000000..c5e678c8d13
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/object/simple/cached/key.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <yql/essentials/sql/v1/complete/name/object/simple/schema.h>
+#include <yql/essentials/sql/v1/complete/name/cache/byte_size.h>
+
+#include <util/generic/string.h>
+#include <util/generic/hash.h>
+
+namespace NSQLComplete {
+
+ struct TSchemaListCacheKey {
+ TString Zone;
+ TString Cluster;
+ TString Folder;
+
+ friend bool operator==(
+ const TSchemaListCacheKey& lhs,
+ const TSchemaListCacheKey& rhs) = default;
+ };
+
+ template <>
+ struct TByteSize<TSchemaListCacheKey> {
+ size_t operator()(const TSchemaListCacheKey& x) const noexcept {
+ return sizeof(x) +
+ TByteSize<TString>()(x.Zone) +
+ TByteSize<TString>()(x.Cluster) +
+ TByteSize<TString>()(x.Folder);
+ }
+ };
+
+ template <>
+ struct TByteSize<TFolderEntry> {
+ size_t operator()(const TFolderEntry& x) const noexcept {
+ return sizeof(x) +
+ TByteSize<TString>()(x.Type) +
+ TByteSize<TString>()(x.Name);
+ }
+ };
+
+} // namespace NSQLComplete
+
+template <>
+struct THash<NSQLComplete::TSchemaListCacheKey> {
+ inline size_t operator()(const NSQLComplete::TSchemaListCacheKey& key) const {
+ return THash<std::tuple<TString, TString, TString>>()(
+ std::tie(key.Zone, key.Cluster, key.Folder));
+ }
+};
diff --git a/yql/essentials/sql/v1/complete/name/object/simple/cached/schema.cpp b/yql/essentials/sql/v1/complete/name/object/simple/cached/schema.cpp
new file mode 100644
index 00000000000..00c4515da58
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/object/simple/cached/schema.cpp
@@ -0,0 +1,49 @@
+#include "schema.h"
+
+#include <yql/essentials/sql/v1/complete/name/cache/cached.h>
+
+namespace NSQLComplete {
+
+ namespace {
+
+ class TSimpleSchema: public ISimpleSchema {
+ public:
+ TSimpleSchema(
+ ISchemaListCache::TPtr Cache,
+ TString Zone,
+ ISimpleSchema::TPtr Origin)
+ : Zone_(std::move(Zone))
+ , Origin_(std::move(Origin))
+ , Query_(std::move(Cache), [origin = Origin_](const TSchemaListCacheKey& key) {
+ return origin->List(key.Cluster, key.Folder);
+ })
+ {
+ }
+
+ TSplittedPath Split(TStringBuf path) const override {
+ return Origin_->Split(path);
+ }
+
+ NThreading::TFuture<TVector<TFolderEntry>>
+ List(TString cluster, TString folder) const override {
+ return Query_({
+ .Zone = Zone_,
+ .Cluster = std::move(cluster),
+ .Folder = std::move(folder),
+ });
+ }
+
+ private:
+ TString Zone_;
+ ISimpleSchema::TPtr Origin_;
+ TCachedQuery<TSchemaListCacheKey, TVector<TFolderEntry>> Query_;
+ };
+
+ } // namespace
+
+ ISimpleSchema::TPtr MakeCachedSimpleSchema(
+ ISchemaListCache::TPtr cache, TString zone, ISimpleSchema::TPtr origin) {
+ return new TSimpleSchema(std::move(cache), std::move(zone), std::move(origin));
+ }
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/object/simple/cached/schema.h b/yql/essentials/sql/v1/complete/name/object/simple/cached/schema.h
new file mode 100644
index 00000000000..3c0545fd6bb
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/object/simple/cached/schema.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "key.h"
+
+#include <yql/essentials/sql/v1/complete/name/object/simple/schema.h>
+
+#include <yql/essentials/sql/v1/complete/name/cache/cache.h>
+
+namespace NSQLComplete {
+
+ using ISchemaListCache = ICache<TSchemaListCacheKey, TVector<TFolderEntry>>;
+
+ ISimpleSchema::TPtr MakeCachedSimpleSchema(
+ ISchemaListCache::TPtr cache, TString zone, ISimpleSchema::TPtr origin);
+
+} // namespace NSQLComplete
diff --git a/yql/essentials/sql/v1/complete/name/object/simple/cached/ya.make b/yql/essentials/sql/v1/complete/name/object/simple/cached/ya.make
new file mode 100644
index 00000000000..1c5e99657ca
--- /dev/null
+++ b/yql/essentials/sql/v1/complete/name/object/simple/cached/ya.make
@@ -0,0 +1,12 @@
+LIBRARY()
+
+SRCS(
+ schema.cpp
+)
+
+PEERDIR(
+ yql/essentials/sql/v1/complete/name/object/simple
+ yql/essentials/sql/v1/complete/name/cache
+)
+
+END()
diff --git a/yql/essentials/sql/v1/complete/name/ya.make b/yql/essentials/sql/v1/complete/name/ya.make
index 0dcc75aabcc..2c435476035 100644
--- a/yql/essentials/sql/v1/complete/name/ya.make
+++ b/yql/essentials/sql/v1/complete/name/ya.make
@@ -8,6 +8,7 @@ PEERDIR(
END()
RECURSE(
+ cache
cluster
object
service
diff --git a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp
index cc39fa875cc..ebc6500b901 100644
--- a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp
+++ b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp
@@ -1,8 +1,10 @@
#include "sql_complete.h"
#include <yql/essentials/sql/v1/complete/syntax/grammar.h>
+#include <yql/essentials/sql/v1/complete/name/cache/local/cache.h>
#include <yql/essentials/sql/v1/complete/name/cluster/static/discovery.h>
#include <yql/essentials/sql/v1/complete/name/object/simple/schema.h>
+#include <yql/essentials/sql/v1/complete/name/object/simple/cached/schema.h>
#include <yql/essentials/sql/v1/complete/name/object/simple/static/schema.h>
#include <yql/essentials/sql/v1/complete/name/service/ranking/frequency.h>
#include <yql/essentials/sql/v1/complete/name/service/ranking/ranking.h>
@@ -1269,4 +1271,39 @@ JOIN yt:$cluster_name.test;
UNIT_ASSERT_UNEQUAL(Complete(engine, {"SELE"}).size(), 0);
}
+ Y_UNIT_TEST(CachedSchema) {
+ TLexerSupplier lexer = MakePureLexerSupplier();
+
+ auto cache = MakeLocalCache<
+ TSchemaListCacheKey, TVector<TFolderEntry>>(
+ NMonotonic::CreateDefaultMonotonicTimeProvider(), {});
+
+ auto aliceService = MakeSchemaNameService(
+ MakeSimpleSchema(
+ MakeCachedSimpleSchema(
+ cache, "alice",
+ MakeStaticSimpleSchema({{"", {{"/", {{"Table", "alice"}}}}}}))));
+
+ auto petyaService = MakeSchemaNameService(
+ MakeSimpleSchema(
+ MakeCachedSimpleSchema(
+ cache, "petya",
+ MakeStaticSimpleSchema({{"", {{"/", {{"Table", "petya"}}}}}}))));
+
+ auto aliceEngine = MakeSqlCompletionEngine(lexer, std::move(aliceService));
+ auto petyaEngine = MakeSqlCompletionEngine(lexer, std::move(petyaService));
+
+ TVector<TCandidate> empty;
+ TVector<TCandidate> aliceExpected = {{TableName, "`alice"}};
+ TVector<TCandidate> petyaExpected = {{TableName, "`petya"}};
+
+ // Cache is empty
+ UNIT_ASSERT_VALUES_EQUAL(Complete(aliceEngine, "SELECT * FROM "), empty);
+ UNIT_ASSERT_VALUES_EQUAL(Complete(petyaEngine, "SELECT * FROM "), empty);
+
+ // Updates in backround
+ UNIT_ASSERT_VALUES_EQUAL(Complete(aliceEngine, "SELECT * FROM "), aliceExpected);
+ UNIT_ASSERT_VALUES_EQUAL(Complete(petyaEngine, "SELECT * FROM "), petyaExpected);
+ }
+
} // Y_UNIT_TEST_SUITE(SqlCompleteTests)
diff --git a/yql/essentials/sql/v1/complete/ut/ya.make b/yql/essentials/sql/v1/complete/ut/ya.make
index d53220ff3b2..0d2afc80d89 100644
--- a/yql/essentials/sql/v1/complete/ut/ya.make
+++ b/yql/essentials/sql/v1/complete/ut/ya.make
@@ -7,8 +7,10 @@ SRCS(
PEERDIR(
yql/essentials/sql/v1/lexer/antlr4_pure
yql/essentials/sql/v1/lexer/antlr4_pure_ansi
+ yql/essentials/sql/v1/complete/name/cache/local
yql/essentials/sql/v1/complete/name/cluster/static
yql/essentials/sql/v1/complete/name/object/simple
+ yql/essentials/sql/v1/complete/name/object/simple/cached
yql/essentials/sql/v1/complete/name/object/simple/static
yql/essentials/sql/v1/complete/name/service/cluster
yql/essentials/sql/v1/complete/name/service/schema