Skip to content

Commit fdca75b

Browse files
Merge 33b35e4 into 4d8e295
2 parents 4d8e295 + 33b35e4 commit fdca75b

File tree

4 files changed

+113
-65
lines changed

4 files changed

+113
-65
lines changed

ydb/core/viewer/json_autocomplete.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class TJsonAutocomplete : public TViewerPipeClient<TJsonAutocomplete> {
8080
Tables.emplace_back(table);
8181
}
8282
Prefix = request.GetPrefix();
83+
Limit = request.GetLimit();
8384

8485
Timeout = ViewerRequest->Get()->Record.GetTimeout();
8586
Direct = true;
@@ -112,6 +113,9 @@ class TJsonAutocomplete : public TViewerPipeClient<TJsonAutocomplete> {
112113
} else {
113114
SearchWord = Prefix;
114115
}
116+
if (Limit == 0) {
117+
Limit = std::numeric_limits<ui32>::max();
118+
}
115119
}
116120

117121
void ParseCgiParameters(const TCgiParameters& params) {
@@ -137,6 +141,9 @@ class TJsonAutocomplete : public TViewerPipeClient<TJsonAutocomplete> {
137141
}
138142
}
139143
Prefix = Prefix.empty() ? requestData["prefix"].GetStringSafe({}) : Prefix;
144+
if (requestData["limit"].IsDefined()) {
145+
Limit = requestData["limit"].GetInteger();
146+
}
140147
}
141148
}
142149

@@ -234,6 +241,7 @@ class TJsonAutocomplete : public TViewerPipeClient<TJsonAutocomplete> {
234241
autocompleteRequest->AddTables(path);
235242
}
236243
autocompleteRequest->SetPrefix(Prefix);
244+
autocompleteRequest->SetLimit(Limit);
237245

238246
ViewerWhiteboardCookie cookie(NKikimrViewer::TEvViewerRequest::kAutocompleteRequest, nodeId);
239247
SendRequest(viewerServiceId, request.Release(), IEventHandle::FlagTrackDelivery | IEventHandle::FlagSubscribeOnSession, cookie.ToUi64());

ydb/core/viewer/protos/viewer.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ message TSchemeCacheRequest {
532532
string Database = 1;
533533
repeated string Tables = 2;
534534
string Prefix = 3;
535+
uint32 Limit = 4;
535536
}
536537

537538
message TEvViewerRequest {

ydb/core/viewer/query_autocomplete_helper.h

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace NKikimr::NViewer {
66

77
inline ui32 LevenshteinDistance(TString word1, TString word2) {
8+
word1 = to_lower(word1);
9+
word2 = to_lower(word2);
810
ui32 size1 = word1.size();
911
ui32 size2 = word2.size();
1012
ui32 dist[size1 + 1][size2 + 1]; // distance matrix
@@ -32,23 +34,50 @@ inline ui32 LevenshteinDistance(TString word1, TString word2) {
3234
template<typename Type>
3335
class FuzzySearcher {
3436
struct WordHit {
35-
ui32 Distance;
37+
bool Contains;
38+
ui32 LengthDifference;
39+
ui32 LevenshteinDistance;
3640
Type Data;
3741

38-
WordHit(ui32 dist, Type data)
39-
: Distance(dist)
42+
WordHit(bool contains, ui32 lengthDifference, ui32 levenshteinDistance, Type data)
43+
: Contains(contains)
44+
, LengthDifference(lengthDifference)
45+
, LevenshteinDistance(levenshteinDistance)
4046
, Data(data)
4147
{}
4248

4349
bool operator<(const WordHit& other) const {
44-
return Distance < other.Distance;
50+
if (this->Contains && !other.Contains) {
51+
return true;
52+
}
53+
if (this->Contains && other.Contains) {
54+
return this->LengthDifference < other.LengthDifference;
55+
}
56+
return this->LevenshteinDistance < other.LevenshteinDistance;
4557
}
4658

4759
bool operator>(const WordHit& other) const {
48-
return Distance > other.Distance;
60+
if (!this->Contains && other.Contains) {
61+
return true;
62+
}
63+
if (this->Contains && other.Contains) {
64+
return this->LengthDifference > other.LengthDifference;
65+
}
66+
return this->LevenshteinDistance > other.LevenshteinDistance;
4967
}
5068
};
5169

70+
static WordHit CalculateWordHit(TString searchWord, TString testWord, Type testData) {
71+
searchWord = to_lower(searchWord);
72+
testWord = to_lower(testWord);
73+
if (testWord.Contains(searchWord)) {
74+
return {1, static_cast<ui32>(testWord.length() - searchWord.length()), 0, testData};
75+
} else {
76+
ui32 levenshteinDistance = LevenshteinDistance(searchWord, testWord);
77+
return {0, 0, levenshteinDistance, testData};
78+
}
79+
}
80+
5281
public:
5382
THashMap<TString, Type> Dictionary;
5483

@@ -63,15 +92,15 @@ class FuzzySearcher {
6392

6493
TVector<Type> Search(const TString& searchWord, ui32 limit = 10) {
6594
auto cmp = [](const WordHit& left, const WordHit& right) {
66-
return left.Distance < right.Distance;
95+
return left < right;
6796
};
6897
std::priority_queue<WordHit, TVector<WordHit>, decltype(cmp)> queue(cmp);
6998

7099
for (const auto& [word, data]: Dictionary) {
71-
auto wordHit = WordHit(LevenshteinDistance(searchWord, word), data);
100+
auto wordHit = CalculateWordHit(searchWord, word, data);
72101
if (queue.size() < limit) {
73102
queue.emplace(wordHit);
74-
} else if (wordHit.Distance < queue.top().Distance) {
103+
} else if (queue.size() > 0 && wordHit < queue.top()) {
75104
queue.pop();
76105
queue.emplace(wordHit);
77106
}

ydb/core/viewer/viewer_ut.cpp

Lines changed: 67 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,7 @@ Y_UNIT_TEST_SUITE(Viewer) {
10401040
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("abc", ""), 3);
10411041
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("apple", "apple"), 0);
10421042
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("apple", "aple"), 1);
1043+
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("UPPER", "upper"), 0);
10431044
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("horse", "ros"), 3);
10441045
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("intention", "execution"), 5);
10451046
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("/slice/db", "/slice"), 3);
@@ -1048,64 +1049,48 @@ Y_UNIT_TEST_SUITE(Viewer) {
10481049
UNIT_ASSERT_VALUES_EQUAL(LevenshteinDistance("/slice/db", "/slice/db26000"), 5);
10491050
}
10501051

1051-
Y_UNIT_TEST(FuzzySearcher)
1052-
{
1053-
TVector<TString> dictionary = { "/slice", "/slice/db", "/slice/db26000" };
1052+
TVector<TString> SimilarWordsDictionary = { "/slice", "/slice/db", "/slice/db26000" };
1053+
TVector<TString> DifferentWordsDictionary = { "/orders", "/peoples", "/OrdinaryScheduleTables" };
10541054

1055-
{
1056-
TVector<TString> expectations = { "/slice/db" };
1057-
auto fuzzy = FuzzySearcher<TString>(dictionary);
1058-
auto result = fuzzy.Search("/slice/db", 1);
1055+
void FuzzySearcherTest(TVector<TString>& dictionary, TString search, ui32 limit, TVector<TString> expectations) {
1056+
auto fuzzy = FuzzySearcher<TString>(dictionary);
1057+
auto result = fuzzy.Search(search, limit);
10591058

1060-
UNIT_ASSERT_VALUES_EQUAL(expectations.size(), result.size());
1061-
for (ui32 i = 0; i < expectations.size(); i++) {
1062-
UNIT_ASSERT_VALUES_EQUAL(expectations[i], result[i]);
1063-
}
1064-
}
1065-
1066-
{
1067-
TVector<TString> expectations = { "/slice/db", "/slice" };
1068-
auto fuzzy = FuzzySearcher<TString>(dictionary);
1069-
auto result = fuzzy.Search("/slice/db", 2);
1070-
1071-
UNIT_ASSERT_VALUES_EQUAL(expectations.size(), result.size());
1072-
for (ui32 i = 0; i < expectations.size(); i++) {
1073-
UNIT_ASSERT_VALUES_EQUAL(expectations[i], result[i]);
1074-
}
1059+
UNIT_ASSERT_VALUES_EQUAL(expectations.size(), result.size());
1060+
for (ui32 i = 0; i < expectations.size(); i++) {
1061+
UNIT_ASSERT_VALUES_EQUAL(expectations[i], result[i]);
10751062
}
1063+
}
10761064

1077-
{
1078-
TVector<TString> expectations = { "/slice/db", "/slice", "/slice/db26000"};
1079-
auto fuzzy = FuzzySearcher<TString>(dictionary);
1080-
auto result = fuzzy.Search("/slice/db", 3);
1065+
Y_UNIT_TEST(FuzzySearcherLimit1OutOf4)
1066+
{
1067+
FuzzySearcherTest(SimilarWordsDictionary, "/slice/db", 1, { "/slice/db" });
1068+
}
10811069

1082-
UNIT_ASSERT_VALUES_EQUAL(expectations.size(), result.size());
1083-
for (ui32 i = 0; i < expectations.size(); i++) {
1084-
UNIT_ASSERT_VALUES_EQUAL(expectations[i], result[i]);
1085-
}
1086-
}
1070+
Y_UNIT_TEST(FuzzySearcherLimit2OutOf4)
1071+
{
1072+
FuzzySearcherTest(SimilarWordsDictionary, "/slice/db", 2, { "/slice/db", "/slice/db26000" });
1073+
}
10871074

1088-
{
1089-
TVector<TString> expectations = { "/slice/db", "/slice", "/slice/db26000" };
1090-
auto fuzzy = FuzzySearcher<TString>(dictionary);
1091-
auto result = fuzzy.Search("/slice/db", 4);
1075+
Y_UNIT_TEST(FuzzySearcherLimit3OutOf4)
1076+
{
1077+
FuzzySearcherTest(SimilarWordsDictionary, "/slice/db", 3, { "/slice/db", "/slice/db26000", "/slice"});
1078+
}
10921079

1093-
UNIT_ASSERT_VALUES_EQUAL(expectations.size(), result.size());
1094-
for (ui32 i = 0; i < expectations.size(); i++) {
1095-
UNIT_ASSERT_VALUES_EQUAL(expectations[i], result[i]);
1096-
}
1097-
}
1080+
Y_UNIT_TEST(FuzzySearcherLimit4OutOf4)
1081+
{
1082+
FuzzySearcherTest(SimilarWordsDictionary, "/slice/db", 4, { "/slice/db", "/slice/db26000", "/slice"});
1083+
}
10981084

1099-
{
1100-
TVector<TString> expectations = { "/slice/db26000", "/slice/db", "/slice" };
1101-
auto fuzzy = FuzzySearcher<TString>(dictionary);
1102-
auto result = fuzzy.Search("/slice/db26001");
1085+
Y_UNIT_TEST(FuzzySearcherLongWord)
1086+
{
1087+
FuzzySearcherTest(SimilarWordsDictionary, "/slice/db26001", 10, { "/slice/db26000", "/slice/db", "/slice"});
1088+
}
11031089

1104-
UNIT_ASSERT_VALUES_EQUAL(expectations.size(), result.size());
1105-
for (ui32 i = 0; i < expectations.size(); i++) {
1106-
UNIT_ASSERT_VALUES_EQUAL(expectations[i], result[i]);
1107-
}
1108-
}
1090+
Y_UNIT_TEST(FuzzySearcherPriority)
1091+
{
1092+
FuzzySearcherTest(DifferentWordsDictionary, "/ord", 10, { "/orders", "/OrdinaryScheduleTables", "/peoples"});
1093+
FuzzySearcherTest(DifferentWordsDictionary, "Tables", 10, { "/OrdinaryScheduleTables", "/orders", "/peoples"});
11091094
}
11101095

11111096
void JsonAutocompleteTest(HTTP_METHOD method, NJson::TJsonValue& value, TString prefix = "", TString database = "", TVector<TString> tables = {}, ui32 limit = 10, bool lowerCaseContentType = false) {
@@ -1136,6 +1121,7 @@ Y_UNIT_TEST_SUITE(Viewer) {
11361121
if (prefix) {
11371122
httpReq.CgiParameters.emplace("prefix", prefix);
11381123
}
1124+
httpReq.CgiParameters.emplace("limit", ToString(limit));
11391125
} else if (method == HTTP_METHOD_POST) {
11401126
NJson::TJsonArray tableArray;
11411127
for (const TString& table : tables) {
@@ -1145,13 +1131,13 @@ Y_UNIT_TEST_SUITE(Viewer) {
11451131
NJson::TJsonValue root = NJson::TJsonMap{
11461132
{"database", database},
11471133
{"table", tableArray},
1148-
{"prefix", prefix}
1134+
{"prefix", prefix},
1135+
{"limit", limit}
11491136
};
11501137
httpReq.PostContent = NJson::WriteJson(root);
1151-
auto contType = lowerCaseContentType ? "content-type" : "Content-Type";
1152-
httpReq.HttpHeaders.AddHeader(contType, "application/json");
1138+
auto contentType = lowerCaseContentType ? "content-type" : "Content-Type";
1139+
httpReq.HttpHeaders.AddHeader(contentType, "application/json");
11531140
}
1154-
httpReq.CgiParameters.emplace("limit", ToString(limit));
11551141
httpReq.CgiParameters.emplace("direct", "1");
11561142
auto page = MakeHolder<TMonPage>("viewer", "title");
11571143
TMonService2HttpRequest monReq(nullptr, &httpReq, nullptr, page.Get(), "/json/autocomplete", nullptr);
@@ -1236,7 +1222,7 @@ Y_UNIT_TEST_SUITE(Viewer) {
12361222
});
12371223
}
12381224

1239-
Y_UNIT_TEST(JsonAutocompleteDatabase) {
1225+
Y_UNIT_TEST(JsonAutocompleteStartOfDatabaseName) {
12401226
NJson::TJsonValue value;
12411227
JsonAutocompleteTest(HTTP_METHOD_GET, value, "/Root");
12421228
VerifyJsonAutocompleteSuccess(value, {
@@ -1246,16 +1232,22 @@ Y_UNIT_TEST_SUITE(Viewer) {
12461232
"/Root/MyDatabase",
12471233
"/Root/TestDatabase"
12481234
});
1235+
}
12491236

1237+
Y_UNIT_TEST(JsonAutocompleteEndOfDatabaseName) {
1238+
NJson::TJsonValue value;
12501239
JsonAutocompleteTest(HTTP_METHOD_GET, value, "Database");
12511240
VerifyJsonAutocompleteSuccess(value, {
1252-
"/Root/test",
12531241
"/Root/MyDatabase",
1254-
"/Root/slice",
12551242
"/Root/TestDatabase",
1243+
"/Root/test",
1244+
"/Root/slice",
12561245
"/Root/qwerty"
12571246
});
1247+
}
12581248

1249+
Y_UNIT_TEST(JsonAutocompleteSimilarDatabaseName) {
1250+
NJson::TJsonValue value;
12591251
JsonAutocompleteTest(HTTP_METHOD_GET, value, "/Root/Database");
12601252
VerifyJsonAutocompleteSuccess(value, {
12611253
"/Root/MyDatabase",
@@ -1264,19 +1256,28 @@ Y_UNIT_TEST_SUITE(Viewer) {
12641256
"/Root/slice",
12651257
"/Root/qwerty"
12661258
});
1259+
}
12671260

1261+
Y_UNIT_TEST(JsonAutocompleteSimilarDatabaseNameWithLimit) {
1262+
NJson::TJsonValue value;
12681263
JsonAutocompleteTest(HTTP_METHOD_GET, value, "/Root/Database", "", {}, 2);
12691264
VerifyJsonAutocompleteSuccess(value, {
12701265
"/Root/MyDatabase",
12711266
"/Root/TestDatabase"
12721267
});
1268+
}
12731269

1270+
Y_UNIT_TEST(JsonAutocompleteSimilarDatabaseNamePOST) {
1271+
NJson::TJsonValue value;
12741272
JsonAutocompleteTest(HTTP_METHOD_POST, value, "/Root/Database", "", {}, 2);
12751273
VerifyJsonAutocompleteSuccess(value, {
12761274
"/Root/MyDatabase",
12771275
"/Root/TestDatabase"
12781276
});
1277+
}
12791278

1279+
Y_UNIT_TEST(JsonAutocompleteSimilarDatabaseNameLowerCase) {
1280+
NJson::TJsonValue value;
12801281
JsonAutocompleteTest(HTTP_METHOD_POST, value, "/Root/Database", "", {}, 2, true);
12811282
VerifyJsonAutocompleteSuccess(value, {
12821283
"/Root/MyDatabase",
@@ -1293,7 +1294,10 @@ Y_UNIT_TEST_SUITE(Viewer) {
12931294
"orders",
12941295
"products"
12951296
});
1297+
}
12961298

1299+
Y_UNIT_TEST(JsonAutocompleteSchemePOST) {
1300+
NJson::TJsonValue value;
12971301
JsonAutocompleteTest(HTTP_METHOD_POST, value, "clien", "/Root/Database");
12981302
VerifyJsonAutocompleteSuccess(value, {
12991303
"clients",
@@ -1302,22 +1306,28 @@ Y_UNIT_TEST_SUITE(Viewer) {
13021306
});
13031307
}
13041308

1305-
Y_UNIT_TEST(JsonAutocompleteColumns) {
1309+
Y_UNIT_TEST(JsonAutocompleteEmptyColumns) {
13061310
NJson::TJsonValue value;
13071311
JsonAutocompleteTest(HTTP_METHOD_GET, value, "", "/Root/Database", {"orders"});
13081312
VerifyJsonAutocompleteSuccess(value, {
13091313
"id",
13101314
"name",
13111315
"description"
13121316
});
1317+
}
13131318

1319+
Y_UNIT_TEST(JsonAutocompleteColumns) {
1320+
NJson::TJsonValue value;
13141321
JsonAutocompleteTest(HTTP_METHOD_GET, value, "nam", "/Root/Database", {"orders", "products"});
13151322
VerifyJsonAutocompleteSuccess(value, {
13161323
"name",
13171324
"id",
13181325
"description",
13191326
});
1327+
}
13201328

1329+
Y_UNIT_TEST(JsonAutocompleteColumnsPOST) {
1330+
NJson::TJsonValue value;
13211331
JsonAutocompleteTest(HTTP_METHOD_POST, value, "nam", "/Root/Database", {"orders", "products"});
13221332
VerifyJsonAutocompleteSuccess(value, {
13231333
"name",

0 commit comments

Comments
 (0)